From e4d30b33242d92b927ac2390b3eb5b8188f7eff4 Mon Sep 17 00:00:00 2001 From: Kyrylo Petrov Date: Wed, 2 Jul 2025 18:34:58 +0300 Subject: [PATCH 1/3] feat(registry-overview): establish revisions --- .../registries-landing.component.html | 2 +- src/app/features/registry/components/index.ts | 1 + .../registry-revisions.component.html | 42 ++++++ .../registry-revisions.component.scss | 18 +++ .../registry-revisions.component.spec.ts | 22 ++++ .../registry-revisions.component.ts | 26 ++++ .../mappers/registry-overview.mapper.ts | 120 ++++++++++-------- .../mappers/registry-schema-block.mapper.ts | 28 ++-- .../get-registry-overview-json-api.model.ts | 9 ++ ...et-registry-schema-block-json-api.model.ts | 1 + .../models/registry-overview.models.ts | 5 + .../models/registry-schema-block.model.ts | 17 ++- .../models/view-schema-block.model.ts | 8 ++ .../registry-overview.component.html | 44 ++----- .../registry-overview.component.scss | 6 + .../registry-overview.component.ts | 29 ++++- .../services/project-overview.service.ts | 7 +- .../registry-overview.state.ts | 6 +- src/assets/i18n/en.json | 2 +- 19 files changed, 282 insertions(+), 111 deletions(-) create mode 100644 src/app/features/registry/components/index.ts create mode 100644 src/app/features/registry/components/registry-revisions/registry-revisions.component.html create mode 100644 src/app/features/registry/components/registry-revisions/registry-revisions.component.scss create mode 100644 src/app/features/registry/components/registry-revisions/registry-revisions.component.spec.ts create mode 100644 src/app/features/registry/components/registry-revisions/registry-revisions.component.ts create mode 100644 src/app/features/registry/models/view-schema-block.model.ts diff --git a/src/app/features/registries/pages/registries-landing/registries-landing.component.html b/src/app/features/registries/pages/registries-landing/registries-landing.component.html index 506e40dcb..14366d157 100644 --- a/src/app/features/registries/pages/registries-landing/registries-landing.component.html +++ b/src/app/features/registries/pages/registries-landing/registries-landing.component.html @@ -1,5 +1,5 @@
-
+
+ + + {{ 'registry.overview.updates' | translate }} + + +
+ @if (registry() && registry()!.schemaResponses.length > 0) { + + @for (revision of registry()!.schemaResponses.slice(1); track $index; let last = $last) { + @if (last) { + + } @else { + + } + } + } +
+
+
+ diff --git a/src/app/features/registry/components/registry-revisions/registry-revisions.component.scss b/src/app/features/registry/components/registry-revisions/registry-revisions.component.scss new file mode 100644 index 000000000..24e8f025d --- /dev/null +++ b/src/app/features/registry/components/registry-revisions/registry-revisions.component.scss @@ -0,0 +1,18 @@ +@use "/assets/styles/variables" as var; +@use "assets/styles/mixins" as mix; + +.accordion-border { + border: 1px solid var.$grey-2; + border-radius: mix.rem(12px); + height: max-content !important; +} + +.no-padding { + --p-accordion-header-padding: 0; +} + +.current-revision { + --p-button-info-background: var(--bg-blue-3); + --p-button-info-hover-background: var(--bg-blue-2); + --p-button-info-hover-border-color: transparent; +} diff --git a/src/app/features/registry/components/registry-revisions/registry-revisions.component.spec.ts b/src/app/features/registry/components/registry-revisions/registry-revisions.component.spec.ts new file mode 100644 index 000000000..4d0d74e21 --- /dev/null +++ b/src/app/features/registry/components/registry-revisions/registry-revisions.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { RegistryRevisionsComponent } from './registry-revisions.component'; + +describe('RegistryRevisionsComponent', () => { + let component: RegistryRevisionsComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [RegistryRevisionsComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(RegistryRevisionsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/features/registry/components/registry-revisions/registry-revisions.component.ts b/src/app/features/registry/components/registry-revisions/registry-revisions.component.ts new file mode 100644 index 000000000..b29de4b6d --- /dev/null +++ b/src/app/features/registry/components/registry-revisions/registry-revisions.component.ts @@ -0,0 +1,26 @@ +import { TranslatePipe } from '@ngx-translate/core'; + +import { Accordion, AccordionContent, AccordionHeader, AccordionPanel } from 'primeng/accordion'; +import { Button } from 'primeng/button'; + +import { ChangeDetectionStrategy, Component, HostBinding, input, output } from '@angular/core'; + +import { RegistryOverview } from '@osf/features/registry/models'; + +@Component({ + selector: 'osf-registry-revisions', + imports: [Accordion, AccordionPanel, AccordionHeader, AccordionContent, Button, TranslatePipe], + templateUrl: './registry-revisions.component.html', + styleUrl: './registry-revisions.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class RegistryRevisionsComponent { + @HostBinding('class') classes = 'flex-1 flex'; + registry = input.required(); + selectedRevisionIndex = input.required(); + openRevision = output(); + + emitOpenRevision(index: number) { + this.openRevision.emit(index); + } +} diff --git a/src/app/features/registry/mappers/registry-overview.mapper.ts b/src/app/features/registry/mappers/registry-overview.mapper.ts index 591b914d9..b557eada7 100644 --- a/src/app/features/registry/mappers/registry-overview.mapper.ts +++ b/src/app/features/registry/mappers/registry-overview.mapper.ts @@ -1,58 +1,68 @@ import { RegistryOverview, RegistryOverviewJsonApiData } from '@osf/features/registry/models'; -export function MapRegistryOverview(data: RegistryOverviewJsonApiData): RegistryOverview { - return { - id: data.id, - type: data.type, - isPublic: data.attributes.public, - forksCount: data?.relationships?.forks?.links?.related?.meta?.count || 0, - title: data.attributes.title, - description: data.attributes?.description, - dateModified: data.attributes?.date_modified, - dateCreated: data.attributes?.date_created, - dateRegistered: data.attributes?.date_registered, - category: data.attributes?.category, - isFork: data.attributes?.fork, - accessRequestsEnabled: data.attributes?.accessRequestsEnabled, - nodeLicense: data.attributes.node_license - ? { - copyrightHolders: data.attributes.node_license.copyright_holders, - year: data.attributes.node_license.year, - } - : undefined, - license: data.embeds.license?.data?.attributes, - registrationType: data.attributes?.registration_supplement, - doi: data.attributes?.doi, - tags: data.attributes?.tags, - contributors: data.embeds?.bibliographic_contributors?.data.map((contributor) => ({ - id: contributor?.embeds?.users?.data?.id, - familyName: contributor?.embeds?.users?.data?.attributes?.family_name, - fullName: contributor?.embeds?.users?.data?.attributes?.full_name, - givenName: contributor?.embeds?.users?.data?.attributes?.given_name, - middleName: contributor?.embeds?.users?.data?.attributes?.middle_names, - type: contributor?.embeds?.users?.data?.type, - })), - identifiers: data.embeds.identifiers?.data.map((identifier) => ({ - id: identifier.id, - type: identifier.type, - value: identifier.attributes.value, - category: identifier.attributes.category, - })), - analyticsKey: data.attributes?.analyticsKey, - currentUserCanComment: data.attributes.current_user_can_comment, - currentUserPermissions: data.attributes.current_user_permissions, - currentUserIsContributor: data.attributes.current_user_is_contributor, - currentUserIsContributorOrGroupMember: data.attributes.current_user_is_contributor_or_group_member, - citation: data.relationships?.citation?.data?.id, - wikiEnabled: data.attributes.wikiEnabled, - region: data.relationships.region?.data, - hasData: data.attributes.has_data, - hasAnalyticCode: data.attributes.has_analytic_code, - hasMaterials: data.attributes.has_materials, - hasPapers: data.attributes.has_papers, - hasSupplements: data.attributes.has_supplements, - questions: data.attributes.registration_responses, - registrationSchemaLink: data.relationships.registration_schema.links.related.href, - associatedProjectId: data.relationships?.registered_from?.data?.id, - } as RegistryOverview; +export function MapRegistryOverview(data: RegistryOverviewJsonApiData): RegistryOverview | null { + try { + return { + id: data.id, + type: data.type, + isPublic: data.attributes.public, + forksCount: data?.relationships?.forks?.links?.related?.meta?.count || 0, + title: data.attributes.title, + description: data.attributes?.description, + dateModified: data.attributes?.date_modified, + dateCreated: data.attributes?.date_created, + dateRegistered: data.attributes?.date_registered, + category: data.attributes?.category, + isFork: data.attributes?.fork, + accessRequestsEnabled: data.attributes?.accessRequestsEnabled, + nodeLicense: data.attributes.node_license + ? { + copyrightHolders: data.attributes.node_license.copyright_holders, + year: data.attributes.node_license.year, + } + : undefined, + license: data.embeds.license?.data?.attributes, + registrationType: data.attributes?.registration_supplement, + doi: data.attributes?.doi, + tags: data.attributes?.tags, + contributors: data.embeds?.bibliographic_contributors?.data.map((contributor) => ({ + id: contributor?.embeds?.users?.data?.id, + familyName: contributor?.embeds?.users?.data?.attributes?.family_name, + fullName: contributor?.embeds?.users?.data?.attributes?.full_name, + givenName: contributor?.embeds?.users?.data?.attributes?.given_name, + middleName: contributor?.embeds?.users?.data?.attributes?.middle_names, + type: contributor?.embeds?.users?.data?.type, + })), + identifiers: data.embeds.identifiers?.data.map((identifier) => ({ + id: identifier.id, + type: identifier.type, + value: identifier.attributes.value, + category: identifier.attributes.category, + })), + analyticsKey: data.attributes?.analyticsKey, + currentUserCanComment: data.attributes.current_user_can_comment, + currentUserPermissions: data.attributes.current_user_permissions, + currentUserIsContributor: data.attributes.current_user_is_contributor, + currentUserIsContributorOrGroupMember: data.attributes.current_user_is_contributor_or_group_member, + citation: data.relationships?.citation?.data?.id, + wikiEnabled: data.attributes.wikiEnabled, + region: data.relationships.region?.data, + hasData: data.attributes.has_data, + hasAnalyticCode: data.attributes.has_analytic_code, + hasMaterials: data.attributes.has_materials, + hasPapers: data.attributes.has_papers, + hasSupplements: data.attributes.has_supplements, + questions: data.attributes.registration_responses, + registrationSchemaLink: data.relationships.registration_schema.links.related.href, + associatedProjectId: data.relationships?.registered_from?.data?.id, + schemaResponses: data.embeds?.schema_responses?.data?.map((schemaResponse) => ({ + id: schemaResponse.id, + revisionResponses: schemaResponse.attributes?.revision_responses, + updatedResponseKeys: schemaResponse.attributes?.updated_response_keys, + })), + } as RegistryOverview; + } catch (error) { + console.error(error); + return null; + } } diff --git a/src/app/features/registry/mappers/registry-schema-block.mapper.ts b/src/app/features/registry/mappers/registry-schema-block.mapper.ts index f73fded26..d745d4cce 100644 --- a/src/app/features/registry/mappers/registry-schema-block.mapper.ts +++ b/src/app/features/registry/mappers/registry-schema-block.mapper.ts @@ -1,21 +1,19 @@ import { RegistrationQuestions, RegistrySchemaBlock, SchemaBlockAttributes } from '@osf/features/registry/models'; +import { ViewSchemaBlock } from '@osf/features/registry/models/view-schema-block.model'; -export function MapRegistrySchemaBlock( - block: SchemaBlockAttributes, - questions: RegistrationQuestions -): RegistrySchemaBlock { +export function MapViewSchemaBlock(block: RegistrySchemaBlock, questions: RegistrationQuestions): ViewSchemaBlock { const schemaBlock = { required: block.required, - type: block.block_type, + type: block.blockType, value: '', values: [], files: [], - } as RegistrySchemaBlock; + } as ViewSchemaBlock; - if (block.display_text) { - schemaBlock.value = block.display_text; - } else if (block.registration_response_key) { - const questionValue = questions[block.registration_response_key]; + if (block.displayText) { + schemaBlock.value = block.displayText; + } else if (block.registrationResponseKey) { + const questionValue = questions[block.registrationResponseKey]; if (questionValue && Array.isArray(questionValue)) { if (schemaBlock.type === 'multi-select-input') { @@ -33,3 +31,13 @@ export function MapRegistrySchemaBlock( return schemaBlock; } + +export function MapRegistrySchemaBlock(block: SchemaBlockAttributes): RegistrySchemaBlock { + return { + blockType: block.block_type, + displayText: block.display_text, + registrationResponseKey: block?.registration_response_key, + required: block.required, + schemaBlockGroupKey: block.schema_block_group_key, + }; +} diff --git a/src/app/features/registry/models/get-registry-overview-json-api.model.ts b/src/app/features/registry/models/get-registry-overview-json-api.model.ts index 9be0c0156..5e0f250f8 100644 --- a/src/app/features/registry/models/get-registry-overview-json-api.model.ts +++ b/src/app/features/registry/models/get-registry-overview-json-api.model.ts @@ -83,6 +83,15 @@ export interface RegistryOverviewJsonApiEmbed { }; }[]; }; + schema_responses: { + data: { + id: string; + attributes: { + revision_responses: RegistrationQuestions; + updated_response_keys: string[]; + }; + }[]; + }; } export interface RegistryOverviewJsonApiRelationships { diff --git a/src/app/features/registry/models/get-registry-schema-block-json-api.model.ts b/src/app/features/registry/models/get-registry-schema-block-json-api.model.ts index dd4db0c5e..2069e5887 100644 --- a/src/app/features/registry/models/get-registry-schema-block-json-api.model.ts +++ b/src/app/features/registry/models/get-registry-schema-block-json-api.model.ts @@ -7,4 +7,5 @@ export interface SchemaBlockAttributes { display_text: string; registration_response_key: string | null; required: boolean; + schema_block_group_key: string; } diff --git a/src/app/features/registry/models/registry-overview.models.ts b/src/app/features/registry/models/registry-overview.models.ts index cd1617120..de3aafecc 100644 --- a/src/app/features/registry/models/registry-overview.models.ts +++ b/src/app/features/registry/models/registry-overview.models.ts @@ -53,4 +53,9 @@ export interface RegistryOverview { questions: RegistrationQuestions; registrationSchemaLink: string; associatedProjectId: string; + schemaResponses: { + id: string; + revisionResponses: RegistrationQuestions; + updatedResponseKeys: string[]; + }[]; } diff --git a/src/app/features/registry/models/registry-schema-block.model.ts b/src/app/features/registry/models/registry-schema-block.model.ts index 27dd1e560..6ef651e1b 100644 --- a/src/app/features/registry/models/registry-schema-block.model.ts +++ b/src/app/features/registry/models/registry-schema-block.model.ts @@ -1,8 +1,15 @@ +// export interface RegistrySchemaBlock { +// value: string; +// values: string[]; +// files?: { id: string; name: string }[]; +// required: boolean; +// html?: string; +// type: string; +// } export interface RegistrySchemaBlock { - value: string; - values: string[]; - files?: { id: string; name: string }[]; + blockType: string; + displayText: string; required: boolean; - html?: string; - type: string; + schemaBlockGroupKey: string; + registrationResponseKey: string | null; } diff --git a/src/app/features/registry/models/view-schema-block.model.ts b/src/app/features/registry/models/view-schema-block.model.ts new file mode 100644 index 000000000..432753f5d --- /dev/null +++ b/src/app/features/registry/models/view-schema-block.model.ts @@ -0,0 +1,8 @@ +export interface ViewSchemaBlock { + value: string; + values: string[]; + files?: { id: string; name: string }[]; + required: boolean; + html?: string; + type: string; +} diff --git a/src/app/features/registry/pages/registry-overview/registry-overview.component.html b/src/app/features/registry/pages/registry-overview/registry-overview.component.html index a492cedd8..7e0593e2f 100644 --- a/src/app/features/registry/pages/registry-overview/registry-overview.component.html +++ b/src/app/features/registry/pages/registry-overview/registry-overview.component.html @@ -1,8 +1,10 @@ @if (!isRegistryLoading() && !isSubjectsLoading() && !isInstitutionsLoading() && !isSchemaBlocksLoading()) { -
+
-
+
-
-
+
+
-
+
@@ -71,33 +73,15 @@

{{ 'common.labels.public' | translate }}

- - - - {{ 'registry.overview.updates' | translate }} - - -
- - - -
-
-
-
+ +
- @for (block of schemaBlocks(); track $index) { + @for (block of mappedSchemaBlocks(); track $index) { @if (block.type === 'file-input' && block.files) {
    @for (file of block.files; track $index) { diff --git a/src/app/features/registry/pages/registry-overview/registry-overview.component.scss b/src/app/features/registry/pages/registry-overview/registry-overview.component.scss index 90ecba76c..62ffbec5f 100644 --- a/src/app/features/registry/pages/registry-overview/registry-overview.component.scss +++ b/src/app/features/registry/pages/registry-overview/registry-overview.component.scss @@ -41,3 +41,9 @@ .no-padding { --p-accordion-header-padding: 0; } + +.current-revision { + --p-button-info-background: var(--bg-blue-3); + --p-button-info-hover-background: var(--bg-blue-2); + --p-button-info-hover-border-color: transparent; +} diff --git a/src/app/features/registry/pages/registry-overview/registry-overview.component.ts b/src/app/features/registry/pages/registry-overview/registry-overview.component.ts index ec8840d9e..621baebcf 100644 --- a/src/app/features/registry/pages/registry-overview/registry-overview.component.ts +++ b/src/app/features/registry/pages/registry-overview/registry-overview.component.ts @@ -3,14 +3,16 @@ import { createDispatchMap, select } from '@ngxs/store'; import { TranslatePipe } from '@ngx-translate/core'; import { Accordion, AccordionContent, AccordionHeader, AccordionPanel } from 'primeng/accordion'; -import { Button } from 'primeng/button'; import { DialogService } from 'primeng/dynamicdialog'; -import { ChangeDetectionStrategy, Component, computed, HostBinding, inject } from '@angular/core'; +import { ChangeDetectionStrategy, Component, computed, HostBinding, inject, signal } from '@angular/core'; import { ActivatedRoute, Router, RouterLink } from '@angular/router'; import { GetBookmarksCollectionId } from '@osf/features/collections/store'; import { OverviewToolbarComponent } from '@osf/features/project/overview/components'; +import { RegistryRevisionsComponent } from '@osf/features/registry/components'; +import { MapViewSchemaBlock } from '@osf/features/registry/mappers'; +import { RegistrationQuestions } from '@osf/features/registry/models'; import { GetRegistryById, GetRegistryInstitutions, @@ -35,7 +37,7 @@ import { ToolbarResource } from '@shared/models'; AccordionPanel, AccordionHeader, ResourceMetadataComponent, - Button, + RegistryRevisionsComponent, ], templateUrl: './registry-overview.component.html', styleUrl: './registry-overview.component.scss', @@ -65,6 +67,23 @@ export class RegistryOverviewComponent { } return null; }); + protected readonly mappedSchemaBlocks = computed(() => { + const schemaBlocks = this.schemaBlocks(); + const index = this.selectedRevisionIndex(); + let questions: RegistrationQuestions | undefined; + if (index === 0) { + questions = this.registry()?.questions; + } else if (this.registry()?.schemaResponses?.length) { + questions = this.registry()?.schemaResponses?.[index]?.revisionResponses; + } + + if (schemaBlocks?.length && questions) { + return schemaBlocks.map((schemaBlock) => MapViewSchemaBlock(schemaBlock, questions)); + } + return []; + }); + + protected readonly selectedRevisionIndex = signal(0); protected toolbarResource = computed(() => { if (this.registry()) { @@ -102,4 +121,8 @@ export class RegistryOverviewComponent { navigateToFile(fileId: string): void { this.router.navigate(['/files', fileId]); } + + openRevision(revisionIndex: number): void { + this.selectedRevisionIndex.set(revisionIndex); + } } diff --git a/src/app/features/registry/services/project-overview.service.ts b/src/app/features/registry/services/project-overview.service.ts index 25c9a0d2c..c97ed6251 100644 --- a/src/app/features/registry/services/project-overview.service.ts +++ b/src/app/features/registry/services/project-overview.service.ts @@ -9,7 +9,6 @@ import { GetRegistryOverviewJsonApi, GetRegistrySchemaBlockJsonApi, GetResourceSubjectsJsonApi, - RegistrationQuestions, RegistryInstitution, RegistryOverview, RegistrySchemaBlock, @@ -24,7 +23,7 @@ import { environment } from 'src/environments/environment'; export class RegistryOverviewService { private jsonApiService = inject(JsonApiService); - getRegistrationById(id: string): Observable { + getRegistrationById(id: string): Observable { const params = { related_counts: 'forks,comments,linked_nodes,linked_registrations,children,wikis', 'embed[]': [ @@ -73,7 +72,7 @@ export class RegistryOverviewService { ); } - getSchemaBlocks(schemaLink: string, questions: RegistrationQuestions): Observable { + getSchemaBlocks(schemaLink: string): Observable { const params = { 'page[size]': 100, page: 1, @@ -81,6 +80,6 @@ export class RegistryOverviewService { return this.jsonApiService .get(`${schemaLink}schema_blocks`, params) - .pipe(map((response) => response.data.map((block) => MapRegistrySchemaBlock(block.attributes, questions)))); + .pipe(map((response) => response.data.map((block) => MapRegistrySchemaBlock(block.attributes)))); } } diff --git a/src/app/features/registry/store/registry-overview/registry-overview.state.ts b/src/app/features/registry/store/registry-overview/registry-overview.state.ts index 383178b22..3cdfa7563 100644 --- a/src/app/features/registry/store/registry-overview/registry-overview.state.ts +++ b/src/app/features/registry/store/registry-overview/registry-overview.state.ts @@ -64,7 +64,9 @@ export class RegistryOverviewState { error: null, }, }); - ctx.dispatch(new GetSchemaBlocks(registryOverview.registrationSchemaLink, registryOverview.questions)); + if (registryOverview?.registrationSchemaLink && registryOverview?.questions) { + ctx.dispatch(new GetSchemaBlocks(registryOverview.registrationSchemaLink, registryOverview.questions)); + } }, }), catchError((error) => this.handleError(ctx, 'registry', error)) @@ -133,7 +135,7 @@ export class RegistryOverviewState { }, }); - return this.registryOverviewService.getSchemaBlocks(action.schemaLink, action.questions).pipe( + return this.registryOverviewService.getSchemaBlocks(action.schemaLink).pipe( tap({ next: (schemaBlocks) => { ctx.patchState({ diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 0b763e9d1..b2147e1d6 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -1653,7 +1653,7 @@ "associatedProject": "Associated project" }, "original": "Original", - "last": "Last" + "latest": "Latest" } }, "truncatedText": { From 39d3164bf92816c338017afca0740a2ab1c31406 Mon Sep 17 00:00:00 2001 From: Kyrylo Petrov Date: Fri, 4 Jul 2025 18:15:17 +0300 Subject: [PATCH 2/3] feat(registry-overvoew): registry revisions, statuses --- .../fork-dialog/fork-dialog.component.html | 6 +- .../fork-dialog/fork-dialog.component.ts | 16 ++-- .../overview-toolbar.component.ts | 10 ++- .../services/project-overview.service.ts | 2 +- .../store/project-overview.actions.ts | 2 +- .../overview/store/project-overview.state.ts | 5 +- src/app/features/registry/components/index.ts | 2 + .../registry-revisions.component.html | 8 ++ .../registry-revisions.component.ts | 3 + .../registry-statuses.component.html | 28 +++++++ .../registry-statuses.component.scss | 12 +++ .../registry-statuses.component.spec.ts | 22 ++++++ .../registry-statuses.component.ts | 61 +++++++++++++++ .../withdraw-dialog.component.html | 19 +++++ .../withdraw-dialog.component.scss | 0 .../withdraw-dialog.component.spec.ts | 22 ++++++ .../withdraw-dialog.component.ts | 44 +++++++++++ .../mappers/registry-overview.mapper.ts | 3 + .../get-registry-overview-json-api.model.ts | 8 ++ .../models/registry-overview.models.ts | 3 + .../registry-overview.component.html | 66 +++------------- .../registry-overview.component.scss | 12 --- .../registry-overview.component.ts | 19 +++-- src/app/features/registry/services/index.ts | 2 +- ...ervice.ts => registry-overview.service.ts} | 36 +++++++++ .../registry-overview.actions.ts | 15 ++++ .../registry-overview.state.ts | 60 +++++++++++++++ .../data-resources.component.html | 42 +++++++++++ .../data-resources.component.scss | 14 ++++ .../data-resources.component.spec.ts | 22 ++++++ .../data-resources.component.ts | 20 +++++ src/app/shared/components/index.ts | 1 + .../resource-card.component.html | 54 +++---------- .../resource-card.component.scss | 8 +- .../resource-card/resource-card.component.ts | 2 + src/app/shared/enums/index.ts | 3 + .../enums/registration-review-states.enum.ts | 10 +++ src/app/shared/enums/registry-status.enum.ts | 14 ++++ .../enums/revision-review-states.enum.ts | 6 ++ src/app/shared/mappers/index.ts | 1 + src/app/shared/mappers/registry/index.ts | 1 + .../registry/map-registry-status.mapper.ts | 30 ++++++++ .../models/confirmation-options.model.ts | 2 +- .../services/custom-confirmation.service.ts | 6 +- src/assets/i18n/en.json | 75 +++++++++++++++++-- 45 files changed, 651 insertions(+), 146 deletions(-) create mode 100644 src/app/features/registry/components/registry-statuses/registry-statuses.component.html create mode 100644 src/app/features/registry/components/registry-statuses/registry-statuses.component.scss create mode 100644 src/app/features/registry/components/registry-statuses/registry-statuses.component.spec.ts create mode 100644 src/app/features/registry/components/registry-statuses/registry-statuses.component.ts create mode 100644 src/app/features/registry/components/withdraw-dialog/withdraw-dialog.component.html create mode 100644 src/app/features/registry/components/withdraw-dialog/withdraw-dialog.component.scss create mode 100644 src/app/features/registry/components/withdraw-dialog/withdraw-dialog.component.spec.ts create mode 100644 src/app/features/registry/components/withdraw-dialog/withdraw-dialog.component.ts rename src/app/features/registry/services/{project-overview.service.ts => registry-overview.service.ts} (71%) create mode 100644 src/app/shared/components/data-resources/data-resources.component.html create mode 100644 src/app/shared/components/data-resources/data-resources.component.scss create mode 100644 src/app/shared/components/data-resources/data-resources.component.spec.ts create mode 100644 src/app/shared/components/data-resources/data-resources.component.ts create mode 100644 src/app/shared/enums/registration-review-states.enum.ts create mode 100644 src/app/shared/enums/registry-status.enum.ts create mode 100644 src/app/shared/enums/revision-review-states.enum.ts create mode 100644 src/app/shared/mappers/registry/index.ts create mode 100644 src/app/shared/mappers/registry/map-registry-status.mapper.ts diff --git a/src/app/features/project/overview/components/fork-dialog/fork-dialog.component.html b/src/app/features/project/overview/components/fork-dialog/fork-dialog.component.html index aab189f9e..03cd469a4 100644 --- a/src/app/features/project/overview/components/fork-dialog/fork-dialog.component.html +++ b/src/app/features/project/overview/components/fork-dialog/fork-dialog.component.html @@ -1,5 +1,9 @@

    - {{ 'project.overview.dialog.fork.message' | translate }} + @if (config.data.resource.resourceType === ResourceType.Project) { + {{ 'project.overview.dialog.fork.messageProject' | translate }} + } @else if (config.data.resource.resourceType === ResourceType.Registration) { + {{ 'project.overview.dialog.fork.messageRegistry' | translate }} + }

    diff --git a/src/app/features/project/overview/components/fork-dialog/fork-dialog.component.ts b/src/app/features/project/overview/components/fork-dialog/fork-dialog.component.ts index b8e262ac5..2d4a1c2dc 100644 --- a/src/app/features/project/overview/components/fork-dialog/fork-dialog.component.ts +++ b/src/app/features/project/overview/components/fork-dialog/fork-dialog.component.ts @@ -5,10 +5,13 @@ import { TranslatePipe } from '@ngx-translate/core'; import { Button } from 'primeng/button'; import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog'; +import { finalize } from 'rxjs'; + import { ChangeDetectionStrategy, Component, DestroyRef, inject } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { ForkResource, ProjectOverviewSelectors } from '@osf/features/project/overview/store'; +import { ResourceType } from '@shared/enums'; import { ToolbarResource } from '@shared/models'; import { ToastService } from '@shared/services'; @@ -33,12 +36,15 @@ export class ForkDialogComponent { this.store .dispatch(new ForkResource(resource.id, resource.resourceType)) - .pipe(takeUntilDestroyed(this.destroyRef)) - .subscribe({ - next: () => { + .pipe( + takeUntilDestroyed(this.destroyRef), + finalize(() => { this.dialogRef.close(); this.toastService.showSuccess('project.overview.dialog.toast.fork.success'); - }, - }); + }) + ) + .subscribe(); } + + protected readonly ResourceType = ResourceType; } diff --git a/src/app/features/project/overview/components/overview-toolbar/overview-toolbar.component.ts b/src/app/features/project/overview/components/overview-toolbar/overview-toolbar.component.ts index f84dea1a2..0b682b9d9 100644 --- a/src/app/features/project/overview/components/overview-toolbar/overview-toolbar.component.ts +++ b/src/app/features/project/overview/components/overview-toolbar/overview-toolbar.component.ts @@ -132,15 +132,21 @@ export class OverviewToolbarComponent { private handleForkResource(): void { const resource = this.currentResource(); + const headerTranslation = + resource?.resourceType === ResourceType.Project + ? 'project.overview.dialog.fork.headerProject' + : resource?.resourceType === ResourceType.Registration + ? 'project.overview.dialog.fork.headerRegistry' + : ''; if (resource) { this.dialogService.open(ForkDialogComponent, { focusOnShow: false, - header: this.translateService.instant('project.overview.dialog.fork.header'), + header: this.translateService.instant(headerTranslation), closeOnEscape: true, modal: true, closable: true, data: { - resourceId: resource, + resource: resource, }, }); } diff --git a/src/app/features/project/overview/services/project-overview.service.ts b/src/app/features/project/overview/services/project-overview.service.ts index 2775e166c..c66ffaf90 100644 --- a/src/app/features/project/overview/services/project-overview.service.ts +++ b/src/app/features/project/overview/services/project-overview.service.ts @@ -59,7 +59,7 @@ export class ProjectOverviewService { forkResource(projectId: string, resourceType: string): Observable { const payload = { data: { - type: resourceType, + type: 'nodes', }, }; diff --git a/src/app/features/project/overview/store/project-overview.actions.ts b/src/app/features/project/overview/store/project-overview.actions.ts index 8c5f24957..c7a5fd259 100644 --- a/src/app/features/project/overview/store/project-overview.actions.ts +++ b/src/app/features/project/overview/store/project-overview.actions.ts @@ -19,7 +19,7 @@ export class ForkResource { static readonly type = '[Project Overview] Fork Resource'; constructor( - public projectId: string, + public resourceId: string, public resourceType: ResourceType ) {} } diff --git a/src/app/features/project/overview/store/project-overview.state.ts b/src/app/features/project/overview/store/project-overview.state.ts index 67194a748..dfa899118 100644 --- a/src/app/features/project/overview/store/project-overview.state.ts +++ b/src/app/features/project/overview/store/project-overview.state.ts @@ -127,12 +127,9 @@ export class ProjectOverviewState { case ResourceType.Registration: resourceType = 'registrations'; break; - case ResourceType.Preprint: - resourceType = 'preprints'; - break; } - return this.projectOverviewService.forkResource(action.projectId, resourceType).pipe( + return this.projectOverviewService.forkResource(action.resourceId, resourceType).pipe( tap(() => { ctx.patchState({ project: { diff --git a/src/app/features/registry/components/index.ts b/src/app/features/registry/components/index.ts index 9614cbd4d..3136382aa 100644 --- a/src/app/features/registry/components/index.ts +++ b/src/app/features/registry/components/index.ts @@ -1 +1,3 @@ export * from './registry-revisions/registry-revisions.component'; +export * from './registry-statuses/registry-statuses.component'; +export * from './withdraw-dialog/withdraw-dialog.component'; diff --git a/src/app/features/registry/components/registry-revisions/registry-revisions.component.html b/src/app/features/registry/components/registry-revisions/registry-revisions.component.html index c70d90d6d..b8e4606ee 100644 --- a/src/app/features/registry/components/registry-revisions/registry-revisions.component.html +++ b/src/app/features/registry/components/registry-revisions/registry-revisions.component.html @@ -35,6 +35,14 @@ /> } } + + + @if (registry()?.revisionStatus === RevisionReviewStates.Approved) { + + } + @if (registry()?.revisionStatus === RevisionReviewStates.RevisionInProgress) { + + } }
    diff --git a/src/app/features/registry/components/registry-revisions/registry-revisions.component.ts b/src/app/features/registry/components/registry-revisions/registry-revisions.component.ts index b29de4b6d..a1f90b92a 100644 --- a/src/app/features/registry/components/registry-revisions/registry-revisions.component.ts +++ b/src/app/features/registry/components/registry-revisions/registry-revisions.component.ts @@ -6,6 +6,7 @@ import { Button } from 'primeng/button'; import { ChangeDetectionStrategy, Component, HostBinding, input, output } from '@angular/core'; import { RegistryOverview } from '@osf/features/registry/models'; +import { RevisionReviewStates } from '@shared/enums'; @Component({ selector: 'osf-registry-revisions', @@ -23,4 +24,6 @@ export class RegistryRevisionsComponent { emitOpenRevision(index: number) { this.openRevision.emit(index); } + + protected readonly RevisionReviewStates = RevisionReviewStates; } diff --git a/src/app/features/registry/components/registry-statuses/registry-statuses.component.html b/src/app/features/registry/components/registry-statuses/registry-statuses.component.html new file mode 100644 index 000000000..7aa6a3204 --- /dev/null +++ b/src/app/features/registry/components/registry-statuses/registry-statuses.component.html @@ -0,0 +1,28 @@ + + + +

    {{ 'registry.overview.statuses.' + registry()?.status + '.text' | translate }}

    +
    + +
    +

    {{ 'registry.overview.statuses.' + registry()?.status + '.short' | translate }}

    +

    {{ 'registry.overview.statuses.' + registry()?.status + '.long' | translate }}

    + @if (registry()?.status === RegistryStatus.InProgress && registry()?.status === RegistryStatus.Accepted) { + + } + @if (registry()?.status === RegistryStatus.Embargo) { + + } +
    +
    +
    +
    diff --git a/src/app/features/registry/components/registry-statuses/registry-statuses.component.scss b/src/app/features/registry/components/registry-statuses/registry-statuses.component.scss new file mode 100644 index 000000000..8af844a39 --- /dev/null +++ b/src/app/features/registry/components/registry-statuses/registry-statuses.component.scss @@ -0,0 +1,12 @@ +@use "/assets/styles/variables" as var; +@use "assets/styles/mixins" as mix; + +.accordion-border { + border: 1px solid var.$grey-2; + border-radius: mix.rem(12px); + height: max-content !important; +} + +.no-padding { + --p-accordion-header-padding: 0; +} diff --git a/src/app/features/registry/components/registry-statuses/registry-statuses.component.spec.ts b/src/app/features/registry/components/registry-statuses/registry-statuses.component.spec.ts new file mode 100644 index 000000000..0ed8cdc6f --- /dev/null +++ b/src/app/features/registry/components/registry-statuses/registry-statuses.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { RegistryStatusesComponent } from './registry-statuses.component'; + +describe('RegistryStatusesComponent', () => { + let component: RegistryStatusesComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [RegistryStatusesComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(RegistryStatusesComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/features/registry/components/registry-statuses/registry-statuses.component.ts b/src/app/features/registry/components/registry-statuses/registry-statuses.component.ts new file mode 100644 index 000000000..f036b8b3d --- /dev/null +++ b/src/app/features/registry/components/registry-statuses/registry-statuses.component.ts @@ -0,0 +1,61 @@ +import { Store } from '@ngxs/store'; + +import { TranslatePipe, TranslateService } from '@ngx-translate/core'; + +import { Accordion, AccordionContent, AccordionHeader, AccordionPanel } from 'primeng/accordion'; +import { Button } from 'primeng/button'; +import { DialogService } from 'primeng/dynamicdialog'; + +import { ChangeDetectionStrategy, Component, HostBinding, inject, input } from '@angular/core'; + +import { WithdrawDialogComponent } from '@osf/features/registry/components'; +import { RegistryOverview } from '@osf/features/registry/models'; +import { MakePublic } from '@osf/features/registry/store/registry-overview'; +import { RegistryStatus } from '@shared/enums'; +import { CustomConfirmationService } from '@shared/services'; + +@Component({ + selector: 'osf-registry-statuses', + imports: [Accordion, AccordionContent, AccordionHeader, AccordionPanel, TranslatePipe, Button], + templateUrl: './registry-statuses.component.html', + styleUrl: './registry-statuses.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class RegistryStatusesComponent { + @HostBinding('class') classes = 'flex-1 flex'; + private readonly store = inject(Store); + registry = input.required(); + private readonly dialogService = inject(DialogService); + private readonly translateService = inject(TranslateService); + protected readonly RegistryStatus = RegistryStatus; + protected readonly customConfirmationService = inject(CustomConfirmationService); + + openWithdrawDialog(): void { + const registry = this.registry(); + if (registry) { + this.dialogService.open(WithdrawDialogComponent, { + width: '552px', + focusOnShow: false, + header: this.translateService.instant('registry.overview.withdrawRegistration'), + closeOnEscape: true, + modal: true, + closable: true, + data: { + registryId: registry.id, + }, + }); + } + } + + openMakePublicDialog(): void { + const registry = this.registry(); + if (registry) { + this.customConfirmationService.confirmAccept({ + headerKey: 'common.labels.makePublic', + messageKey: 'registry.overview.makePublicMessage', + acceptLabelKey: 'common.labels.makePublic', + onConfirm: () => this.store.dispatch(new MakePublic(registry.id)), + }); + } + } +} diff --git a/src/app/features/registry/components/withdraw-dialog/withdraw-dialog.component.html b/src/app/features/registry/components/withdraw-dialog/withdraw-dialog.component.html new file mode 100644 index 000000000..6d95fc1dd --- /dev/null +++ b/src/app/features/registry/components/withdraw-dialog/withdraw-dialog.component.html @@ -0,0 +1,19 @@ +
    +

    {{ 'registry.overview.withdrawDescription' | translate }}

    + + + + +
    + + +
    +
    diff --git a/src/app/features/registry/components/withdraw-dialog/withdraw-dialog.component.scss b/src/app/features/registry/components/withdraw-dialog/withdraw-dialog.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/features/registry/components/withdraw-dialog/withdraw-dialog.component.spec.ts b/src/app/features/registry/components/withdraw-dialog/withdraw-dialog.component.spec.ts new file mode 100644 index 000000000..f7b939b63 --- /dev/null +++ b/src/app/features/registry/components/withdraw-dialog/withdraw-dialog.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { WithdrawDialogComponent } from './withdraw-dialog.component'; + +describe('WithdrawDialogComponent', () => { + let component: WithdrawDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [WithdrawDialogComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(WithdrawDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/features/registry/components/withdraw-dialog/withdraw-dialog.component.ts b/src/app/features/registry/components/withdraw-dialog/withdraw-dialog.component.ts new file mode 100644 index 000000000..e191aa65c --- /dev/null +++ b/src/app/features/registry/components/withdraw-dialog/withdraw-dialog.component.ts @@ -0,0 +1,44 @@ +import { Store } from '@ngxs/store'; + +import { TranslatePipe } from '@ngx-translate/core'; + +import { Button } from 'primeng/button'; +import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog'; + +import { finalize, take } from 'rxjs'; + +import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; +import { FormControl, FormGroup } from '@angular/forms'; + +import { WithdrawRegistration } from '@osf/features/registry/store/registry-overview'; +import { TextInputComponent } from '@shared/components'; + +@Component({ + selector: 'osf-withdraw-dialog', + imports: [TranslatePipe, TextInputComponent, Button], + templateUrl: './withdraw-dialog.component.html', + styleUrl: './withdraw-dialog.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class WithdrawDialogComponent { + private readonly store = inject(Store); + protected readonly dialogRef = inject(DynamicDialogRef); + private readonly config = inject(DynamicDialogConfig); + + protected readonly form = new FormGroup({ + text: new FormControl(''), + }); + + withdrawRegistration(): void { + const registryId = this.config.data.registryId; + if (registryId) { + this.store + .dispatch(new WithdrawRegistration(registryId, this.form.controls.text.value ?? '')) + .pipe( + take(1), + finalize(() => this.dialogRef.close()) + ) + .subscribe(); + } + } +} diff --git a/src/app/features/registry/mappers/registry-overview.mapper.ts b/src/app/features/registry/mappers/registry-overview.mapper.ts index b557eada7..9e3e4ad89 100644 --- a/src/app/features/registry/mappers/registry-overview.mapper.ts +++ b/src/app/features/registry/mappers/registry-overview.mapper.ts @@ -1,4 +1,5 @@ import { RegistryOverview, RegistryOverviewJsonApiData } from '@osf/features/registry/models'; +import { MapRegistryStatus } from '@shared/mappers/registry/map-registry-status.mapper'; export function MapRegistryOverview(data: RegistryOverviewJsonApiData): RegistryOverview | null { try { @@ -60,6 +61,8 @@ export function MapRegistryOverview(data: RegistryOverviewJsonApiData): Registry revisionResponses: schemaResponse.attributes?.revision_responses, updatedResponseKeys: schemaResponse.attributes?.updated_response_keys, })), + status: MapRegistryStatus(data.attributes), + revisionStatus: data.attributes.revision_state, } as RegistryOverview; } catch (error) { console.error(error); diff --git a/src/app/features/registry/models/get-registry-overview-json-api.model.ts b/src/app/features/registry/models/get-registry-overview-json-api.model.ts index 5e0f250f8..99d9f1952 100644 --- a/src/app/features/registry/models/get-registry-overview-json-api.model.ts +++ b/src/app/features/registry/models/get-registry-overview-json-api.model.ts @@ -1,4 +1,5 @@ import { ApiData, JsonApiResponse } from '@core/models'; +import { RegistrationReviewStates, RevisionReviewStates } from '@shared/enums'; export type GetRegistryOverviewJsonApi = JsonApiResponse; @@ -39,6 +40,13 @@ export interface RegistryOverviewJsonApiAttributes { has_papers: boolean; has_supplements: boolean; registration_responses: RegistrationQuestions; + pending_embargo_approval: boolean; + pending_embargo_termination_approval: boolean; + pending_registration_approval: boolean; + pending_withdrawal: boolean; + revision_state: RevisionReviewStates; + review_state: RegistrationReviewStates; + embargoed: boolean; } export type RegistrationQuestions = Record; diff --git a/src/app/features/registry/models/registry-overview.models.ts b/src/app/features/registry/models/registry-overview.models.ts index de3aafecc..6ff2f5349 100644 --- a/src/app/features/registry/models/registry-overview.models.ts +++ b/src/app/features/registry/models/registry-overview.models.ts @@ -1,5 +1,6 @@ import { ProjectOverviewContributor } from '@osf/features/project/overview/models'; import { RegistrationQuestions, RegistrySubject } from '@osf/features/registry/models'; +import { RegistryStatus, RevisionReviewStates } from '@shared/enums'; export interface RegistryOverview { id: string; @@ -58,4 +59,6 @@ export interface RegistryOverview { revisionResponses: RegistrationQuestions; updatedResponseKeys: string[]; }[]; + status: RegistryStatus; + revisionStatus: RevisionReviewStates; } diff --git a/src/app/features/registry/pages/registry-overview/registry-overview.component.html b/src/app/features/registry/pages/registry-overview/registry-overview.component.html index 7e0593e2f..566b12bd7 100644 --- a/src/app/features/registry/pages/registry-overview/registry-overview.component.html +++ b/src/app/features/registry/pages/registry-overview/registry-overview.component.html @@ -12,67 +12,21 @@ />
-
- +
+
- - - -

{{ 'registry.overview.public' | translate }}

-
- -
-

{{ 'common.labels.public' | translate }}

-

{{ 'registry.overview.statuses.frozen' | translate }}

-
-
-
-
+ (`${schemaLink}schema_blocks`, params) .pipe(map((response) => response.data.map((block) => MapRegistrySchemaBlock(block.attributes)))); } + + withdrawRegistration(registryId: string, justification: string): Observable { + const payload = { + data: { + id: registryId, + attributes: { + withdrawal_justification: justification, + pending_withdrawal: true, + }, + relationships: {}, + type: 'registrations', + }, + }; + + return this.jsonApiService + .patch(`${environment.apiUrl}/registrations/${registryId}`, payload) + .pipe(map((response) => MapRegistryOverview(response))); + } + + makePublic(registryId: string): Observable { + const payload = { + data: { + id: registryId, + attributes: { + public: true, + }, + relationships: {}, + type: 'registrations', + }, + }; + + return this.jsonApiService + .patch(`${environment.apiUrl}/registrations/${registryId}`, payload) + .pipe(map((response) => MapRegistryOverview(response))); + } } diff --git a/src/app/features/registry/store/registry-overview/registry-overview.actions.ts b/src/app/features/registry/store/registry-overview/registry-overview.actions.ts index 26754c848..1fb066991 100644 --- a/src/app/features/registry/store/registry-overview/registry-overview.actions.ts +++ b/src/app/features/registry/store/registry-overview/registry-overview.actions.ts @@ -26,3 +26,18 @@ export class GetSchemaBlocks { public questions: RegistrationQuestions ) {} } + +export class WithdrawRegistration { + static readonly type = '[Registry Overview] Withdraw Registration'; + + constructor( + public registryId: string, + public justification: string + ) {} +} + +export class MakePublic { + static readonly type = '[Registry Overview] Make Public'; + + constructor(public registryId: string) {} +} diff --git a/src/app/features/registry/store/registry-overview/registry-overview.state.ts b/src/app/features/registry/store/registry-overview/registry-overview.state.ts index 3cdfa7563..92678450d 100644 --- a/src/app/features/registry/store/registry-overview/registry-overview.state.ts +++ b/src/app/features/registry/store/registry-overview/registry-overview.state.ts @@ -11,6 +11,8 @@ import { GetRegistryInstitutions, GetRegistrySubjects, GetSchemaBlocks, + MakePublic, + WithdrawRegistration, } from '@osf/features/registry/store/registry-overview'; import { RegistryOverviewStateModel } from './registry-overview.model'; @@ -151,6 +153,64 @@ export class RegistryOverviewState { ); } + @Action(WithdrawRegistration) + withdrawRegistration(ctx: StateContext, action: WithdrawRegistration) { + const state = ctx.getState(); + ctx.patchState({ + registry: { + ...state.registry, + isLoading: true, + }, + }); + + return this.registryOverviewService.withdrawRegistration(action.registryId, action.justification).pipe( + tap({ + next: (registryOverview) => { + ctx.patchState({ + registry: { + data: registryOverview, + isLoading: false, + error: null, + }, + }); + if (registryOverview?.registrationSchemaLink && registryOverview?.questions) { + ctx.dispatch(new GetSchemaBlocks(registryOverview.registrationSchemaLink, registryOverview.questions)); + } + }, + }), + catchError((error) => this.handleError(ctx, 'registry', error)) + ); + } + + @Action(MakePublic) + makePublic(ctx: StateContext, action: MakePublic) { + const state = ctx.getState(); + ctx.patchState({ + registry: { + ...state.registry, + isLoading: true, + }, + }); + + return this.registryOverviewService.makePublic(action.registryId).pipe( + tap({ + next: (registryOverview) => { + ctx.patchState({ + registry: { + data: registryOverview, + isLoading: false, + error: null, + }, + }); + if (registryOverview?.registrationSchemaLink && registryOverview?.questions) { + ctx.dispatch(new GetSchemaBlocks(registryOverview.registrationSchemaLink, registryOverview.questions)); + } + }, + }), + catchError((error) => this.handleError(ctx, 'registry', error)) + ); + } + private handleError( ctx: StateContext, section: 'registry' | 'subjects' | 'institutions' | 'schemaBlocks', diff --git a/src/app/shared/components/data-resources/data-resources.component.html b/src/app/shared/components/data-resources/data-resources.component.html new file mode 100644 index 000000000..08df6fdea --- /dev/null +++ b/src/app/shared/components/data-resources/data-resources.component.html @@ -0,0 +1,42 @@ + diff --git a/src/app/shared/components/data-resources/data-resources.component.scss b/src/app/shared/components/data-resources/data-resources.component.scss new file mode 100644 index 000000000..457e102bf --- /dev/null +++ b/src/app/shared/components/data-resources/data-resources.component.scss @@ -0,0 +1,14 @@ +@use "/assets/styles/variables" as var; +@use "assets/styles/mixins" as mix; + +.icon-container { + color: var.$dark-blue-1; + display: flex; + align-items: center; + column-gap: 0.5rem; + + &:hover { + text-decoration: none; + color: var.$pr-blue-1; + } +} diff --git a/src/app/shared/components/data-resources/data-resources.component.spec.ts b/src/app/shared/components/data-resources/data-resources.component.spec.ts new file mode 100644 index 000000000..199f7f58a --- /dev/null +++ b/src/app/shared/components/data-resources/data-resources.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DataResourcesComponent } from './data-resources.component'; + +describe('DataResourcesComponent', () => { + let component: DataResourcesComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [DataResourcesComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(DataResourcesComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/shared/components/data-resources/data-resources.component.ts b/src/app/shared/components/data-resources/data-resources.component.ts new file mode 100644 index 000000000..8aaf2c13d --- /dev/null +++ b/src/app/shared/components/data-resources/data-resources.component.ts @@ -0,0 +1,20 @@ +import { TranslatePipe } from '@ngx-translate/core'; + +import { ChangeDetectionStrategy, Component, HostBinding, input } from '@angular/core'; + +@Component({ + selector: 'osf-data-resources', + imports: [TranslatePipe], + templateUrl: './data-resources.component.html', + styleUrl: './data-resources.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class DataResourcesComponent { + @HostBinding('class') classes = 'flex-1 flex'; + resourceId = input(); + hasData = input(); + hasAnalyticCode = input(); + hasMaterials = input(); + hasPapers = input(); + hasSupplements = input(); +} diff --git a/src/app/shared/components/index.ts b/src/app/shared/components/index.ts index 80d1049bc..be0ce5306 100644 --- a/src/app/shared/components/index.ts +++ b/src/app/shared/components/index.ts @@ -2,6 +2,7 @@ export { AddProjectFormComponent } from './add-project-form/add-project-form.com export { BarChartComponent } from './bar-chart/bar-chart.component'; export { CopyButtonComponent } from './copy-button/copy-button.component'; export { CustomPaginatorComponent } from './custom-paginator/custom-paginator.component'; +export { DataResourcesComponent } from './data-resources/data-resources.component'; export { EducationHistoryComponent } from './education-history/education-history.component'; export { EducationHistoryDialogComponent } from './education-history-dialog/education-history-dialog.component'; export { EmploymentHistoryComponent } from './employment-history/employment-history.component'; diff --git a/src/app/shared/components/resource-card/resource-card.component.html b/src/app/shared/components/resource-card/resource-card.component.html index b2e26060f..a5685b933 100644 --- a/src/app/shared/components/resource-card/resource-card.component.html +++ b/src/app/shared/components/resource-card/resource-card.component.html @@ -1,7 +1,7 @@
- +
+
+ @if (item().description) {

{{ 'resourceCard.labels.description' | translate }} {{ item().description }}

} diff --git a/src/app/shared/components/resource-card/resource-card.component.scss b/src/app/shared/components/resource-card/resource-card.component.scss index 572744b3b..77961708f 100644 --- a/src/app/shared/components/resource-card/resource-card.component.scss +++ b/src/app/shared/components/resource-card/resource-card.component.scss @@ -84,8 +84,12 @@ .content { display: flex; flex-direction: column; - row-gap: 1.7rem; + gap: 1.7rem; + padding-top: 1.7rem; + } + + .break-line { + border: none; border-top: 1px solid var.$grey-2; - padding-top: 1rem; } } diff --git a/src/app/shared/components/resource-card/resource-card.component.ts b/src/app/shared/components/resource-card/resource-card.component.ts index 8a2c0bc21..e867f5726 100644 --- a/src/app/shared/components/resource-card/resource-card.component.ts +++ b/src/app/shared/components/resource-card/resource-card.component.ts @@ -10,6 +10,7 @@ import { ChangeDetectionStrategy, Component, inject, model } from '@angular/core import { toSignal } from '@angular/core/rxjs-interop'; import { Router } from '@angular/router'; +import { DataResourcesComponent } from '@shared/components/data-resources/data-resources.component'; import { ResourceType } from '@shared/enums'; import { Resource } from '@shared/models'; import { ResourceCardService } from '@shared/services'; @@ -26,6 +27,7 @@ import { IS_XSMALL } from '@shared/utils'; NgOptimizedImage, Skeleton, TranslatePipe, + DataResourcesComponent, ], templateUrl: './resource-card.component.html', styleUrl: './resource-card.component.scss', diff --git a/src/app/shared/enums/index.ts b/src/app/shared/enums/index.ts index 5af34c16c..c2e42104c 100644 --- a/src/app/shared/enums/index.ts +++ b/src/app/shared/enums/index.ts @@ -9,9 +9,12 @@ export * from './file-menu-type.enum'; export * from './filter-type.enum'; export * from './get-resources-request-type.enum'; export * from './profile-addons-stepper.enum'; +export * from './registration-review-states.enum'; +export * from './registry-status.enum'; export * from './resource-tab.enum'; export * from './resource-type.enum'; export * from './reusable-filter-type.enum'; +export * from './revision-review-states.enum'; export * from './share-indexing.enum'; export * from './sort-order.enum'; export * from './sort-type.enum'; diff --git a/src/app/shared/enums/registration-review-states.enum.ts b/src/app/shared/enums/registration-review-states.enum.ts new file mode 100644 index 000000000..9f03aeed5 --- /dev/null +++ b/src/app/shared/enums/registration-review-states.enum.ts @@ -0,0 +1,10 @@ +export enum RegistrationReviewStates { + Initial = 'initial', + Pending = 'pending', + Accepted = 'accepted', + Rejected = 'rejected', + Withdrawn = 'withdrawn', + PendingEmbargoTermination = 'pending_embargo_termination', + PendingWithdrawRequest = 'pending_withdraw_request', + PendingWithdraw = 'pending_withdraw', +} diff --git a/src/app/shared/enums/registry-status.enum.ts b/src/app/shared/enums/registry-status.enum.ts new file mode 100644 index 000000000..96bc210eb --- /dev/null +++ b/src/app/shared/enums/registry-status.enum.ts @@ -0,0 +1,14 @@ +export enum RegistryStatus { + None = 'None', + PendingRegistrationApproval = 'pendingRegistrationApproval', + PendingEmbargoApproval = 'pendingEmbargoApproval', + Pending = 'pending', + Accepted = 'accepted', + Embargo = 'embargo', + PendingEmbargoTerminationApproval = 'pendingEmbargoTermination', + PendingWithdrawRequest = 'pendingWithdrawRequest', + PendingWithdraw = 'pendingWithdraw', + Unapproved = 'unapproved', + InProgress = 'inProgress', + PendingModeration = 'pendingModeration', +} diff --git a/src/app/shared/enums/revision-review-states.enum.ts b/src/app/shared/enums/revision-review-states.enum.ts new file mode 100644 index 000000000..8ede37c87 --- /dev/null +++ b/src/app/shared/enums/revision-review-states.enum.ts @@ -0,0 +1,6 @@ +export enum RevisionReviewStates { + Unapproved = 'unapproved', + RevisionInProgress = 'in_progress', + RevisionPendingModeration = 'pending_moderation', + Approved = 'approved', +} diff --git a/src/app/shared/mappers/index.ts b/src/app/shared/mappers/index.ts index 711de153a..dd0b841b1 100644 --- a/src/app/shared/mappers/index.ts +++ b/src/app/shared/mappers/index.ts @@ -2,6 +2,7 @@ export * from './addon.mapper'; export * from './filters'; export * from './institutions'; export * from './licenses.mapper'; +export * from './registry'; export * from './resource-card'; export * from './resource-overview.mappers'; export * from './subjects'; diff --git a/src/app/shared/mappers/registry/index.ts b/src/app/shared/mappers/registry/index.ts new file mode 100644 index 000000000..b4034dc67 --- /dev/null +++ b/src/app/shared/mappers/registry/index.ts @@ -0,0 +1 @@ +export * from './map-registry-status.mapper'; diff --git a/src/app/shared/mappers/registry/map-registry-status.mapper.ts b/src/app/shared/mappers/registry/map-registry-status.mapper.ts new file mode 100644 index 000000000..d34b52371 --- /dev/null +++ b/src/app/shared/mappers/registry/map-registry-status.mapper.ts @@ -0,0 +1,30 @@ +import { RegistryOverviewJsonApiAttributes } from '@osf/features/registry/models'; +import { RegistrationReviewStates, RegistryStatus, RevisionReviewStates } from '@shared/enums'; + +export function MapRegistryStatus(registry: RegistryOverviewJsonApiAttributes): RegistryStatus { + if (registry.pending_embargo_approval) { + return RegistryStatus.PendingEmbargoApproval; + } else if (registry.pending_embargo_termination_approval) { + return RegistryStatus.PendingEmbargoTerminationApproval; + } else if (registry.embargoed) { + return RegistryStatus.Embargo; + } else if (registry.pending_registration_approval) { + return RegistryStatus.PendingRegistrationApproval; + } else if (registry.revision_state === RevisionReviewStates.Unapproved) { + return RegistryStatus.Unapproved; + } else if (registry.revision_state === RevisionReviewStates.RevisionInProgress) { + return RegistryStatus.InProgress; + } else if (registry.revision_state === RevisionReviewStates.RevisionPendingModeration) { + return RegistryStatus.PendingModeration; + } else if (registry.review_state === RegistrationReviewStates.Accepted) { + return RegistryStatus.Accepted; + } else if (registry.review_state === RegistrationReviewStates.Pending) { + return RegistryStatus.Pending; + } else if (registry.review_state === RegistrationReviewStates.PendingWithdraw) { + return RegistryStatus.PendingWithdraw; + } else if (registry.review_state === RegistrationReviewStates.PendingWithdrawRequest) { + return RegistryStatus.PendingWithdrawRequest; + } else { + return RegistryStatus.None; + } +} diff --git a/src/app/shared/models/confirmation-options.model.ts b/src/app/shared/models/confirmation-options.model.ts index 01b0d28e8..8afbb57e5 100644 --- a/src/app/shared/models/confirmation-options.model.ts +++ b/src/app/shared/models/confirmation-options.model.ts @@ -19,7 +19,7 @@ export interface AcceptConfirmationOptions { messageParams?: any; acceptLabelKey?: string; onConfirm: () => void; - onReject: () => void; + onReject?: () => void; } export interface ContinueConfirmationOptions { diff --git a/src/app/shared/services/custom-confirmation.service.ts b/src/app/shared/services/custom-confirmation.service.ts index 49028e7d5..51451e050 100644 --- a/src/app/shared/services/custom-confirmation.service.ts +++ b/src/app/shared/services/custom-confirmation.service.ts @@ -50,7 +50,11 @@ export class CustomConfirmationService { options.onConfirm(); }, reject: () => { - options.onReject(); + if (options.onReject) { + options.onReject(); + } else { + this.confirmationService.close(); + } }, }); } diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index b2147e1d6..6dab2f2a8 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -28,7 +28,9 @@ "deselect": "Deselect", "yes": "Yes", "no": "No", - "update": "Update" + "update": "Update", + "continue update": "Continue update", + "withdraw": "Withdraw" }, "search": { "title": "Search", @@ -65,7 +67,9 @@ "public": "Public", "title": "Title", "description": "Description", - "year": "Year" + "year": "Year", + "optional": "Optional", + "makePublic": "Make Public" }, "deleteConfirmation": { "header": "Delete", @@ -658,10 +662,12 @@ "confirmation": "Type the following to continue:" }, "fork": { - "header": "Fork This Project", + "headerProject": "Fork This Project", + "headerRegistry": "Fork This Registry", "confirmButton": "Fork", "cancelButton": "Cancel", - "message": "Are you sure you want to fork this project?" + "messageProject": "Are you sure you want to fork this project?", + "messageRegistry": "Are you sure you want to fork this registry?" }, "duplicate": { "header": "Duplicate Template", @@ -1641,10 +1647,67 @@ }, "registry": { "overview": { - "public": "Public Registration", "updates": "Updates", + "withdrawRegistration": "Withdraw registration", + "withdrawDescription": "Withdrawing a registration will remove its content from the OSF, but leave basic metadata behind. The title of a withdrawn registration and its contributor list will remain, as will justification or explanation of the withdrawal, should you wish to provide it. Withdrawn registrations will be marked with a \"withdrawn\" tag. This action is irreversible.", + "withdrawJustificationLabel": "Please provide your justification for withdrawing this registration.", + "makePublicMessage": "Are you sure you want make this registration public ?", "statuses": { - "frozen": "This public registration is a frozen, non-editable version of this" + "pendingRegistrationApproval": { + "text": "Pending approval", + "short": "Pending registration approval", + "long": "This registration is waiting for approval from its contributors." + }, + "pendingEmbargoApproval": { + "text": "Pending approval", + "short": "Pending embargo approval", + "long": "This registration is waiting for approval from its contributors. Once approved, the registration will remain private until the embargo ends." + }, + "pending": { + "text": "Pending moderation", + "short": "Pending moderator approval", + "long": "This registration is awaiting a decision by the registry moderators. An email will notify all registration contributors of the decision." + }, + "accepted": { + "text": "Public registration", + "short": "Public", + "long": "This public registration is a frozen, non-editable version of this." + }, + "embargo": { + "text": "Embargoed registration", + "short": "Embargoed", + "long": "This registration is embargoed. It will remain private until {embargoEndDate}." + }, + "pendingEmbargoTermination": { + "text": "Embargoed registration", + "short": "Pending embargo termination", + "long": "This registration is waiting for approval from its contributors to terminate its embargo and be made public." + }, + "pendingWithdrawRequest": { + "text": "Pending withdrawal", + "short": "Pending withdraw request", + "long": "This registration is waiting for approval to be withdrawn by its contributors." + }, + "pendingWithdraw": { + "text": "Pending withdrawal", + "short": "Pending withdrawal moderation", + "long": "This registration is waiting for approval to be withdrawn by registry moderators." + }, + "unapproved": { + "text": "Update pending", + "short": "Update pending approval", + "long": "This registration has an update that is waiting for approval from its contributors. Updates will be made available once approved." + }, + "inProgress": { + "text": "Update in progress", + "short": "Updates being made", + "long": "This registration is currently being updated by its contributors. Updates will be made available once approved." + }, + "pendingModeration": { + "text": "Update pending review", + "short": "Update pending moderator approval", + "long": "This registration has an update that is waiting for moderation. Updates will be made available once approved." + } }, "metadata": { "type": "Registration Type", From f565f6c740dbccbe2f7968fae44a63d16eeb4fed Mon Sep 17 00:00:00 2001 From: Kyrylo Petrov Date: Mon, 7 Jul 2025 13:42:51 +0300 Subject: [PATCH 3/3] fix(registry-overview): pr fixes --- .../registry-statuses.component.ts | 7 +- .../withdraw-dialog.component.ts | 10 +- .../mappers/registry-overview.mapper.ts | 127 +++++++++--------- .../mappers/registry-schema-block.mapper.ts | 8 +- src/app/features/registry/models/index.ts | 1 + .../models/registry-schema-block.model.ts | 8 -- .../registry-overview.component.html | 1 - .../data-resources.component.scss | 1 - 8 files changed, 79 insertions(+), 84 deletions(-) diff --git a/src/app/features/registry/components/registry-statuses/registry-statuses.component.ts b/src/app/features/registry/components/registry-statuses/registry-statuses.component.ts index f036b8b3d..76a7b76bf 100644 --- a/src/app/features/registry/components/registry-statuses/registry-statuses.component.ts +++ b/src/app/features/registry/components/registry-statuses/registry-statuses.component.ts @@ -1,4 +1,4 @@ -import { Store } from '@ngxs/store'; +import { createDispatchMap, Store } from '@ngxs/store'; import { TranslatePipe, TranslateService } from '@ngx-translate/core'; @@ -29,6 +29,9 @@ export class RegistryStatusesComponent { private readonly translateService = inject(TranslateService); protected readonly RegistryStatus = RegistryStatus; protected readonly customConfirmationService = inject(CustomConfirmationService); + protected readonly actions = createDispatchMap({ + makePublic: MakePublic, + }); openWithdrawDialog(): void { const registry = this.registry(); @@ -54,7 +57,7 @@ export class RegistryStatusesComponent { headerKey: 'common.labels.makePublic', messageKey: 'registry.overview.makePublicMessage', acceptLabelKey: 'common.labels.makePublic', - onConfirm: () => this.store.dispatch(new MakePublic(registry.id)), + onConfirm: () => this.actions.makePublic(registry.id), }); } } diff --git a/src/app/features/registry/components/withdraw-dialog/withdraw-dialog.component.ts b/src/app/features/registry/components/withdraw-dialog/withdraw-dialog.component.ts index e191aa65c..bf0471cc5 100644 --- a/src/app/features/registry/components/withdraw-dialog/withdraw-dialog.component.ts +++ b/src/app/features/registry/components/withdraw-dialog/withdraw-dialog.component.ts @@ -1,4 +1,4 @@ -import { Store } from '@ngxs/store'; +import { createDispatchMap } from '@ngxs/store'; import { TranslatePipe } from '@ngx-translate/core'; @@ -21,9 +21,11 @@ import { TextInputComponent } from '@shared/components'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class WithdrawDialogComponent { - private readonly store = inject(Store); protected readonly dialogRef = inject(DynamicDialogRef); private readonly config = inject(DynamicDialogConfig); + private readonly actions = createDispatchMap({ + withdrawRegistration: WithdrawRegistration, + }); protected readonly form = new FormGroup({ text: new FormControl(''), @@ -32,8 +34,8 @@ export class WithdrawDialogComponent { withdrawRegistration(): void { const registryId = this.config.data.registryId; if (registryId) { - this.store - .dispatch(new WithdrawRegistration(registryId, this.form.controls.text.value ?? '')) + this.actions + .withdrawRegistration(registryId, this.form.controls.text.value ?? '') .pipe( take(1), finalize(() => this.dialogRef.close()) diff --git a/src/app/features/registry/mappers/registry-overview.mapper.ts b/src/app/features/registry/mappers/registry-overview.mapper.ts index 9e3e4ad89..8c841fd64 100644 --- a/src/app/features/registry/mappers/registry-overview.mapper.ts +++ b/src/app/features/registry/mappers/registry-overview.mapper.ts @@ -2,70 +2,65 @@ import { RegistryOverview, RegistryOverviewJsonApiData } from '@osf/features/reg import { MapRegistryStatus } from '@shared/mappers/registry/map-registry-status.mapper'; export function MapRegistryOverview(data: RegistryOverviewJsonApiData): RegistryOverview | null { - try { - return { - id: data.id, - type: data.type, - isPublic: data.attributes.public, - forksCount: data?.relationships?.forks?.links?.related?.meta?.count || 0, - title: data.attributes.title, - description: data.attributes?.description, - dateModified: data.attributes?.date_modified, - dateCreated: data.attributes?.date_created, - dateRegistered: data.attributes?.date_registered, - category: data.attributes?.category, - isFork: data.attributes?.fork, - accessRequestsEnabled: data.attributes?.accessRequestsEnabled, - nodeLicense: data.attributes.node_license - ? { - copyrightHolders: data.attributes.node_license.copyright_holders, - year: data.attributes.node_license.year, - } - : undefined, - license: data.embeds.license?.data?.attributes, - registrationType: data.attributes?.registration_supplement, - doi: data.attributes?.doi, - tags: data.attributes?.tags, - contributors: data.embeds?.bibliographic_contributors?.data.map((contributor) => ({ - id: contributor?.embeds?.users?.data?.id, - familyName: contributor?.embeds?.users?.data?.attributes?.family_name, - fullName: contributor?.embeds?.users?.data?.attributes?.full_name, - givenName: contributor?.embeds?.users?.data?.attributes?.given_name, - middleName: contributor?.embeds?.users?.data?.attributes?.middle_names, - type: contributor?.embeds?.users?.data?.type, - })), - identifiers: data.embeds.identifiers?.data.map((identifier) => ({ - id: identifier.id, - type: identifier.type, - value: identifier.attributes.value, - category: identifier.attributes.category, - })), - analyticsKey: data.attributes?.analyticsKey, - currentUserCanComment: data.attributes.current_user_can_comment, - currentUserPermissions: data.attributes.current_user_permissions, - currentUserIsContributor: data.attributes.current_user_is_contributor, - currentUserIsContributorOrGroupMember: data.attributes.current_user_is_contributor_or_group_member, - citation: data.relationships?.citation?.data?.id, - wikiEnabled: data.attributes.wikiEnabled, - region: data.relationships.region?.data, - hasData: data.attributes.has_data, - hasAnalyticCode: data.attributes.has_analytic_code, - hasMaterials: data.attributes.has_materials, - hasPapers: data.attributes.has_papers, - hasSupplements: data.attributes.has_supplements, - questions: data.attributes.registration_responses, - registrationSchemaLink: data.relationships.registration_schema.links.related.href, - associatedProjectId: data.relationships?.registered_from?.data?.id, - schemaResponses: data.embeds?.schema_responses?.data?.map((schemaResponse) => ({ - id: schemaResponse.id, - revisionResponses: schemaResponse.attributes?.revision_responses, - updatedResponseKeys: schemaResponse.attributes?.updated_response_keys, - })), - status: MapRegistryStatus(data.attributes), - revisionStatus: data.attributes.revision_state, - } as RegistryOverview; - } catch (error) { - console.error(error); - return null; - } + return { + id: data.id, + type: data.type, + isPublic: data.attributes.public, + forksCount: data?.relationships?.forks?.links?.related?.meta?.count || 0, + title: data.attributes.title, + description: data.attributes?.description, + dateModified: data.attributes?.date_modified, + dateCreated: data.attributes?.date_created, + dateRegistered: data.attributes?.date_registered, + category: data.attributes?.category, + isFork: data.attributes?.fork, + accessRequestsEnabled: data.attributes?.accessRequestsEnabled, + nodeLicense: data.attributes.node_license + ? { + copyrightHolders: data.attributes.node_license.copyright_holders, + year: data.attributes.node_license.year, + } + : undefined, + license: data.embeds.license?.data?.attributes, + registrationType: data.attributes?.registration_supplement, + doi: data.attributes?.doi, + tags: data.attributes?.tags, + contributors: data.embeds?.bibliographic_contributors?.data.map((contributor) => ({ + id: contributor?.embeds?.users?.data?.id, + familyName: contributor?.embeds?.users?.data?.attributes?.family_name, + fullName: contributor?.embeds?.users?.data?.attributes?.full_name, + givenName: contributor?.embeds?.users?.data?.attributes?.given_name, + middleName: contributor?.embeds?.users?.data?.attributes?.middle_names, + type: contributor?.embeds?.users?.data?.type, + })), + identifiers: data.embeds.identifiers?.data.map((identifier) => ({ + id: identifier.id, + type: identifier.type, + value: identifier.attributes.value, + category: identifier.attributes.category, + })), + analyticsKey: data.attributes?.analyticsKey, + currentUserCanComment: data.attributes.current_user_can_comment, + currentUserPermissions: data.attributes.current_user_permissions, + currentUserIsContributor: data.attributes.current_user_is_contributor, + currentUserIsContributorOrGroupMember: data.attributes.current_user_is_contributor_or_group_member, + citation: data.relationships?.citation?.data?.id, + wikiEnabled: data.attributes.wikiEnabled, + region: data.relationships.region?.data, + hasData: data.attributes.has_data, + hasAnalyticCode: data.attributes.has_analytic_code, + hasMaterials: data.attributes.has_materials, + hasPapers: data.attributes.has_papers, + hasSupplements: data.attributes.has_supplements, + questions: data.attributes.registration_responses, + registrationSchemaLink: data.relationships.registration_schema.links.related.href, + associatedProjectId: data.relationships?.registered_from?.data?.id, + schemaResponses: data.embeds?.schema_responses?.data?.map((schemaResponse) => ({ + id: schemaResponse.id, + revisionResponses: schemaResponse.attributes?.revision_responses, + updatedResponseKeys: schemaResponse.attributes?.updated_response_keys, + })), + status: MapRegistryStatus(data.attributes), + revisionStatus: data.attributes.revision_state, + } as RegistryOverview; } diff --git a/src/app/features/registry/mappers/registry-schema-block.mapper.ts b/src/app/features/registry/mappers/registry-schema-block.mapper.ts index d745d4cce..8b5325ccc 100644 --- a/src/app/features/registry/mappers/registry-schema-block.mapper.ts +++ b/src/app/features/registry/mappers/registry-schema-block.mapper.ts @@ -1,5 +1,9 @@ -import { RegistrationQuestions, RegistrySchemaBlock, SchemaBlockAttributes } from '@osf/features/registry/models'; -import { ViewSchemaBlock } from '@osf/features/registry/models/view-schema-block.model'; +import { + RegistrationQuestions, + RegistrySchemaBlock, + SchemaBlockAttributes, + ViewSchemaBlock, +} from '@osf/features/registry/models'; export function MapViewSchemaBlock(block: RegistrySchemaBlock, questions: RegistrationQuestions): ViewSchemaBlock { const schemaBlock = { diff --git a/src/app/features/registry/models/index.ts b/src/app/features/registry/models/index.ts index c92010cce..05202a94a 100644 --- a/src/app/features/registry/models/index.ts +++ b/src/app/features/registry/models/index.ts @@ -6,3 +6,4 @@ export * from './registry-institution.model'; export * from './registry-overview.models'; export * from './registry-schema-block.model'; export * from './registry-subject.model'; +export * from './view-schema-block.model'; diff --git a/src/app/features/registry/models/registry-schema-block.model.ts b/src/app/features/registry/models/registry-schema-block.model.ts index 6ef651e1b..433d6c603 100644 --- a/src/app/features/registry/models/registry-schema-block.model.ts +++ b/src/app/features/registry/models/registry-schema-block.model.ts @@ -1,11 +1,3 @@ -// export interface RegistrySchemaBlock { -// value: string; -// values: string[]; -// files?: { id: string; name: string }[]; -// required: boolean; -// html?: string; -// type: string; -// } export interface RegistrySchemaBlock { blockType: string; displayText: string; diff --git a/src/app/features/registry/pages/registry-overview/registry-overview.component.html b/src/app/features/registry/pages/registry-overview/registry-overview.component.html index 566b12bd7..6ff52ce7b 100644 --- a/src/app/features/registry/pages/registry-overview/registry-overview.component.html +++ b/src/app/features/registry/pages/registry-overview/registry-overview.component.html @@ -24,7 +24,6 @@
-