Skip to content

Commit

Permalink
Merge pull request #861 from europeana/feat/MET-5963-DeBias
Browse files Browse the repository at this point in the history
MET-5963 Add DeBias Report
  • Loading branch information
andyjmaclean authored Oct 25, 2024
2 parents ae57e0d + 4e0c68c commit 8bbc576
Show file tree
Hide file tree
Showing 39 changed files with 1,290 additions and 183 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,6 @@ describe('WorkflowComponent', () => {
});

it('should set the highlighted field', () => {

window.innerHeight = 800;

const fields = [
Expand Down
67 changes: 37 additions & 30 deletions projects/sandbox/src/app/_mocked/mocked-sandbox.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import {
DatasetInfo,
DatasetProgress,
DatasetStatus,
DebiasInfo,
DebiasReport,
DebiasState,
FieldOption,
ProblemPatternsDataset,
ProblemPatternsRecord,
Expand Down Expand Up @@ -149,6 +152,14 @@ export const mockRecordData = [
export class MockSandboxService {
errorMode = false;

getError<T>(msg: string): Observable<T> {
return timer(1).pipe(
switchMap(() => {
return throwError(new Error(msg));
})
);
}

/**
* getCountries
* gets the country options
Expand All @@ -158,6 +169,26 @@ export class MockSandboxService {
return of(mockCountries);
}

getDebiasInfo(datasetId: string): Observable<DebiasInfo> {
return of(({
europeanaId: datasetId,
state: (datasetId as unknown) as DebiasState,
sourceField: 'DC_TITLE'
} as unknown) as DebiasInfo);
}

getDebiasReport(datasetId: string): Observable<DebiasReport> {
if (this.errorMode) {
return this.getError('mock getDebiasReport throws error');
}
return of(({
europeanaId: datasetId,
state: (datasetId as unknown) as DebiasState,
detections: [],
sourceField: 'DC_TITLE'
} as unknown) as DebiasReport);
}

/**
* getLanguages
* gets the language options
Expand All @@ -169,22 +200,14 @@ export class MockSandboxService {

getRecordReport(_: string, __: string): Observable<RecordReport> {
if (this.errorMode) {
return timer(1).pipe(
switchMap(() => {
return throwError(new Error(`mock getRecordReport throws error`));
})
);
return this.getError('mock getRecordReport throws error');
}
return of(mockRecordReport).pipe(delay(1));
}

requestProgress(_: string): Observable<DatasetProgress> {
if (this.errorMode) {
return timer(1).pipe(
switchMap(() => {
return throwError(new Error(`mock requestProgress throws error`));
})
);
return this.getError('mock requestProgress throws error');
}
const res = structuredClone(mockDataset);
res.status = DatasetStatus.COMPLETED;
Expand All @@ -208,11 +231,7 @@ export class MockSandboxService {
`mock submitDataset(${form.value.name}, ${form.value.country}, ${form.value.language}, ${form.value.url}, ${fileNames})`
);
if (this.errorMode) {
return timer(1).pipe(
switchMap(() => {
return throwError(new Error(`mock submitDataset throws error`));
})
);
return this.getError('mock submitDataset throws error');
}
if (form.value.url && form.value.url.indexOf('wrap') > -1) {
return of({
Expand All @@ -232,22 +251,14 @@ export class MockSandboxService {

getProblemPatternsDataset(_: string): Observable<ProblemPatternsDataset> {
if (this.errorMode) {
return timer(1).pipe(
switchMap(() => {
return throwError(new Error(`mock getProblemPatternsDataset throws error`));
})
);
return this.getError('mock getProblemPatternsDataset throws error');
}
return of(mockProblemPatternsDataset).pipe(delay(1));
}

getProblemPatternsRecordWrapped(datasetId: string, _: string): Observable<ProblemPatternsRecord> {
if (this.errorMode) {
return timer(1).pipe(
switchMap(() => {
return throwError(new Error(`mock getProblemPatternsRecordWrapped throws error`));
})
);
return this.getError('mock getProblemPatternsRecordWrapped throws error');
}
return of({
datasetId: datasetId,
Expand All @@ -257,11 +268,7 @@ export class MockSandboxService {

getProcessedRecordData(_: string, __: string): Observable<ProcessedRecordData> {
if (this.errorMode) {
return timer(1).pipe(
switchMap(() => {
return throwError(new Error(`mock getProcessedRecordData throws error`));
})
);
return this.getError('mock getProcessedRecordData throws error');
}
return of(mockProcessedRecordData);
}
Expand Down
46 changes: 46 additions & 0 deletions projects/sandbox/src/app/_models/debias.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
export enum DebiasSourceField {
DCTERMS_ALTERNATIVE = 'DCTERMS_ALTERNATIVE',
DC_DESCRIPTION = 'DC_DESCRIPTION',
DC_TITLE = 'DC_TITLE',
DC_SUBJECT_LITERAL = 'DC_SUBJECT_LITERAL',
DC_SUBJECT_REFERENCE = 'DC_SUBJECT_REFERENCE',
DC_TYPE_LITERAL = 'DC_TYPE_LITERAL',
DC_TYPE_REFERENCE = 'DC_TYPE_REFERENCE'
}

export enum DebiasState {
READY = 'READY',
ERROR = 'ERROR',
PROCESSING = 'PROCESSING',
COMPLETED = 'COMPLETED'
}

export interface DebiasInfo {
'dataset-id': string;
state: DebiasState;
'creation-date': string;
}

export interface DebiasTag {
start: number;
end: number;
length: number;
uri: string;
}

export interface DebiasValue {
language: string;
literal: string;
tags: Array<DebiasTag>;
}

export interface DebiasDetection {
recordId: string;
europeanaId: string;
valueDetection: DebiasValue;
sourceField: DebiasSourceField;
}

export interface DebiasReport extends DebiasInfo {
detections: Array<DebiasDetection>;
}
1 change: 1 addition & 0 deletions projects/sandbox/src/app/_models/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './api-response';
export * from './debias';
export * from './matomo';
export * from './navigation-orbs';
export * from './problem-patterns';
Expand Down
14 changes: 14 additions & 0 deletions projects/sandbox/src/app/_services/sandbox.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
DatasetInfo,
DatasetProgress,
DatasetStatus,
DebiasInfo,
DebiasReport,
FieldOption,
ProblemPattern,
ProblemPatternsDataset,
Expand Down Expand Up @@ -238,4 +240,16 @@ export class SandboxService {
`${apiSettings.apiHost}/dataset/${datasetId}/records-tiers`
);
}

runDebiasReport(datasetId: number): Observable<boolean> {
return this.http.post<boolean>(`${apiSettings.apiHost}/dataset/${datasetId}/debias`, {});
}

getDebiasReport(datasetId: number): Observable<DebiasReport> {
return this.http.get<DebiasReport>(`${apiSettings.apiHost}/dataset/${datasetId}/debias/report`);
}

getDebiasInfo(datasetId: number): Observable<DebiasInfo> {
return this.http.get<DebiasInfo>(`${apiSettings.apiHost}/dataset/${datasetId}/debias/info`);
}
}
27 changes: 27 additions & 0 deletions projects/sandbox/src/app/_translate/format-dc-field.pipe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Pipe, PipeTransform } from '@angular/core';
import { DebiasSourceField } from '../_models';

@Pipe({
name: 'formatDcField',
standalone: true
})
export class FormatDcFieldPipe implements PipeTransform {
transform(value: string): string {
if (value === DebiasSourceField.DC_TITLE) {
return 'dc:title';
} else if (value === DebiasSourceField.DC_DESCRIPTION) {
return 'dc:description';
} else if (value === DebiasSourceField.DC_TYPE_LITERAL) {
return 'dc:type literal';
} else if (value === DebiasSourceField.DC_TYPE_REFERENCE) {
return 'dc:type reference';
} else if (value === DebiasSourceField.DC_SUBJECT_LITERAL) {
return 'dc:subject literal';
} else if (value === DebiasSourceField.DC_SUBJECT_REFERENCE) {
return 'dc:subject reference';
} else if (value === DebiasSourceField.DCTERMS_ALTERNATIVE) {
return 'dc:terms alternative';
}
return value;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/** HighlightMatchesAndLinkPipe
/*
/* a text / html highlighting and link-injecting facility
*/
import { Pipe, PipeTransform } from '@angular/core';
import { DebiasTag } from '../_models';

@Pipe({
name: 'highlightMatchesAndLink',
standalone: true
})
export class HighlightMatchesAndLinkPipe implements PipeTransform {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
transform(value: string, args?: Array<any>): string {
if (args && args.length > 0 && args[0].length > 0) {
const tags = args[0] as Array<DebiasTag>;
if (tags.length === 0) {
return value;
}
const startIndexes = [0];
const endIndexes: Array<number> = [];
const matches: Array<string> = [];
const uris: Array<string> = [];

for (let i = 0; i < tags.length; i++) {
endIndexes.push(tags[i].start); // close off last
startIndexes.push(tags[i].end); // start new
matches.push(value.substr(tags[i].start, tags[i].length));
uris.push(tags[i].uri);
}

endIndexes.push(value.length);

let newStr = '';

// assemble result: for each match-index pair concatenate a substring with the match
startIndexes.forEach((start: number, index: number) => {
newStr += value.substring(start, endIndexes[index]);
if (index < matches.length) {
const tagOpen = `<a href="${uris[index]}" class="term-highlight external-link-debias" target="_blank">`;
const tagClose = '</a>';
newStr += `${tagOpen}${matches[index]}${tagClose}`;
}
});
return newStr;
}
return value;
}
}
2 changes: 2 additions & 0 deletions projects/sandbox/src/app/_translate/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
export * from './format-dc-field.pipe';
export * from './format-harvest-url.pipe';
export * from './format-license.pipe';
export * from './format-tier-dimension.pipe';
export * from './highlight-match.pipe';
export * from './highlight-matches-and-link.pipe';
export * from './rename-status.pipe';
export * from './rename-step.pipe';
62 changes: 62 additions & 0 deletions projects/sandbox/src/app/dataset-info/dataset-info.component.html
Original file line number Diff line number Diff line change
@@ -1,3 +1,50 @@
<ng-template #tmpCtrlDebias let-runEnabled="runEnabled">
<a
tabindex="0"
class="debias-link"
(click)="runOrShowDebiasReport(runEnabled)"
[attr.data-linkText]="(runEnabled ? 'run' : 'view') + ' report'"
>
</a>
</ng-template>

<ng-template #tmpModalHead>
<span class="debias-modal-title">
<ng-container *ngIf="cmpDebias">
<ng-container *ngIf="cmpDebias.debiasReport">
<span
class="modal-title-icon"
[ngClass]="{
'debias-icon': cmpDebias.debiasReport.state === DebiasState.COMPLETED,
'loading-icon': cmpDebias.debiasReport.state !== DebiasState.COMPLETED
}"
>
</span>
</ng-container>
</ng-container>
<span>
Dataset {{ datasetId }} - Debias Report
{{
cmpDebias.debiasReport && cmpDebias.debiasReport.detections.length
? ' (' + (cmpDebias.debiasReport.detections.length | number: '1.0-0') + ')'
: ''
}}
</span>
</span>
</ng-template>

<lib-modal
id="{{ modalIdPrefix }}{{ modalIdDebias }}"
[isSmall]="false"
[buttons]="[{ label: 'Close' }]"
[templateHeadContent]="tmpModalHead"
#modalDebias
>
<ng-container>
<sb-debias [datasetId]="datasetId" #cmpDebias></sb-debias>
</ng-container>
</lib-modal>

<lib-modal
id="{{ modalIdPrefix }}{{ modalIdIncompleteData }}"
[title]="'Dataset Issues Detected'"
Expand Down Expand Up @@ -181,6 +228,14 @@ <h2>
<ng-container *ngTemplateOutlet="tmpProcessingError"></ng-container>
<ng-container *ngTemplateOutlet="tmpLinkPublished"></ng-container>
</li>
<li *ngIf="publishUrl" class="grid-label hide-desktop">
Debias
</li>
<li *ngIf="publishUrl" class="align-right hide-desktop">
<ng-container
*ngTemplateOutlet="tmpCtrlDebias; context: { runEnabled: canRunDebias }"
></ng-container>
</li>

<li class="grid-heading">Uploaded Record Data</li>

Expand Down Expand Up @@ -260,6 +315,13 @@ <h2 class="title-id">{{ datasetInfo['dataset-id'] }}</h2>
</li>
<ng-container *ngTemplateOutlet="tmpProcessingError"></ng-container>
<ng-container *ngTemplateOutlet="tmpLinkPublished"></ng-container>
<li class="debias-link-wrapper">
<ng-container *ngIf="publishUrl">
<ng-container
*ngTemplateOutlet="tmpCtrlDebias; context: { runEnabled: canRunDebias }"
></ng-container>
</ng-container>
</li>
<li
class="container-h transparent"
*ngIf="datasetInfo['transformed-to-edm-external']"
Expand Down
Loading

0 comments on commit 8bbc576

Please sign in to comment.