diff --git a/CHANGELOG.md b/CHANGELOG.md index 6855d87..d29fa19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). -## [1.1.0 - 2026-05-06] +## [1.1.0 - 2026-05-07] ### Added - Dockerfile - Single Spa @@ -145,6 +145,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Filter boolean form fields for creation rules - Cohort definition in a query - Query Details +- Rename overview headers ### Removed - Application Form diff --git a/src/components/BridgeheadOverview.vue b/src/components/BridgeheadOverview.vue index 0f4e874..0cb3baf 100644 --- a/src/components/BridgeheadOverview.vue +++ b/src/components/BridgeheadOverview.vue @@ -1,6 +1,7 @@ -
+
-
@@ -236,7 +252,8 @@ @click="draftDialogStepper.setCurrentStep(step.id)"> {{ index + 1 }}
-
+
-
{{ draftDialogStepper.currentStep?.displayName }}
-
{{ extendedExplanations.get("2")?.message }}
+
{{ + draftDialogStepper.currentStep?.displayName + }} +
+
{{ + extendedExplanations.get("2")?.message + }} +
-
@@ -276,10 +300,16 @@ v-if="!existsDraftDialog && (index === 0 || projectFields[index - 1]?.category !== projectField.category) && projectField.visibilityCondition && projectField.fieldKey !== 'DescriptionUpload'" class="project-field-header project-field-category-header" > -
{{ getDialogStep(projectField.category)?.displayName }}
-
{{ getDialogStep(projectField.category)?.description }}
+
+ {{ getDialogStep(projectField.category)?.displayName }} +
+
+ {{ getDialogStep(projectField.category)?.description }} +
-
+
+ :context="context" + :project-manager-backend-service="projectManagerBackendService"/>
@@ -321,7 +352,8 @@
@@ -339,34 +371,41 @@
- +
-
+
Publications
+ :bridgeheads="visibleBridgeheads" icon-class="bi bi-download" + text="Publications: "/>
+ :module="Module.PROJECT_DOCUMENTS_MODULE" + :action="Action.UPLOAD_PUBLICATION_ACTION" + text="Upload publication" :call-refresh-context="refreshContext" + :is-file="true"/>
+ :module="Module.PROJECT_DOCUMENTS_MODULE" + :action="Action.ADD_PUBLICATION_URL_ACTION" + text="Upload publication URL" :call-refresh-context="refreshContext" + :is-file="false"/>
@@ -377,19 +416,27 @@
- + - +

