- @if (registry() && registry()!.schemaResponses.length > 0) {
-
- @for (revision of registry()!.schemaResponses.slice(1); track $index; let last = $last) {
- @if (last) {
-
- } @else {
-
- }
+ @if (revisions().length > 0) {
+ @for (revision of revisions(); track $index; let last = $last) {
+
+ }
+ @if (registryAcceptedUnapproved) {
+
}
-
- @if (registry()?.revisionStatus === RevisionReviewStates.Approved) {
+ @if (registryApproved) {
}
- @if (registry()?.revisionStatus === RevisionReviewStates.RevisionInProgress) {
+ @if (registryInProgress) {
}
}
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 d910b98dd..761ca39ff 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
@@ -3,14 +3,16 @@ 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 { ChangeDetectionStrategy, Component, computed, HostBinding, input, output } from '@angular/core';
+import { RouterLink } from '@angular/router';
import { RegistryOverview } from '@osf/features/registry/models';
+import { RegistrationReviewStates } from '@osf/shared/enums';
import { RevisionReviewStates } from '@shared/enums';
@Component({
selector: 'osf-registry-revisions',
- imports: [Accordion, AccordionPanel, AccordionHeader, AccordionContent, Button, TranslatePipe],
+ imports: [Accordion, AccordionPanel, AccordionHeader, AccordionContent, Button, TranslatePipe, RouterLink],
templateUrl: './registry-revisions.component.html',
styleUrl: './registry-revisions.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
@@ -19,9 +21,56 @@ export class RegistryRevisionsComponent {
@HostBinding('class') classes = 'flex-1 flex';
registry = input.required
();
selectedRevisionIndex = input.required();
+ isSubmitting = input(false);
+ isModeration = input(false);
openRevision = output();
readonly updateRegistration = output();
- readonly continueUpdate = output<{ id: string; unapproved: boolean }>();
+ readonly continueUpdate = output();
+
+ unApprovedRevisionId: string | null = null;
+
+ revisions = computed(() => {
+ let schemaResponses = this.registry()?.schemaResponses || [];
+ if (this.registryAcceptedUnapproved) {
+ this.unApprovedRevisionId =
+ schemaResponses.find((response) => response.reviewsState === RevisionReviewStates.Unapproved)?.id || null;
+ }
+ schemaResponses = this.isModeration()
+ ? schemaResponses
+ : schemaResponses.filter((r) => r.reviewsState === RevisionReviewStates.Approved);
+
+ return schemaResponses.map((response, index) => {
+ const onlyOne = schemaResponses.length === 1;
+ const label = onlyOne
+ ? `registry.overview.original`
+ : index === 0
+ ? `registry.overview.latest`
+ : index === schemaResponses.length - 1
+ ? `registry.overview.original`
+ : `registry.overview.update`;
+ return {
+ ...response,
+ index,
+ label,
+ isSelected: index === this.selectedRevisionIndex(),
+ };
+ });
+ });
+
+ get registryInProgress(): boolean {
+ return this.registry()?.revisionStatus === RevisionReviewStates.RevisionInProgress;
+ }
+
+ get registryApproved(): boolean {
+ return this.registry()?.revisionStatus === RevisionReviewStates.Approved;
+ }
+
+ get registryAcceptedUnapproved(): boolean {
+ return (
+ this.registry()?.revisionStatus === RevisionReviewStates.Unapproved &&
+ this.registry()?.reviewsState === RegistrationReviewStates.Accepted
+ );
+ }
emitOpenRevision(index: number) {
this.openRevision.emit(index);
@@ -29,9 +78,6 @@ export class RegistryRevisionsComponent {
protected readonly RevisionReviewStates = RevisionReviewStates;
continueUpdateHandler(): void {
- this.continueUpdate.emit({
- id: this.registry()?.id || '',
- unapproved: this.registry()?.revisionStatus === RevisionReviewStates.Unapproved,
- });
+ this.continueUpdate.emit();
}
}
diff --git a/src/app/features/registry/components/short-registration-info/short-registration-info.component.html b/src/app/features/registry/components/short-registration-info/short-registration-info.component.html
index d32bc07c9..ecf9c9da0 100644
--- a/src/app/features/registry/components/short-registration-info/short-registration-info.component.html
+++ b/src/app/features/registry/components/short-registration-info/short-registration-info.component.html
@@ -1,5 +1,5 @@
-
{{ 'navigation.registration.contributors' | translate }}
+
{{ 'navigation.contributors' | translate }}
@for (c of registration().contributors; track c.id) {
({
- id: schemaResponse.id,
- revisionResponses: schemaResponse.attributes?.revision_responses,
- updatedResponseKeys: schemaResponse.attributes?.updated_response_keys,
- })),
+ schemaResponses: data.embeds?.schema_responses?.data?.map((item) => RegistrationMapper.fromSchemaResponse(item)),
provider: {
id: data.embeds.provider.data.id,
name: data.embeds.provider.data.attributes.name,
diff --git a/src/app/features/registry/mappers/registry-schema-block.mapper.ts b/src/app/features/registry/mappers/registry-schema-block.mapper.ts
deleted file mode 100644
index 8b5325ccc..000000000
--- a/src/app/features/registry/mappers/registry-schema-block.mapper.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-import {
- RegistrationQuestions,
- RegistrySchemaBlock,
- SchemaBlockAttributes,
- ViewSchemaBlock,
-} from '@osf/features/registry/models';
-
-export function MapViewSchemaBlock(block: RegistrySchemaBlock, questions: RegistrationQuestions): ViewSchemaBlock {
- const schemaBlock = {
- required: block.required,
- type: block.blockType,
- value: '',
- values: [],
- files: [],
- } as ViewSchemaBlock;
-
- 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') {
- schemaBlock.values = questionValue as string[];
- } else if (schemaBlock.type === 'file-input') {
- schemaBlock.files = (questionValue as { file_id: string; file_name: string }[]).map((file) => ({
- id: file.file_id,
- name: file.file_name,
- }));
- }
- } else if (questionValue) {
- schemaBlock.value = questionValue as string;
- }
- }
-
- 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 71c5112a7..c12262767 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,5 +1,5 @@
import { ApiData, JsonApiResponse } from '@core/models';
-import { ProviderDataJsonApi } from '@osf/shared/models';
+import { ProviderDataJsonApi, SchemaResponseDataJsonApi } from '@osf/shared/models';
import { RegistrationReviewStates, RevisionReviewStates } from '@shared/enums';
export type GetRegistryOverviewJsonApi = JsonApiResponse;
@@ -99,13 +99,7 @@ export interface RegistryOverviewJsonApiEmbed {
}[];
};
schema_responses: {
- data: {
- id: string;
- attributes: {
- revision_responses: RegistrationQuestions;
- updated_response_keys: string[];
- };
- }[];
+ data: SchemaResponseDataJsonApi[];
};
files: {
data: {
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
deleted file mode 100644
index 2069e5887..000000000
--- a/src/app/features/registry/models/get-registry-schema-block-json-api.model.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import { ApiData, JsonApiResponse } from '@core/models';
-
-export type GetRegistrySchemaBlockJsonApi = JsonApiResponse[], null>;
-
-export interface SchemaBlockAttributes {
- block_type: string;
- display_text: string;
- registration_response_key: string | null;
- required: boolean;
- schema_block_group_key: string;
-}
diff --git a/src/app/features/registry/models/index.ts b/src/app/features/registry/models/index.ts
index e303bc64c..36d0bdff4 100644
--- a/src/app/features/registry/models/index.ts
+++ b/src/app/features/registry/models/index.ts
@@ -1,7 +1,6 @@
export * from './bibliographic-contributors.models';
export * from './get-registry-institutions-json-api.model';
export * from './get-registry-overview-json-api.model';
-export * from './get-registry-schema-block-json-api.model';
export * from './get-resource-subjects-json-api.model';
export * from './linked-nodes.models';
export * from './linked-nodes-json-api.model';
@@ -14,7 +13,6 @@ export * from './registry-institution.model';
export * from './registry-institutions-json-api.model';
export * from './registry-metadata.models';
export * from './registry-overview.models';
-export * from './registry-schema-block.model';
export * from './registry-subject.model';
export * from './resources/add-resource.model';
export * from './resources/add-resource-request.model';
diff --git a/src/app/features/registry/models/registry-overview.models.ts b/src/app/features/registry/models/registry-overview.models.ts
index 28fe721a4..7c9b75413 100644
--- a/src/app/features/registry/models/registry-overview.models.ts
+++ b/src/app/features/registry/models/registry-overview.models.ts
@@ -1,7 +1,7 @@
import { ProjectOverviewContributor } from '@osf/features/project/overview/models';
import { RegistrationQuestions, RegistrySubject } from '@osf/features/registry/models';
import { RegistrationReviewStates, RegistryStatus, RevisionReviewStates } from '@shared/enums';
-import { License, ProviderModel } from '@shared/models';
+import { License, ProviderModel, SchemaResponse } from '@shared/models';
export interface RegistryOverview {
id: string;
@@ -54,11 +54,7 @@ export interface RegistryOverview {
questions: RegistrationQuestions;
registrationSchemaLink: string;
associatedProjectId: string;
- schemaResponses: {
- id: string;
- revisionResponses: RegistrationQuestions;
- updatedResponseKeys: string[];
- }[];
+ schemaResponses: SchemaResponse[];
status: RegistryStatus;
revisionStatus: RevisionReviewStates;
reviewsState?: RegistrationReviewStates;
diff --git a/src/app/features/registry/models/registry-schema-block.model.ts b/src/app/features/registry/models/registry-schema-block.model.ts
deleted file mode 100644
index 433d6c603..000000000
--- a/src/app/features/registry/models/registry-schema-block.model.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-export interface RegistrySchemaBlock {
- blockType: string;
- displayText: string;
- required: boolean;
- schemaBlockGroupKey: string;
- registrationResponseKey: string | null;
-}
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 7935c0f45..d5d4e35bb 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
@@ -27,6 +27,24 @@
} @else {
+ @if (!schemaResponse()?.isOriginalResponse) {
+
+
+
+
+ {{ 'registry.overview.updatedRevisionDescription' | translate }}
+ {{ schemaResponse()?.dateCreated | date }}.
+
+
+ {{ 'registry.overview.updateReason' | translate }}
+
+
+ {{ schemaResponse()?.revisionJustification }}
+
+
+
+
+ }
+
- @for (block of mappedSchemaBlocks(); track $index) {
- @if (block.type === 'file-input' && block.files) {
-
- } @else if (block.type === 'multi-select-input' && block.values) {
-
- @for (value of block.values; track $index) {
- @if (value) {
-
{{ value }}
+ @for (page of schemaBlocks(); track page.id) {
+
{{ page.title }}
+
+ @if (page.description) {
+
{{ page.description }}
+ }
+
+ @if (page.sections?.length) {
+ @for (section of page.sections; track section.id) {
+
+
{{ section.title }}
+ @if (section.description) {
+
{{ section.description }}
+ }
+ @if (section.questions?.length) {
+
}
- }
-
- } @else if (block.type === 'page-heading' && block.value) {
-
{{ block.value }}
- } @else if ((block.type === 'subsection-heading' || block.type === 'question-label') && block.value) {
-
{{ block.value }}
- } @else if (block.value && block.value !== 'select-input-option') {
-
{{ block.value }}
+
+ }
+ } @else {
+ @if (page.questions?.length) {
+
+ }
}
}
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 bc379f6e4..583bfbfd8 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,32 +3,34 @@ import { createDispatchMap, select } from '@ngxs/store';
import { TranslatePipe, TranslateService } from '@ngx-translate/core';
import { DialogService } from 'primeng/dynamicdialog';
+import { Message } from 'primeng/message';
import { filter, map, switchMap, tap } from 'rxjs';
+import { DatePipe } from '@angular/common';
import { ChangeDetectionStrategy, Component, computed, DestroyRef, HostBinding, inject, signal } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
-import { ActivatedRoute, Router, RouterLink } from '@angular/router';
+import { ActivatedRoute, Router } from '@angular/router';
import { OverviewToolbarComponent } from '@osf/features/project/overview/components';
-import { CreateSchemaResponse, FetchAllSchemaResponses, RegistriesSelectors } from '@osf/features/registries/store';
+import { CreateSchemaResponse, FetchAllSchemaResponses } from '@osf/features/registries/store';
import {
DataResourcesComponent,
LoadingSpinnerComponent,
+ RegistrationBlocksDataComponent,
ResourceMetadataComponent,
SubHeaderComponent,
} from '@osf/shared/components';
-import { ResourceType, UserPermissions } from '@osf/shared/enums';
+import { ResourceType, RevisionReviewStates, UserPermissions } from '@osf/shared/enums';
import { MapRegistryOverview } from '@osf/shared/mappers';
-import { ToolbarResource } from '@osf/shared/models';
+import { SchemaResponse, ToolbarResource } from '@osf/shared/models';
import { ToastService } from '@osf/shared/services';
+import { toCamelCase } from '@osf/shared/utils';
import { GetBookmarksCollectionId } from '@shared/stores';
import { ArchivingMessageComponent, RegistryRevisionsComponent, RegistryStatusesComponent } from '../../components';
import { RegistryMakeDecisionComponent } from '../../components/registry-make-decision/registry-make-decision.component';
import { WithdrawnMessageComponent } from '../../components/withdrawn-message/withdrawn-message.component';
-import { MapViewSchemaBlock } from '../../mappers';
-import { RegistrationQuestions } from '../../models';
import {
GetRegistryById,
GetRegistryInstitutions,
@@ -38,13 +40,14 @@ import {
SetRegistryCustomCitation,
} from '../../store/registry-overview';
+import { RegistriesSelectors } from './../../../registries/store/registries.selectors';
+
@Component({
selector: 'osf-registry-overview',
imports: [
SubHeaderComponent,
OverviewToolbarComponent,
LoadingSpinnerComponent,
- RouterLink,
ResourceMetadataComponent,
RegistryRevisionsComponent,
RegistryStatusesComponent,
@@ -52,6 +55,9 @@ import {
ArchivingMessageComponent,
TranslatePipe,
WithdrawnMessageComponent,
+ RegistrationBlocksDataComponent,
+ Message,
+ DatePipe,
],
templateUrl: './registry-overview.component.html',
styleUrl: './registry-overview.component.scss',
@@ -76,7 +82,33 @@ export class RegistryOverviewComponent {
protected readonly schemaBlocks = select(RegistryOverviewSelectors.getSchemaBlocks);
protected readonly isSchemaBlocksLoading = select(RegistryOverviewSelectors.isSchemaBlocksLoading);
protected areReviewActionsLoading = select(RegistryOverviewSelectors.areReviewActionsLoading);
- protected schemaResponse = select(RegistriesSelectors.getSchemaResponse);
+ protected readonly currentRevision = select(RegistriesSelectors.getSchemaResponse);
+ protected readonly isSchemaResponseLoading = select(RegistriesSelectors.getSchemaResponseLoading);
+ protected revisionInProgress: SchemaResponse | undefined;
+
+ protected readonly schemaResponse = computed(() => {
+ const registry = this.registry();
+ const index = this.selectedRevisionIndex();
+ this.revisionInProgress = registry?.schemaResponses.find(
+ (r) => r.reviewsState === RevisionReviewStates.RevisionInProgress
+ );
+ const schemaResponses =
+ (this.isModeration
+ ? registry?.schemaResponses
+ : registry?.schemaResponses.filter((r) => r.reviewsState === RevisionReviewStates.Approved)) || [];
+ if (index !== null) {
+ return schemaResponses[index];
+ }
+ return null;
+ });
+
+ protected readonly updatedFields = computed(() => {
+ const schemaResponse = this.schemaResponse();
+ if (schemaResponse) {
+ return schemaResponse.updatedResponseKeys || [];
+ }
+ return [];
+ });
protected readonly resourceOverview = computed(() => {
const registry = this.registry();
@@ -87,21 +119,6 @@ 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);
@@ -130,8 +147,9 @@ export class RegistryOverviewComponent {
createSchemaResponse: CreateSchemaResponse,
});
- isModeration = this.route.snapshot.queryParamMap.get('mode') === 'moderator';
revisionId: string | null = null;
+ isModeration = false;
+
protected userPermissions = computed(() => {
return this.registry()?.currentUserPermissions || [];
});
@@ -144,28 +162,35 @@ export class RegistryOverviewComponent {
this.route.parent?.params.subscribe((params) => {
const id = params['id'];
if (id) {
- this.actions.getRegistryById(id);
- this.actions.getSubjects(id);
- this.actions.getInstitutions(id);
+ this.actions
+ .getRegistryById(id)
+ .pipe(
+ filter(() => {
+ return !this.registry()?.withdrawn;
+ }),
+ tap(() => {
+ this.actions.getSubjects(id);
+ this.actions.getInstitutions(id);
+ })
+ )
+ .subscribe();
}
});
this.actions.getBookmarksId();
this.route.queryParams
.pipe(
takeUntilDestroyed(),
- map((params) => params['revisionId']),
- filter((revisionId) => revisionId),
- tap((revisionId) => {
+ map((params) => ({ revisionId: params['revisionId'], mode: params['mode'] })),
+ tap(({ revisionId, mode }) => {
this.revisionId = revisionId;
+ this.isModeration = mode === 'moderator';
})
- // [NM] TODO: add logic to handle revisionId
- // switchMap((revisionId) => {
- // })
)
.subscribe();
}
navigateToFile(fileId: string): void {
+ // [NM] TODO: add logic to handle fileId
this.router.navigate(['/files', fileId]);
}
@@ -182,13 +207,18 @@ export class RegistryOverviewComponent {
.createSchemaResponse(id)
.pipe(
tap(() => {
+ this.revisionInProgress = this.currentRevision()!;
this.navigateToJustificationPage();
})
)
.subscribe();
}
- onContinueUpdateRegistration({ id, unapproved }: { id: string; unapproved: boolean }): void {
+ onContinueUpdateRegistration(): void {
+ const { id, unapproved } = {
+ id: this.registry()?.id || '',
+ unapproved: this.revisionInProgress?.reviewsState === RevisionReviewStates.Unapproved,
+ };
this.actions
.getSchemaResponse(id)
.pipe(
@@ -204,12 +234,12 @@ export class RegistryOverviewComponent {
}
private navigateToJustificationPage(): void {
- const revisionId = this.revisionId || this.schemaResponse()?.id;
+ const revisionId = this.revisionId || this.revisionInProgress?.id;
this.router.navigate([`/registries/revisions/${revisionId}/justification`]);
}
private navigateToJustificationReview(): void {
- const revisionId = this.revisionId || this.schemaResponse()?.id;
+ const revisionId = this.revisionId || this.revisionInProgress?.id;
this.router.navigate([`/registries/revisions/${revisionId}/review`]);
}
@@ -238,12 +268,14 @@ export class RegistryOverviewComponent {
.subscribe((data) => {
if (data) {
if (data.action) {
- this.toastService.showSuccess(`moderation.makeDecision.${data.action}Success`);
+ const action = toCamelCase(data.action);
+ this.toastService.showSuccess(`moderation.makeDecision.${action}Success`);
}
const currentUrl = this.router.url.split('?')[0];
this.router.navigateByUrl('/', { skipLocationChange: true }).then(() => {
this.router.navigateByUrl(currentUrl);
});
+ this.actions.getRegistryById(this.registry()?.id || '');
}
});
}
diff --git a/src/app/features/registry/services/registry-overview.service.ts b/src/app/features/registry/services/registry-overview.service.ts
index 2ffc383d1..e716137ef 100644
--- a/src/app/features/registry/services/registry-overview.service.ts
+++ b/src/app/features/registry/services/registry-overview.service.ts
@@ -5,19 +5,19 @@ import { inject, Injectable } from '@angular/core';
import { JsonApiService } from '@core/services';
import { RegistryModerationMapper } from '@osf/features/moderation/mappers';
import { ReviewAction, ReviewActionsResponseJsonApi } from '@osf/features/moderation/models';
-import { MapRegistryOverview, MapRegistrySchemaBlock } from '@osf/features/registry/mappers';
+import { MapRegistryOverview } from '@osf/features/registry/mappers';
import {
GetRegistryInstitutionsJsonApi,
GetRegistryOverviewJsonApi,
- GetRegistrySchemaBlockJsonApi,
GetResourceSubjectsJsonApi,
RegistryInstitution,
RegistryOverview,
RegistryOverviewJsonApiData,
- RegistrySchemaBlock,
RegistrySubject,
} from '@osf/features/registry/models';
import { ReviewActionsMapper } from '@osf/shared/mappers';
+import { PageSchemaMapper } from '@osf/shared/mappers/registration';
+import { PageSchema, SchemaBlocksResponseJsonApi } from '@osf/shared/models';
import { ReviewActionPayload } from '@osf/shared/models/review-action';
import { environment } from 'src/environments/environment';
@@ -77,15 +77,15 @@ export class RegistryOverviewService {
);
}
- getSchemaBlocks(schemaLink: string): Observable {
+ getSchemaBlocks(schemaLink: string): Observable {
const params = {
- 'page[size]': 100,
+ 'page[size]': 200,
page: 1,
};
return this.jsonApiService
- .get(`${schemaLink}schema_blocks`, params)
- .pipe(map((response) => response.data.map((block) => MapRegistrySchemaBlock(block.attributes))));
+ .get(`${schemaLink}schema_blocks`, params)
+ .pipe(map((response) => PageSchemaMapper.fromSchemaBlocksResponse(response)));
}
withdrawRegistration(registryId: string, justification: string): Observable {
diff --git a/src/app/features/registry/store/registry-overview/registry-overview.model.ts b/src/app/features/registry/store/registry-overview/registry-overview.model.ts
index b5c561b0f..6d25c5715 100644
--- a/src/app/features/registry/store/registry-overview/registry-overview.model.ts
+++ b/src/app/features/registry/store/registry-overview/registry-overview.model.ts
@@ -1,17 +1,13 @@
import { ReviewAction } from '@osf/features/moderation/models';
-import {
- RegistryInstitution,
- RegistryOverview,
- RegistrySchemaBlock,
- RegistrySubject,
-} from '@osf/features/registry/models';
+import { RegistryInstitution, RegistryOverview, RegistrySubject } from '@osf/features/registry/models';
+import { PageSchema } from '@osf/shared/models';
import { AsyncStateModel } from '@shared/models';
export interface RegistryOverviewStateModel {
registry: AsyncStateModel;
subjects: AsyncStateModel;
institutions: AsyncStateModel;
- schemaBlocks: AsyncStateModel;
+ schemaBlocks: AsyncStateModel;
moderationActions: AsyncStateModel;
}
diff --git a/src/app/features/registry/store/registry-overview/registry-overview.selectors.ts b/src/app/features/registry/store/registry-overview/registry-overview.selectors.ts
index a211eda43..3b3913f95 100644
--- a/src/app/features/registry/store/registry-overview/registry-overview.selectors.ts
+++ b/src/app/features/registry/store/registry-overview/registry-overview.selectors.ts
@@ -1,12 +1,8 @@
import { Selector } from '@ngxs/store';
import { ReviewAction } from '@osf/features/moderation/models';
-import {
- RegistryInstitution,
- RegistryOverview,
- RegistrySchemaBlock,
- RegistrySubject,
-} from '@osf/features/registry/models';
+import { RegistryInstitution, RegistryOverview, RegistrySubject } from '@osf/features/registry/models';
+import { PageSchema } from '@osf/shared/models';
import { RegistryOverviewStateModel } from './registry-overview.model';
import { RegistryOverviewState } from './registry-overview.state';
@@ -43,7 +39,7 @@ export class RegistryOverviewSelectors {
}
@Selector([RegistryOverviewState])
- static getSchemaBlocks(state: RegistryOverviewStateModel): RegistrySchemaBlock[] | null {
+ static getSchemaBlocks(state: RegistryOverviewStateModel): PageSchema[] | null {
return state.schemaBlocks.data;
}
diff --git a/src/app/shared/components/index.ts b/src/app/shared/components/index.ts
index ec2cfddab..a1c1589d2 100644
--- a/src/app/shared/components/index.ts
+++ b/src/app/shared/components/index.ts
@@ -25,6 +25,7 @@ export { MyProjectsTableComponent } from './my-projects-table/my-projects-table.
export { PasswordInputHintComponent } from './password-input-hint/password-input-hint.component';
export { PieChartComponent } from './pie-chart/pie-chart.component';
export { ReadonlyInputComponent } from './readonly-input/readonly-input.component';
+export { RegistrationBlocksDataComponent } from './registration-blocks-data/registration-blocks-data.component';
export { ResourceCardComponent } from './resource-card/resource-card.component';
export { ResourceMetadataComponent } from './resource-metadata/resource-metadata.component';
export { ReusableFilterComponent } from './reusable-filter/reusable-filter.component';
diff --git a/src/app/features/registries/components/review-data/review-data.component.html b/src/app/shared/components/registration-blocks-data/registration-blocks-data.component.html
similarity index 81%
rename from src/app/features/registries/components/review-data/review-data.component.html
rename to src/app/shared/components/registration-blocks-data/registration-blocks-data.component.html
index 57e2f00b8..d4392c874 100644
--- a/src/app/features/registries/components/review-data/review-data.component.html
+++ b/src/app/shared/components/registration-blocks-data/registration-blocks-data.component.html
@@ -1,6 +1,13 @@
@for (question of questions(); track question.responseKey) {
-
{{ question.displayText }}
+
+ {{ question.displayText }}
+
+ @if (!isOriginalRevision() && updatedKeysMap()[question.responseKey!]) {
+
+ }
+
+
@if (reviewData()[question.responseKey!]) {
@switch (question.fieldType) {
@case (FieldType.Text) {
@@ -34,7 +41,7 @@
{{ question.displayText }}
} @else {
{{ 'common.labels.noData' | translate }}
}
- @if (question.required) {
+ @if (question.required && !isOverviewPage()) {
{{ INPUT_VALIDATION_MESSAGES.required | translate }}
diff --git a/src/app/shared/components/registration-blocks-data/registration-blocks-data.component.scss b/src/app/shared/components/registration-blocks-data/registration-blocks-data.component.scss
new file mode 100644
index 000000000..5d4e87f30
--- /dev/null
+++ b/src/app/shared/components/registration-blocks-data/registration-blocks-data.component.scss
@@ -0,0 +1,3 @@
+:host {
+ display: block;
+}
diff --git a/src/app/shared/components/registration-blocks-data/registration-blocks-data.component.spec.ts b/src/app/shared/components/registration-blocks-data/registration-blocks-data.component.spec.ts
new file mode 100644
index 000000000..e29f7c476
--- /dev/null
+++ b/src/app/shared/components/registration-blocks-data/registration-blocks-data.component.spec.ts
@@ -0,0 +1,22 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { RegistrationBlocksDataComponent } from './registration-blocks-data.component';
+
+describe.skip('RegistrationBlocksDataComponent', () => {
+ let component: RegistrationBlocksDataComponent;
+ let fixture: ComponentFixture
;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [RegistrationBlocksDataComponent],
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(RegistrationBlocksDataComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/shared/components/registration-blocks-data/registration-blocks-data.component.ts b/src/app/shared/components/registration-blocks-data/registration-blocks-data.component.ts
new file mode 100644
index 000000000..846aa407e
--- /dev/null
+++ b/src/app/shared/components/registration-blocks-data/registration-blocks-data.component.ts
@@ -0,0 +1,35 @@
+import { TranslatePipe } from '@ngx-translate/core';
+
+import { Message } from 'primeng/message';
+import { Tag } from 'primeng/tag';
+
+import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core';
+
+import { INPUT_VALIDATION_MESSAGES } from '@osf/shared/constants';
+import { FieldType } from '@osf/shared/enums';
+import { Question } from '@osf/shared/models';
+
+@Component({
+ selector: 'osf-registration-blocks-data',
+ imports: [Tag, TranslatePipe, Message],
+ templateUrl: './registration-blocks-data.component.html',
+ styleUrl: './registration-blocks-data.component.scss',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class RegistrationBlocksDataComponent {
+ questions = input();
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ reviewData = input>({});
+ isOverviewPage = input(false);
+ updatedFields = input([]);
+ isOriginalRevision = input(true);
+
+ updatedKeysMap = computed>(() => {
+ return this.updatedFields().reduce((acc, key) => {
+ return { ...acc, [key]: true };
+ }, {});
+ });
+
+ protected readonly FieldType = FieldType;
+ protected readonly INPUT_VALIDATION_MESSAGES = INPUT_VALIDATION_MESSAGES;
+}
diff --git a/src/app/shared/components/stepper/stepper.component.scss b/src/app/shared/components/stepper/stepper.component.scss
index 4c6a07f5e..989771ce0 100644
--- a/src/app/shared/components/stepper/stepper.component.scss
+++ b/src/app/shared/components/stepper/stepper.component.scss
@@ -1,4 +1,8 @@
@use "assets/styles/mixins" as mix;
+:host {
+ max-width: 100%;
+ overflow: auto;
+}
.stepper-container {
display: flex;
diff --git a/src/app/features/registries/enums/block-type.enum.ts b/src/app/shared/enums/block-type.enum.ts
similarity index 100%
rename from src/app/features/registries/enums/block-type.enum.ts
rename to src/app/shared/enums/block-type.enum.ts
diff --git a/src/app/features/registries/enums/field-type.enum.ts b/src/app/shared/enums/field-type.enum.ts
similarity index 100%
rename from src/app/features/registries/enums/field-type.enum.ts
rename to src/app/shared/enums/field-type.enum.ts
diff --git a/src/app/shared/enums/index.ts b/src/app/shared/enums/index.ts
index 9d312c2b0..0735536bc 100644
--- a/src/app/shared/enums/index.ts
+++ b/src/app/shared/enums/index.ts
@@ -2,11 +2,13 @@ export * from './addon-form-controls.enum';
export * from './addon-tab.enum';
export * from './addons-category.enum';
export * from './addons-credentials-format.enum';
+export * from './block-type.enum';
export * from './breakpoint-queries.enum';
export * from './citation-types.enum';
export * from './contributors';
export * from './create-component-form-controls.enum';
export * from './create-project-form-controls.enum';
+export * from './field-type.enum';
export * from './file-menu-type.enum';
export * from './filter-type.enum';
export * from './get-resources-request-type.enum';
diff --git a/src/app/shared/enums/trigger-action.enum.ts b/src/app/shared/enums/trigger-action.enum.ts
index 513bc9f4a..2740af5ca 100644
--- a/src/app/shared/enums/trigger-action.enum.ts
+++ b/src/app/shared/enums/trigger-action.enum.ts
@@ -1,5 +1,18 @@
-export enum TriggerAction {
- Accept = 'accept',
- Reject = 'reject',
+export enum ReviewActionTrigger {
+ Submit = 'submit',
+ AcceptSubmission = 'accept_submission',
+ RejectSubmission = 'reject_submission',
ForceWithdraw = 'force_withdraw',
+ RequestWithdrawal = 'request_withdrawal',
+ AcceptWithdrawal = 'accept_withdrawal',
+ RejectWithdrawal = 'reject_withdrawal',
+ RequestEmbargoTermination = 'request_embargo_termination',
+}
+
+export enum SchemaResponseActionTrigger {
+ SubmitRevision = 'submit',
+ AdminApproveRevision = 'approve',
+ AdminRejectRevision = 'admin_reject',
+ AcceptRevision = 'accept',
+ RejectRevision = 'moderator_reject',
}
diff --git a/src/app/shared/mappers/registration/index.ts b/src/app/shared/mappers/registration/index.ts
index 8f0e07dd0..a6edc914e 100644
--- a/src/app/shared/mappers/registration/index.ts
+++ b/src/app/shared/mappers/registration/index.ts
@@ -1 +1,2 @@
+export * from './page-schema.mapper';
export * from './registration.mapper';
diff --git a/src/app/features/registries/mappers/page-schema.mapper.ts b/src/app/shared/mappers/registration/page-schema.mapper.ts
similarity index 95%
rename from src/app/features/registries/mappers/page-schema.mapper.ts
rename to src/app/shared/mappers/registration/page-schema.mapper.ts
index 08fa5f8a5..140ae9641 100644
--- a/src/app/features/registries/mappers/page-schema.mapper.ts
+++ b/src/app/shared/mappers/registration/page-schema.mapper.ts
@@ -1,6 +1,6 @@
-import { BlockType, FieldType } from '../enums';
-import { PageSchema, Question, Section } from '../models';
-import { SchemaBlocksResponseJsonApi } from '../models/schema-blocks-json-api.model';
+import { BlockType } from '@osf/shared/enums/block-type.enum';
+import { FieldType } from '@osf/shared/enums/field-type.enum';
+import { PageSchema, Question, SchemaBlocksResponseJsonApi, Section } from '@osf/shared/models';
export class PageSchemaMapper {
static fromSchemaBlocksResponse(response: SchemaBlocksResponseJsonApi): PageSchema[] {
diff --git a/src/app/shared/models/registration/index.ts b/src/app/shared/models/registration/index.ts
index 9f01d7695..cdbda99e1 100644
--- a/src/app/shared/models/registration/index.ts
+++ b/src/app/shared/models/registration/index.ts
@@ -1,5 +1,7 @@
export * from './draft-registration.model';
+export * from './page-schema.model';
export * from './registration.model';
export * from './registration-card.model';
export * from './registration-json-api.model';
+export * from './schema-blocks-json-api.model';
export * from './schema-response.model';
diff --git a/src/app/features/registries/models/page-schema.model.ts b/src/app/shared/models/registration/page-schema.model.ts
similarity index 90%
rename from src/app/features/registries/models/page-schema.model.ts
rename to src/app/shared/models/registration/page-schema.model.ts
index 84ad1f13d..95ee897f1 100644
--- a/src/app/features/registries/models/page-schema.model.ts
+++ b/src/app/shared/models/registration/page-schema.model.ts
@@ -1,4 +1,4 @@
-import { FieldType } from '../enums';
+import { FieldType } from '@osf/shared/enums/field-type.enum';
export interface PageSchema {
id: string;
diff --git a/src/app/features/registries/models/schema-blocks-json-api.model.ts b/src/app/shared/models/registration/schema-blocks-json-api.model.ts
similarity index 89%
rename from src/app/features/registries/models/schema-blocks-json-api.model.ts
rename to src/app/shared/models/registration/schema-blocks-json-api.model.ts
index 796eab5d1..c3bf9127d 100644
--- a/src/app/features/registries/models/schema-blocks-json-api.model.ts
+++ b/src/app/shared/models/registration/schema-blocks-json-api.model.ts
@@ -1,6 +1,5 @@
import { ApiData, MetaJsonApi, PaginationLinksJsonApi } from '@osf/core/models';
-
-import { BlockType } from '../enums';
+import { BlockType } from '@osf/shared/enums/block-type.enum';
export interface SchemaBlocksResponseJsonApi {
data: SchemaBlockJsonApi[];
diff --git a/src/app/shared/models/review-action/review-action-payload-json-api.model.ts b/src/app/shared/models/review-action/review-action-payload-json-api.model.ts
index 61737d68e..308b3d7f0 100644
--- a/src/app/shared/models/review-action/review-action-payload-json-api.model.ts
+++ b/src/app/shared/models/review-action/review-action-payload-json-api.model.ts
@@ -1,10 +1,10 @@
-import { TriggerAction } from '@osf/shared/enums/trigger-action.enum';
+import { ReviewActionTrigger } from '@osf/shared/enums';
export interface ReviewActionPayloadJsonApi {
data: {
type: ActionType;
attributes: {
- trigger: TriggerAction | string;
+ trigger: ReviewActionTrigger | string;
comment: string;
};
relationships: {
diff --git a/src/app/shared/models/review-action/review-action-payload.model.ts b/src/app/shared/models/review-action/review-action-payload.model.ts
index a7d43e0bf..54360e253 100644
--- a/src/app/shared/models/review-action/review-action-payload.model.ts
+++ b/src/app/shared/models/review-action/review-action-payload.model.ts
@@ -1,7 +1,7 @@
-import { TriggerAction } from '@osf/shared/enums/trigger-action.enum';
+import { ReviewActionTrigger } from '@osf/shared/enums';
export interface ReviewActionPayload {
targetId: string;
- action: TriggerAction | string;
+ action: ReviewActionTrigger | string;
comment: string;
}
diff --git a/src/app/shared/utils/camel-case.ts b/src/app/shared/utils/camel-case.ts
new file mode 100644
index 000000000..ab4043815
--- /dev/null
+++ b/src/app/shared/utils/camel-case.ts
@@ -0,0 +1,11 @@
+export function toCamelCase(input: string): string {
+ if (!input) return '';
+
+ return input
+ .split('_')
+ .filter(Boolean)
+ .map((word, index) =>
+ index === 0 ? word.toLowerCase() : word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()
+ )
+ .join('');
+}
diff --git a/src/app/shared/utils/index.ts b/src/app/shared/utils/index.ts
index 907dfe655..eb6fdbb28 100644
--- a/src/app/shared/utils/index.ts
+++ b/src/app/shared/utils/index.ts
@@ -3,6 +3,7 @@ export * from './add-filters-params.helper';
export * from './addon-type.helper';
export * from './breakpoints.tokens';
export { BrowserTabHelper } from './browser-tab.helper';
+export * from './camel-case';
export * from './convert-to-snake-case.helper';
export * from './custom-form-validators.helper';
export * from './default-confirmation-config.helper';
diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json
index 1c239c028..cbbf2aefa 100644
--- a/src/assets/i18n/en.json
+++ b/src/assets/i18n/en.json
@@ -100,6 +100,7 @@
"and": "and",
"more": "more",
"data": "Data",
+ "updated": "Updated",
"dateUpdated": "Date Updated",
"dateCreated": "Date Created"
},
@@ -1195,6 +1196,7 @@
"rejectSuccess": "Submission has been rejected successfully",
"acceptSubmissionSuccess": "Submission has been accepted successfully",
"rejectSubmissionSuccess": "Submission has been rejected successfully",
+ "forceWithdrawSuccess": "Submission has been force withdrawn successfully",
"removeSuccess": "Submission has been withdrawn successfully",
"justificationPlaceholder": "Provide justification for withdrawal"
},
@@ -2154,7 +2156,8 @@
"header": "Are you sure this needs more edits?",
"message": "This will cancel any approvals from other admin contributors and return the registration back to its draft form for additional changes.",
"label": "What additional changes need to be made and why?"
- }
+ },
+ "caution": "Caution: Only one person is able to edit a registration draft at a time. Be sure to coordinate with any other contributors."
}
},
"registry": {
@@ -2228,7 +2231,11 @@
"associatedProject": "Associated project"
},
"original": "Original",
- "latest": "Latest"
+ "latest": "Latest",
+ "update": "Update {{version}}",
+ "reviewUpdate": "Review Update",
+ "updateReason": "Reason for update:",
+ "updatedRevisionDescription": "This is an update to the original registration. This update was made on "
},
"links": {
"viewLinksProjectsAndComponents": "View for Linked projects and Components",