- + :bridgeheads="visibleBridgeheads" icon-class="bi bi-download" + text="Other documents: "/>
@@ -397,38 +444,47 @@
-
-
TODO +
TODO
+ class="notification-tab" :class="{ 'active': showNotification }" + @click="toggleNotification"> Notifications
-
-
-
@@ -443,7 +499,8 @@ class="notification-box">
-
No action is required at the moment. Please wait for the next notification, which +
No action is required at the moment. Please wait for the next + notification, which will also be sent to you via email.
@@ -472,7 +529,7 @@ import { FormTemplate, FormTitle, getAllProjectTypes, - getQueryState, + getMergedQueryStates, hasProjectType, hasValidOutputs, isQueryOnTheWay, @@ -500,7 +557,12 @@ import UserInput from "@/components/UserInput.vue"; import UploadButton from "@/components/UploadButton.vue"; import DocumentsTable from "@/components/DocumentsTable.vue"; import BridgeheadOverview from "@/components/BridgeheadOverview.vue"; -import {DialogStep, DialogStepper, FixedDialogStep, FixedDialogSteps} from "@/services/fixedDialogStep"; +import { + DialogStep, + DialogStepper, + FixedDialogStep, + FixedDialogSteps +} from "@/services/fixedDialogStep"; import ResultsBox from "@/components/ResultsBox.vue"; import '@/assets/styles/state-circle.css' import UserAndEmail from "@/components/UserAndEmail.vue"; @@ -509,10 +571,14 @@ import {AuthService} from "@/services/auth"; import {ActionFunction, ProjectField, Section} from "@/services/utils"; import DownloadFormTemplatePdfButtons from "@/components/DownloadFormTemplatePdfButtons.vue"; import {PollingService} from "@/services/PollingService"; +import {BridgeheadOverviewHeader} from "@/services/BridgeheadOverviewHeaders"; export default defineComponent({ computed: { + BridgeheadOverviewHeader() { + return BridgeheadOverviewHeader + }, ProjectState() { return ProjectState }, @@ -578,6 +644,7 @@ export default defineComponent({ showExplanations: true, showRightPanel: false, existsVotum: false, + mergedQueryStates: [] as { state: string; types: ProjectType[] }[], existsProjectDescription: false, existsVotumForAllBridgeheads: false, existsAuthenticationScript: false, @@ -616,7 +683,7 @@ export default defineComponent({ groupedMissingFields: {} as Record, currentMenuStep: "Status", editMode: false, - categoryRank: {project:0, query:1, services:2} as Record // order the categories how they should appear in the Request tab (except in DRAFT). not listed categories will be shown after these + categoryRank: {project: 0, query: 1, services: 2} as Record // order the categories how they should appear in the Request tab (except in DRAFT). not listed categories will be shown after these }; }, watch: { @@ -627,7 +694,11 @@ export default defineComponent({ }, context(newValue, _oldValue) { this.projectManagerBackendService = new ProjectManagerBackendService(newValue, Site.PROJECT_VIEW_SITE); - this.fetchProject(); + this.fetchProject().then(() => { + if (this.activeBridgehead){ + this.mergedQueryStates = this.getMergedQueryStates(this.activeBridgehead, getAllProjectTypes(this.project)); + } + }) }, async project() { await this.initializeProjectRelatedData(); @@ -670,8 +741,7 @@ export default defineComponent({ methods: { hasProjectType, - getQueryState, - getAllProjectTypes, + getMergedQueryStates, toggleNotification() { this.showNotification = !this.showNotification; @@ -723,7 +793,7 @@ export default defineComponent({ // TODO: Control page size params.set('page', '' + 0); params.set('page-size', '' + 10); - await this.initializeData(Module.PROJECT_BRIDGEHEAD_MODULE, Action.FETCH_PROJECT_ACTION, params, 'project'); + return await this.initializeData(Module.PROJECT_BRIDGEHEAD_MODULE, Action.FETCH_PROJECT_ACTION, params, 'project'); }, fetchIfProjectHasAllMandatoryFields(): boolean { @@ -738,8 +808,8 @@ export default defineComponent({ // We assume that a boolean mandatory field not set is equal to false — so we ignore it. const mandatoryFormFieldsValid = this.formFields - ?.filter(field => field.type != FormDataType.BOOLEAN && field.mandatory && this.selectedForms.some(ft => ft.title === field.title)) - .every(field => field.value != null && field.value !== ''); + ?.filter(field => field.type != FormDataType.BOOLEAN && field.mandatory && this.selectedForms.some(ft => ft.title === field.title)) + .every(field => field.value != null && field.value !== ''); return baseFieldsValid && mandatoryFormFieldsValid; }, @@ -771,15 +841,15 @@ export default defineComponent({ // 👇 group missing mandatory form fields // We assume that a boolean mandatory field not set is equal to false — so we ignore it. this.groupedMissingFields = this.formFields - ?.filter(field => field.type != FormDataType.BOOLEAN && field.mandatory && !field.value && this.selectedForms.some(ft => ft.title === field.title)) - .reduce((acc, field) => { - const title = field.titleDisplayName ?? field.title; - const label = field.labelDisplayName ?? field.label; + ?.filter(field => field.type != FormDataType.BOOLEAN && field.mandatory && !field.value && this.selectedForms.some(ft => ft.title === field.title)) + .reduce((acc, field) => { + const title = field.titleDisplayName ?? field.title; + const label = field.labelDisplayName ?? field.label; - if (!acc[title]) acc[title] = []; - acc[title].push(label); - return acc; - }, {} as Record); + if (!acc[title]) acc[title] = []; + acc[title].push(label); + return acc; + }, {} as Record); // Create blocks for each title const blocks = Object.entries(this.groupedMissingFields ?? {}).map( @@ -835,7 +905,6 @@ export default defineComponent({ this.projectDescription = {} as ProjectDocument } }), - , this.initializeDataInCallback(Module.PROJECT_DOCUMENTS_MODULE, Action.EXISTS_VOTUM_ACTION, new Map(), async (result: boolean) => { this.existsVotum = result; if (this.existsVotum) { @@ -1017,11 +1086,11 @@ export default defineComponent({ resolve(); } ) - .then(() => { - // IMPORTANT: this runs even if condition === false - resolve(); - }) - .catch(reject); + .then(() => { + // IMPORTANT: this runs even if condition === false + resolve(); + }) + .catch(reject); }); }, @@ -1229,8 +1298,8 @@ export default defineComponent({ } if (this.projectRoles?.includes(ProjectRole.BRIDGEHEAD_ADMIN) && this.activeBridgehead?.executions) { const pendingTypes = this.activeBridgehead.executions - .filter(exec => ![QueryState.CREATED, QueryState.FINISHED, QueryState.ERROR].includes(exec.queryState)) - .map(exec => exec.projectType); + .filter(exec => ![QueryState.CREATED, QueryState.FINISHED, QueryState.ERROR].includes(exec.queryState)) + .map(exec => exec.projectType); if (pendingTypes.length) { extendedExplanations.set(count.toString(), { @@ -1307,7 +1376,9 @@ export default defineComponent({ isEditable: this.isNotIncludedInCurrentProjectConfiguration(exec.projectType + '.projectType'), editMode: this.editMode, possibleValues: this.projectTypes, - displayPossibleValue: (label: string) => {return {name: label, description: ""}}, + displayPossibleValue: (label: string) => { + return {name: label, description: ""} + }, mandatory: true, category: FixedDialogStep.CUSTOM, visibilityCondition: @@ -1325,7 +1396,9 @@ export default defineComponent({ isEditable: this.isNotIncludedInCurrentProjectConfiguration(exec.projectType + '.outputFormat'), editMode: this.editMode, possibleValues: this.outputFormats[exec.projectType] ?? [], - displayPossibleValue: (label: string) => {return {name: label, description: ""}}, + displayPossibleValue: (label: string) => { + return {name: label, description: ""} + }, mandatory: true, category: FixedDialogStep.CUSTOM, visibilityCondition: @@ -1341,7 +1414,9 @@ export default defineComponent({ isEditable: this.isNotIncludedInCurrentProjectConfiguration(exec.projectType + '.templateId'), editMode: this.editMode, possibleValues: this.exporterTemplateIds[exec.projectType] ?? [], - displayPossibleValue: (label: string) => {return {name: label, description: ""}}, + displayPossibleValue: (label: string) => { + return {name: label, description: ""} + }, mandatory: true, category: FixedDialogStep.CUSTOM, visibilityCondition: @@ -1436,7 +1511,9 @@ export default defineComponent({ editMode: this.editMode, redirectUrl: this.project?.explorerUrl ?? undefined, possibleValues: this.queryFormats, - displayPossibleValue: (label: string) => {return {name: label, description: ""}}, + displayPossibleValue: (label: string) => { + return {name: label, description: ""} + }, mandatory: true, category: FixedDialogStep.QUERY, visibilityCondition: !this.existsDraftDialog || this.draftDialogStepper.currentStep?.id === FixedDialogStep.SUMMARY @@ -1514,8 +1591,8 @@ export default defineComponent({ this.formTitles.filter(formTitle => { this.draftDialogStepper.hasCurrentStep(formTitle.title) })); - return [...fixedFields, ...dynamicSelectedForms, ...dynamicFields].sort((a,b) => - (this.categoryRank[a.category.toLowerCase()] ?? 999) - (this.categoryRank[b.category.toLowerCase()] ?? 999)); + return [...fixedFields, ...dynamicSelectedForms, ...dynamicFields].sort((a, b) => + (this.categoryRank[a.category.toLowerCase()] ?? 999) - (this.categoryRank[b.category.toLowerCase()] ?? 999)); }, fetchButtons(): void { @@ -1699,9 +1776,12 @@ export default defineComponent({ visibilityCondition: this.currentUser?.projectState !== 'REJECTED' }, { - module: Module.PROJECT_STATE_MODULE, action: Action.REQUEST_CHANGES_IN_PROJECT_ANALYSIS_ACTION, + module: Module.PROJECT_STATE_MODULE, + action: Action.REQUEST_CHANGES_IN_PROJECT_ANALYSIS_ACTION, refreshContextCallFunction: this.refreshContext as () => void, - text: "Request Changes", withMessage: true, cssClass: "btn btn-primary mr-2" + text: "Request Changes", + withMessage: true, + cssClass: "btn btn-primary mr-2" } ] as ActionButton[] }, @@ -1709,9 +1789,12 @@ export default defineComponent({ label: "Research Environment", button: [ { - module: Module.USER_MODULE, action: Action.EXISTS_RESEARCH_ENVIRONMENT_WORKSPACE_ACTION, + module: Module.USER_MODULE, + action: Action.EXISTS_RESEARCH_ENVIRONMENT_WORKSPACE_ACTION, refreshContextCallFunction: this.refreshContext as () => void, - text: "Go", withMessage: false, cssClass: "btn btn-primary mr-2", + text: "Go", + withMessage: false, + cssClass: "btn btn-primary mr-2", visibilityCondition: this.researchEnvironmentUrl !== undefined && this.existsResearchEnvironmentWorkspace, doActionOnClick: this.goToResearchEnvironment as () => void } @@ -1758,7 +1841,11 @@ export default defineComponent({ getDialogStep(stepId: string): DialogStep | undefined { const step = FixedDialogSteps.find(step => step.id === stepId) - return step ? step : {id: "", displayName: stepId.charAt(0).toUpperCase() + stepId.slice(1), description: ""} + return step ? step : { + id: "", + displayName: stepId.charAt(0).toUpperCase() + stepId.slice(1), + description: "" + } } } @@ -2133,14 +2220,6 @@ export default defineComponent({ justify-content: center; } -.stepper-button { - color: #00489c; - border: none; - background-color: white; - font-size: large; - cursor: pointer; -} - .project-field-header { width: 90%; margin: 2rem 1rem 1rem 1rem; @@ -2176,6 +2255,7 @@ export default defineComponent({ cursor: pointer; color: #00489c; } + .form-switch-box { display: flex; justify-content: end; @@ -2183,19 +2263,23 @@ export default defineComponent({ position: sticky; top: 1rem; } + .form-switch .form-check-input.inactive { background-color: #EEEEEE; } + .input-field-separator { height: 1px; padding: 0; margin: 1rem 4rem 1rem 0; background-image: linear-gradient(to right, transparent, rgb(180, 180, 180), transparent); } + .clickable { cursor: pointer; - color: rgb(51,142,195); + color: rgb(51, 142, 195); } + .clickable:hover { text-decoration: underline; } diff --git a/src/services/BridgeheadOverviewHeaders.ts b/src/services/BridgeheadOverviewHeaders.ts index aaecc82..c656ed3 100644 --- a/src/services/BridgeheadOverviewHeaders.ts +++ b/src/services/BridgeheadOverviewHeaders.ts @@ -1,29 +1,29 @@ import {ProjectType} from "@/services/projectManagerBackendService"; export enum BridgeheadOverviewHeader { - SITES = 'Sites', - VOTUM = 'Votum', - TEILER = 'Teiler', - USER_ACCESS = 'User Access', - APPLICANT_RESULTS_ACCEPTANCE = 'Applicant Results Acceptance' + SITES = 'Sites', + VOTUM = 'Ethic vote', + TEILER = 'Data export authorised', + USER_ACCESS = 'Data accessible', + APPLICANT_RESULTS_ACCEPTANCE = 'Data received and accepted' } export class MultiHeader { - // Build Teiler header for a given projectType - static build(projectType: ProjectType, headerType: BridgeheadOverviewHeader): string { - return `${headerType} (${projectType})`; - } + // Build Teiler header for a given projectType + static build(projectType: ProjectType, headerType: BridgeheadOverviewHeader): string { + return `${headerType} (${projectType})`; + } - // Check if a header string is a Teiler header - static isHeaderOfHeaderType(header: string, headerType: BridgeheadOverviewHeader): boolean { - return header.includes(headerType); - } + // Check if a header string is a Teiler header + static isHeaderOfHeaderType(header: string, headerType: BridgeheadOverviewHeader): boolean { + return header.includes(headerType); + } - // Extract projectType from a Teiler header string - static extractProjectType(header: string): ProjectType | undefined { - const match = header.match(/\((.*?)\)/); - return match ? (match[1] as ProjectType) : undefined; - } + // Extract projectType from a Teiler header string + static extractProjectType(header: string): ProjectType | undefined { + const match = header.match(/\((.*?)\)/); + return match ? (match[1] as ProjectType) : undefined; + } } diff --git a/src/services/projectManagerBackendService.ts b/src/services/projectManagerBackendService.ts index 0f17a92..51b2fbe 100644 --- a/src/services/projectManagerBackendService.ts +++ b/src/services/projectManagerBackendService.ts @@ -327,6 +327,23 @@ export function getQueryState(bridgehead: Bridgehead, projectType?: ProjectType) : undefined; } +export function getMergedQueryStates( + bridgehead: Bridgehead, + types: ProjectType[] +): { state: string; types: ProjectType[] }[] { + const stateMap = new Map(); + + for (const type of types) { + const state = getQueryState(bridgehead, type) ?? 'unknown'; + if (!stateMap.has(state)) { + stateMap.set(state, []); + } + stateMap.get(state)!.push(type); + } + + return Array.from(stateMap.entries()).map(([state, types]) => ({ state, types })); +} + export function hasValidOutputs(project?: Project): boolean { return ( (project?.outputs?.length ?? 0) > 0 &&