Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • 2.11.0-privacyPolicy
  • APIv2
  • Docs/Setup
  • Experiment/fix-debugging
  • Experimental/Heinrichs-cypress
  • Feature/xxxx-turnOffDataPub
  • Fix/xxxx-ToS400Error
  • Fix/xxxx-migrateLogin
  • Fix/xxxx-tokenUploadButton
  • Hotfix/0038-correctDownload
  • Hotfix/1917-PublicFilesVisibility
  • Hotfix/1963-fixOrganizationField
  • Hotfix/2015-PublicFilesVisibility
  • Hotfix/2130-uiv2ContactChange
  • Hotfix/2144-invitationCall
  • Hotfix/2150-fixUpload
  • Hotfix/2160-userOrgsInst
  • Hotfix/2190-requiredFieldsForUserProfile
  • Hotfix/2196-RCVTableTranslation
  • Hotfix/2212-fixFiles
  • Hotfix/2226-userProfileSaveButton
  • Hotfix/2232-dependencyFix
  • Hotfix/2233-fixMe
  • Hotfix/2258-saveButtonWorksAsExpected
  • Hotfix/2296-selectedValuesNotReturned
  • Hotfix/2308-defaultLicense
  • Hotfix/2335-fixingSearchRCV
  • Hotfix/2353-dropShape
  • Hotfix/2370-fixDeleteButton
  • Hotfix/2378-linkedFix
  • Hotfix/2379-filesDragAndDrop
  • Hotfix/2382-guestStillBuggy
  • Hotfix/2384-guestsAndLinked
  • Hotfix/2427-adminTrouble
  • Hotfix/2459-EncodingPath
  • Hotfix/2465-orcidLink
  • Hotfix/2465-orcidLink-v1.25.1
  • Hotfix/2504-formGen
  • Hotfix/2541-resCreate
  • Hotfix/2601-correctMetadataIdentity
  • Hotfix/2611-feedback
  • Hotfix/2618-turtle
  • Hotfix/2681-validationErrors
  • Hotfix/2684-correctEncoding
  • Hotfix/2684-fixSubMetadata
  • Hotfix/2713-validateEntryName
  • Hotfix/2734-allowEmptyLicense
  • Hotfix/2765-encodingAgain
  • Hotfix/2852-adaptTextForToSUi
  • Hotfix/2853-optimizationV4
  • Hotfix/2943-reloadingResources
  • Hotfix/2943-searchHighlighting
  • Hotfix/2957-styleAndUpgrade
  • Hotfix/2971-fixTextInDataPub
  • Hotfix/2989-cookieLength
  • Hotfix/662-keepSidebarExpanded
  • Hotfix/xxxx-correctLinking
  • Hotfix/xxxx-folderRecursive
  • Hotfix/xxxx-fullscreenCss
  • Hotfix/xxxx-homepageDisplay
  • Hotfix/xxxx-liveReleaseFixes
  • Hotfix/xxxx-partnerProjects
  • Hotfix/xxxx-workingFileIndex
  • Issue/1782-structualDataIntegration
  • Issue/1792-newMetadataStructure
  • Issue/1822-coscineUIv2App
  • Issue/1824-componentsUIv2
  • Issue/1824-routerAdditions
  • Issue/1825-codeQualityPipelines
  • Issue/1833-newLogin
  • Issue/1843-multipleFilesValidation
  • Issue/1860-searchScoping
  • Issue/1861-searchMetadata
  • Issue/1862-searchFacets
  • Issue/1863-paginationForSearch
  • Issue/1926-userProfile
  • Issue/1927-projectAppMigration
  • Issue/1928-sidebarmenuAddition
  • Issue/1929-vuexToPinia
  • Issue/1938-internalHandling
  • Issue/1951-quotaImplementation
  • Issue/1953-owlImports
  • Issue/1957-resourceAppMigration
  • Issue/1957-resourceAppMigrationNew
  • Issue/1962-SearchAppUI2
  • Issue/1964-tokenExpiryUIv2
  • Issue/1965-userListMigration
  • Issue/1970-breadcrumbs
  • Issue/1971-projectEditCreateMigration
  • Issue/1972-homeDepot
  • Issue/1974-shibbolethLogout
  • Issue/1976-resouceCreationVaildEmail
  • Issue/1979-supportAdminUIv2Migration
  • Issue/1980-userManagement
  • Issue/1985-adaptSidebar
  • Issue/2002-migrateResourceCreate
  • Issue/2003-resourceSettings
  • Issue/2008-quotaManagement
  • Issue/2011-pathConfig
  • Issue/2016-BannerMigration
  • 1.28.0-pilot
  • v1.0.0
  • v1.1.0
  • v1.10.0
  • v1.10.1
  • v1.10.2
  • v1.10.3
  • v1.11.0
  • v1.11.1
  • v1.11.2
  • v1.11.3
  • v1.11.4
  • v1.11.5
  • v1.11.6
  • v1.11.7
  • v1.12.0
  • v1.13.0
  • v1.14.0
  • v1.14.1
  • v1.14.2
  • v1.14.3
  • v1.15.0
  • v1.15.1
  • v1.16.0
  • v1.16.1
  • v1.16.2
  • v1.16.3
  • v1.17.0
  • v1.17.1
  • v1.17.2
  • v1.18.0
  • v1.18.1
  • v1.19.0
  • v1.2.0
  • v1.20.0
  • v1.20.1
  • v1.20.2
  • v1.20.3
  • v1.20.4
  • v1.20.5
  • v1.21.0
  • v1.22.0
  • v1.22.1
  • v1.22.2
  • v1.23.0
  • v1.23.1
  • v1.23.2
  • v1.23.3
  • v1.23.4
  • v1.23.5
  • v1.23.6
  • v1.23.6-patch-2417-2427
  • v1.24.0
  • v1.24.1
  • v1.25.0
  • v1.25.1
  • v1.26.0
  • v1.26.1
  • v1.27.0
  • v1.27.1
  • v1.27.1-pilot
  • v1.28.0
  • v1.29.0
  • v1.29.1
  • v1.29.2
  • v1.3.0
  • v1.30.0
  • v1.30.1
  • v1.30.2
  • v1.31.0
  • v1.32.0
  • v1.4.0
  • v1.4.1
  • v1.5.0
  • v1.6.0
  • v1.6.1
  • v1.6.2
  • v1.7.0
  • v1.8.0
  • v1.8.1
  • v1.8.2
  • v1.9.0
  • v2.0.0
  • v2.1.0
  • v2.10.0
  • v2.10.1
  • v2.11.0
  • v2.12.0
  • v2.12.1
  • v2.12.2
  • v2.12.3
  • v2.12.4
  • v2.12.5
  • v2.13.0
  • v2.13.1
  • v2.13.2
  • v2.13.3
  • v2.13.4
  • v2.14.0
  • v2.15.0
200 results

Target

Select target project
  • coscine/frontend/apps/ui
1 result
Select Git revision
  • 2.11.0-privacyPolicy
  • APIv2
  • Docs/Setup
  • Experiment/fix-debugging
  • Experimental/Heinrichs-cypress
  • Feature/xxxx-turnOffDataPub
  • Fix/xxxx-ToS400Error
  • Fix/xxxx-migrateLogin
  • Fix/xxxx-tokenUploadButton
  • Hotfix/0038-correctDownload
  • Hotfix/1917-PublicFilesVisibility
  • Hotfix/1963-fixOrganizationField
  • Hotfix/2015-PublicFilesVisibility
  • Hotfix/2130-uiv2ContactChange
  • Hotfix/2144-invitationCall
  • Hotfix/2150-fixUpload
  • Hotfix/2160-userOrgsInst
  • Hotfix/2190-requiredFieldsForUserProfile
  • Hotfix/2196-RCVTableTranslation
  • Hotfix/2212-fixFiles
  • Hotfix/2226-userProfileSaveButton
  • Hotfix/2232-dependencyFix
  • Hotfix/2233-fixMe
  • Hotfix/2258-saveButtonWorksAsExpected
  • Hotfix/2296-selectedValuesNotReturned
  • Hotfix/2308-defaultLicense
  • Hotfix/2335-fixingSearchRCV
  • Hotfix/2353-dropShape
  • Hotfix/2370-fixDeleteButton
  • Hotfix/2378-linkedFix
  • Hotfix/2379-filesDragAndDrop
  • Hotfix/2382-guestStillBuggy
  • Hotfix/2384-guestsAndLinked
  • Hotfix/2427-adminTrouble
  • Hotfix/2459-EncodingPath
  • Hotfix/2465-orcidLink
  • Hotfix/2465-orcidLink-v1.25.1
  • Hotfix/2504-formGen
  • Hotfix/2541-resCreate
  • Hotfix/2601-correctMetadataIdentity
  • Hotfix/2611-feedback
  • Hotfix/2618-turtle
  • Hotfix/2681-validationErrors
  • Hotfix/2684-correctEncoding
  • Hotfix/2684-fixSubMetadata
  • Hotfix/2713-validateEntryName
  • Hotfix/2734-allowEmptyLicense
  • Hotfix/2765-encodingAgain
  • Hotfix/2852-adaptTextForToSUi
  • Hotfix/2853-optimizationV4
  • Hotfix/2943-reloadingResources
  • Hotfix/2943-searchHighlighting
  • Hotfix/2957-styleAndUpgrade
  • Hotfix/2971-fixTextInDataPub
  • Hotfix/2989-cookieLength
  • Hotfix/662-keepSidebarExpanded
  • Hotfix/xxxx-correctLinking
  • Hotfix/xxxx-folderRecursive
  • Hotfix/xxxx-fullscreenCss
  • Hotfix/xxxx-homepageDisplay
  • Hotfix/xxxx-liveReleaseFixes
  • Hotfix/xxxx-partnerProjects
  • Hotfix/xxxx-workingFileIndex
  • Issue/1782-structualDataIntegration
  • Issue/1792-newMetadataStructure
  • Issue/1822-coscineUIv2App
  • Issue/1824-componentsUIv2
  • Issue/1824-routerAdditions
  • Issue/1825-codeQualityPipelines
  • Issue/1833-newLogin
  • Issue/1843-multipleFilesValidation
  • Issue/1860-searchScoping
  • Issue/1861-searchMetadata
  • Issue/1862-searchFacets
  • Issue/1863-paginationForSearch
  • Issue/1926-userProfile
  • Issue/1927-projectAppMigration
  • Issue/1928-sidebarmenuAddition
  • Issue/1929-vuexToPinia
  • Issue/1938-internalHandling
  • Issue/1951-quotaImplementation
  • Issue/1953-owlImports
  • Issue/1957-resourceAppMigration
  • Issue/1957-resourceAppMigrationNew
  • Issue/1962-SearchAppUI2
  • Issue/1964-tokenExpiryUIv2
  • Issue/1965-userListMigration
  • Issue/1970-breadcrumbs
  • Issue/1971-projectEditCreateMigration
  • Issue/1972-homeDepot
  • Issue/1974-shibbolethLogout
  • Issue/1976-resouceCreationVaildEmail
  • Issue/1979-supportAdminUIv2Migration
  • Issue/1980-userManagement
  • Issue/1985-adaptSidebar
  • Issue/2002-migrateResourceCreate
  • Issue/2003-resourceSettings
  • Issue/2008-quotaManagement
  • Issue/2011-pathConfig
  • Issue/2016-BannerMigration
  • 1.28.0-pilot
  • v1.0.0
  • v1.1.0
  • v1.10.0
  • v1.10.1
  • v1.10.2
  • v1.10.3
  • v1.11.0
  • v1.11.1
  • v1.11.2
  • v1.11.3
  • v1.11.4
  • v1.11.5
  • v1.11.6
  • v1.11.7
  • v1.12.0
  • v1.13.0
  • v1.14.0
  • v1.14.1
  • v1.14.2
  • v1.14.3
  • v1.15.0
  • v1.15.1
  • v1.16.0
  • v1.16.1
  • v1.16.2
  • v1.16.3
  • v1.17.0
  • v1.17.1
  • v1.17.2
  • v1.18.0
  • v1.18.1
  • v1.19.0
  • v1.2.0
  • v1.20.0
  • v1.20.1
  • v1.20.2
  • v1.20.3
  • v1.20.4
  • v1.20.5
  • v1.21.0
  • v1.22.0
  • v1.22.1
  • v1.22.2
  • v1.23.0
  • v1.23.1
  • v1.23.2
  • v1.23.3
  • v1.23.4
  • v1.23.5
  • v1.23.6
  • v1.23.6-patch-2417-2427
  • v1.24.0
  • v1.24.1
  • v1.25.0
  • v1.25.1
  • v1.26.0
  • v1.26.1
  • v1.27.0
  • v1.27.1
  • v1.27.1-pilot
  • v1.28.0
  • v1.29.0
  • v1.29.1
  • v1.29.2
  • v1.3.0
  • v1.30.0
  • v1.30.1
  • v1.30.2
  • v1.31.0
  • v1.32.0
  • v1.4.0
  • v1.4.1
  • v1.5.0
  • v1.6.0
  • v1.6.1
  • v1.6.2
  • v1.7.0
  • v1.8.0
  • v1.8.1
  • v1.8.2
  • v1.9.0
  • v2.0.0
  • v2.1.0
  • v2.10.0
  • v2.10.1
  • v2.11.0
  • v2.12.0
  • v2.12.1
  • v2.12.2
  • v2.12.3
  • v2.12.4
  • v2.12.5
  • v2.13.0
  • v2.13.1
  • v2.13.2
  • v2.13.3
  • v2.13.4
  • v2.14.0
  • v2.15.0
200 results
Show changes
Commits on Source (16)
Showing
with 505 additions and 228 deletions
...@@ -4,13 +4,12 @@ ...@@ -4,13 +4,12 @@
"preset": "eslint" "preset": "eslint"
}], }],
["@semantic-release/release-notes-generator", { ["@semantic-release/release-notes-generator", {
"preset": "eslint", "preset": "eslint"
}], }],
["@semantic-release/gitlab", { ["@semantic-release/gitlab", {
"preset": "eslint", "preset": "eslint",
"gitlabUrl": "https://git.rwth-aachen.de" "gitlabUrl": "https://git.rwth-aachen.de"
}], }],
#Currently not supported
["@semantic-release/npm", { ["@semantic-release/npm", {
"preset": "eslint", "preset": "eslint",
"tarballDir": "dist", "tarballDir": "dist",
......
{ {
"workbench.colorCustomizations": {
"activityBar.background": "#64b687",
"activityBar.foreground": "#ffffff",
"activityBar.inactiveForeground": "#32485c",
"activityBar.activeBorder": "#32485c",
"activityBarBadge.background": "#32485c",
"activityBarBadge.foreground": "#ffffff",
},
"search.exclude": { "search.exclude": {
"**/.yarn": true, "**/.yarn": true,
"**/.pnp.*": true "**/.pnp.*": true
......
{ {
"name": "ui", "name": "ui",
"version": "3.6.1", "version": "3.7.0",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vite build", "build": "vite build",
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
"coverage": "vitest run --coverage" "coverage": "vitest run --coverage"
}, },
"dependencies": { "dependencies": {
"@coscine/api-client": "^3.11.0", "@coscine/api-client": "^3.12.0",
"@coscine/form-generator": "^4.0.5", "@coscine/form-generator": "^4.0.5",
"@dynamic-mapper/mapper": "^1.10.4", "@dynamic-mapper/mapper": "^1.10.4",
"@pinia/testing": "^0.1.3", "@pinia/testing": "^0.1.3",
...@@ -30,6 +30,7 @@ ...@@ -30,6 +30,7 @@
"http-status-codes": "^2.3.0", "http-status-codes": "^2.3.0",
"jose": "^5.1.1", "jose": "^5.1.1",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"markdown-it": "^14.1.0",
"moment": "^2.29.4", "moment": "^2.29.4",
"pinia": "^2.1.7", "pinia": "^2.1.7",
"rdf-ext": "^2.5.2", "rdf-ext": "^2.5.2",
...@@ -58,6 +59,7 @@ ...@@ -58,6 +59,7 @@
"@semantic-release/release-notes-generator": "^11.0.7", "@semantic-release/release-notes-generator": "^11.0.7",
"@types/file-saver": "^2.0.7", "@types/file-saver": "^2.0.7",
"@types/lodash": "^4.14.201", "@types/lodash": "^4.14.201",
"@types/markdown-it": "^14",
"@types/rdf-ext": "^2.5.0", "@types/rdf-ext": "^2.5.0",
"@types/rdf-validate-shacl": "^0.4.7", "@types/rdf-validate-shacl": "^0.4.7",
"@types/uuid": "^9.0.7", "@types/uuid": "^9.0.7",
......
...@@ -10,7 +10,8 @@ ...@@ -10,7 +10,8 @@
<SidebarMenu v-if="isLoggedIn && areTosAccepted" /> <SidebarMenu v-if="isLoggedIn && areTosAccepted" />
<main :class="mainContainerSizing"> <main :class="mainContainerSizing">
<b-container fluid> <b-container fluid>
<Maintenance /> <Noc />
<Internal />
<BreadCrumbs v-if="isLoggedIn && areTosAccepted" /> <BreadCrumbs v-if="isLoggedIn && areTosAccepted" />
<RouterView /> <RouterView />
</b-container> </b-container>
......
...@@ -68,6 +68,7 @@ declare module 'vue' { ...@@ -68,6 +68,7 @@ declare module 'vue' {
BToastOrchestrator: typeof import('bootstrap-vue-next')['BToastOrchestrator'] BToastOrchestrator: typeof import('bootstrap-vue-next')['BToastOrchestrator']
BTooltip: typeof import('bootstrap-vue-next')['BTooltip'] BTooltip: typeof import('bootstrap-vue-next')['BTooltip']
BTr: typeof import('bootstrap-vue-next')['BTr'] BTr: typeof import('bootstrap-vue-next')['BTr']
copy: typeof import('./components/banner/Noc copy.vue')['default']
CoscineCard: typeof import('./components/coscine/CoscineCard.vue')['default'] CoscineCard: typeof import('./components/coscine/CoscineCard.vue')['default']
CoscineFormGroup: typeof import('./components/coscine/CoscineFormGroup.vue')['default'] CoscineFormGroup: typeof import('./components/coscine/CoscineFormGroup.vue')['default']
CoscineHeadline: typeof import('./components/coscine/CoscineHeadline.vue')['default'] CoscineHeadline: typeof import('./components/coscine/CoscineHeadline.vue')['default']
...@@ -108,10 +109,12 @@ declare module 'vue' { ...@@ -108,10 +109,12 @@ declare module 'vue' {
IBiSortUp: typeof import('~icons/bi/sort-up')['default'] IBiSortUp: typeof import('~icons/bi/sort-up')['default']
IBiSunFill: typeof import('~icons/bi/sun-fill')['default'] IBiSunFill: typeof import('~icons/bi/sun-fill')['default']
IBiThreeDotsVertical: typeof import('~icons/bi/three-dots-vertical')['default'] IBiThreeDotsVertical: typeof import('~icons/bi/three-dots-vertical')['default']
Internal: typeof import('./components/banner/Internal.vue')['default']
LoadingIndicator: typeof import('./components/elements/LoadingIndicator.vue')['default'] LoadingIndicator: typeof import('./components/elements/LoadingIndicator.vue')['default']
LoadingSpinner: typeof import('./components/coscine/LoadingSpinner.vue')['default'] LoadingSpinner: typeof import('./components/coscine/LoadingSpinner.vue')['default']
Maintenance: typeof import('./components/banner/Maintenance.vue')['default'] Maintenance: typeof import('./components/banner/Maintenance.vue')['default']
Navbar: typeof import('./components/elements/Navbar.vue')['default'] Navbar: typeof import('./components/elements/Navbar.vue')['default']
Noc: typeof import('./components/banner/Noc.vue')['default']
NotificationToast: typeof import('./components/toasts/NotificationToast.vue')['default'] NotificationToast: typeof import('./components/toasts/NotificationToast.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink'] RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView'] RouterView: typeof import('vue-router')['RouterView']
......
/* Testing imports */
import { mount } from "@vue/test-utils";
import { createTestingPinia } from "@pinia/testing";
/* Vue i18n */
import i18n, { def } from "@/plugins/vue-i18n";
i18n.global.availableLocales.forEach((locale) => {
i18n.global.setLocaleMessage(locale, def[locale]); // default locale messages
});
/* Tested Component */
import Internal from "./Internal.vue";
import { getTestShibbolethUserState } from "@/data/mockup/testUser";
import {
getInternalMessagesResponse,
testSystemStatusState,
} from "@/data/mockup/testSystemStatus";
import useSystemStatusStore from "@/store/systemStatus";
import { getTestMainState } from "@/data/mockup/testMain";
describe("Internal", async () => {
// Create a mocked pinia instance with initial state
const testingPinia = createTestingPinia({
createSpy: vitest.fn,
initialState: {
main: await getTestMainState(),
systemStatus: testSystemStatusState,
user: getTestShibbolethUserState(),
},
});
const createWrapper = () => {
return mount(Internal, {
global: {
plugins: [testingPinia, i18n],
},
});
};
let wrapper: ReturnType<typeof createWrapper>;
beforeEach(() => {
// shallowMount does not work here!
wrapper = createWrapper();
});
const systemStatusStore = useSystemStatusStore(testingPinia);
vi.mock("vue-i18n", () => ({
useI18n: () => ({
locale: {
value: "en",
},
t: (key: string) => key,
d: (key: string) => key,
}),
}));
it("Should render internal messages if they exist", async () => {
await nextTick();
expect(wrapper.findAll(".alert").length).toBe(1);
expect(wrapper.text()).toEqual(getInternalMessagesResponse[0].body?.en);
});
it("Should not render any messages if internalMessages array is empty", async () => {
await nextTick();
// Overwrite the internalMessages array with an empty array
systemStatusStore.banner.internal.messages = [];
await nextTick();
expect(wrapper.findAll(".alert").length).toBe(0);
// Restore the store's state, as tests are dependent on each other
systemStatusStore.banner.internal.messages = getInternalMessagesResponse;
});
it("Should use the correct variant for each message type", async () => {
await nextTick();
const alerts = wrapper.findAll(".alert");
expect(alerts.at(0)?.attributes("class")).toContain("info");
});
it("Should call hideMessage when an alert is closed", async () => {
await nextTick();
const banner = wrapper.find(".alert");
const closeButton = banner.findAll(".btn-close").at(0);
closeButton?.trigger("click");
expect(systemStatusStore.hideMessage).toHaveBeenCalledOnce();
});
it("Should not display hidden messages", async () => {
await nextTick();
systemStatusStore.banner.hidden = [getInternalMessagesResponse[0].id!];
await nextTick();
expect(wrapper.findAll(".alert").length).toBe(0);
});
});
<template>
<div v-if="internalMessages.length">
<b-alert
v-for="(message, index) in internalMessages"
:key="index"
model-value
show
dismissible
:locale="$i18n.locale"
:variant="getBannerVariant(message.type)"
@closed="hideMessage(message.id)"
>
<p v-dompurify-html="renderMarkdown(message.body?.[locale] || '~')" />
</b-alert>
</div>
</template>
<script lang="ts">
import { defineComponent, computed } from "vue";
import { useI18n } from "vue-i18n";
import { useSystemStatusStore } from "@/store/systemStatus";
import { mapMessageTypeToBannerVariant } from "@/util/messageTypeMap";
import type { MessageType } from "@coscine/api-client/dist/types/Coscine.Api";
import type { BaseColorVariant } from "bootstrap-vue-next/dist/src/types";
import markdownIt from "markdown-it";
export default defineComponent({
setup() {
const i18n = useI18n();
const systemStatusStore = useSystemStatusStore();
const md = markdownIt();
const locale = computed(() => i18n.locale.value);
/**
* Computes the list of visible internal messages from the system status store.
* @returns {MessageDto[]} The list of visible internal messages.
*/
const internalMessages = computed(
() => systemStatusStore.visibleInternalMessages,
);
/**
* Hides a message by its ID.
* @param {string | undefined} id - The ID of the message to hide.
*/
const hideMessage = (id?: string) => {
if (id) systemStatusStore.hideMessage(id);
};
/**
* Maps a message type to a Bootstrap Vue variant.
* @param {MessageType | undefined} type - The type of the message.
* @returns {keyof BaseColorVariant | null | undefined} The corresponding Bootstrap Vue variant.
*/
const getBannerVariant = (
type?: MessageType,
): keyof BaseColorVariant | null | undefined => {
return mapMessageTypeToBannerVariant(type);
};
/**
* Renders markdown to HTML.
* @param {string} markdownText - The markdown text to render.
* @returns {string} The rendered HTML.
*/
const renderMarkdown = (markdownText: string): string => {
return md.render(markdownText);
};
return {
internalMessages,
locale,
hideMessage,
getBannerVariant,
renderMarkdown,
};
},
});
</script>
<template>
<b-alert
v-if="visibility"
:model-value="show"
dismissible
:variant="maintenance.type !== 'Info' ? 'warning' : 'info'"
@closed="saveVisibility"
>
<p>
<span class="font-weight-bold">
{{ `${messageType}${$t("banner.separator")}` }}
</span>
<span v-dompurify-html="messageBody"></span>
<span v-if="maintenance.href">
{{ $t("banner.maintenance.linkText") }}
<a :href="maintenance.href" target="_blank"
>{{ $t("banner.maintenance.moreInformation") }}
</a>
</span>
</p>
</b-alert>
</template>
<script lang="ts">
// import the main store
import useMainStore from "@/store/index";
import type { MaintenanceDto } from "@coscine/api-client/dist/types/Coscine.Api";
export default defineComponent({
setup() {
const mainStore = useMainStore();
return { mainStore };
},
computed: {
maintenance(): MaintenanceDto {
return this.mainStore.coscine.banner.maintenance;
},
visibility(): boolean {
return (
this.mainStore.coscine.banner.maintenanceVisibility !==
this.mainStore.coscine.banner.dateString &&
this.mainStore.coscine.banner.dateString.trim() !== ""
);
},
show(): boolean {
return this.visibility && this.maintenance.type && this.maintenance.body
? true
: false;
},
locale(): string {
return this.$i18n.locale;
},
messageBody(): string | undefined {
const notificationText = this.createNotificationText()?.trim();
// empty texts
if (!notificationText) {
return this.$t("banner.maintenance.notificationDefaultText").toString();
}
const languageSpecificNotificationTexts =
notificationText.split(/(?<!:)\/\//);
if (languageSpecificNotificationTexts?.length === 1) {
return languageSpecificNotificationTexts[0];
}
return this.$i18n.locale === "de"
? languageSpecificNotificationTexts[1]
: languageSpecificNotificationTexts[0];
},
messageType(): string {
if (this.maintenance.type) {
switch (this.maintenance.type) {
case "Eingriff":
return this.$t("banner.maintenance.type.intervention").toString();
case "Störung":
return this.$t("banner.maintenance.type.disturbance").toString();
case "Teilstörung":
return this.$t(
"banner.maintenance.type.partialDisturbance",
).toString();
case "Unterbrechung":
return this.$t("banner.maintenance.type.interruption").toString();
case "eingeschränkt betriebsfähig":
return this.$t(
"banner.maintenance.type.limitedOperation",
).toString();
case "Wartung":
return this.$t("banner.maintenance.type.maintenance").toString();
case "Teilwartung":
return this.$t(
"banner.maintenance.type.partialMaintenance",
).toString();
case "Info":
return this.$t("banner.maintenance.type.info").toString();
default:
return this.$t("banner.maintenance.type.maintenance").toString();
}
} else {
return this.$t("banner.maintenance.type.maintenance").toString();
}
},
},
methods: {
saveVisibility() {
this.mainStore.coscine.banner.maintenanceVisibility =
this.mainStore.coscine.banner.dateString;
},
createNotificationText(): string | undefined {
if (this.maintenance && this.maintenance.body) {
return this.maintenance.body;
} else {
return "";
}
},
},
});
</script>
<style></style>
/* Testing imports */
import { mount } from "@vue/test-utils";
import { createTestingPinia } from "@pinia/testing";
/* Vue i18n */
import i18n, { def } from "@/plugins/vue-i18n";
i18n.global.availableLocales.forEach((locale) => {
i18n.global.setLocaleMessage(locale, def[locale]); // default locale messages
});
/* Tested Component */
import Noc from "./Noc.vue";
import { getTestShibbolethUserState } from "@/data/mockup/testUser";
import {
getNocMessagesResponse,
testSystemStatusState,
} from "@/data/mockup/testSystemStatus";
import useSystemStatusStore from "@/store/systemStatus";
import { getTestMainState } from "@/data/mockup/testMain";
describe("Noc", async () => {
// Create a mocked pinia instance with initial state
const testingPinia = createTestingPinia({
createSpy: vitest.fn,
initialState: {
main: await getTestMainState(),
systemStatus: testSystemStatusState,
user: getTestShibbolethUserState(),
},
});
const createWrapper = () => {
return mount(Noc, {
global: {
plugins: [testingPinia, i18n],
},
});
};
let wrapper: ReturnType<typeof createWrapper>;
beforeEach(() => {
wrapper = createWrapper();
});
const systemStatusStore = useSystemStatusStore(testingPinia);
it("Should render NOC messages if they exist", async () => {
await nextTick();
const banners = wrapper.findAll(".alert");
expect(banners.length).toBe(getNocMessagesResponse.length);
const text = i18n.global.t("banner.noc.text.Disturbance").substring(0, 10);
expect(banners.at(0)?.text()).toContain(text);
expect(banners.at(0)?.attributes("class")).toContain("danger");
});
it("Should not render any messages if nocMessages array is empty", async () => {
await nextTick();
// Overwrite the nocMessages array with an empty array
systemStatusStore.banner.noc.messages = [];
await nextTick();
expect(wrapper.findAll(".alert").length).toBe(0);
// Restore the store's state, as tests are dependent on each other
systemStatusStore.banner.noc.messages = getNocMessagesResponse;
});
it("Should call hideMessage when an alert is closed", async () => {
await nextTick();
const banner = wrapper.find(".alert");
const closeButton = banner.findAll(".btn-close").at(0);
closeButton?.trigger("click");
expect(systemStatusStore.hideMessage).toHaveBeenCalledOnce();
});
it("Should use the correct variant for each message type", async () => {
await nextTick();
const alerts = wrapper.findAll(".alert");
expect(alerts.at(0)?.attributes("class")).toContain("danger");
expect(alerts.at(1)?.attributes("class")).toContain("warning");
});
it("Should render a link within the message if href is provided", async () => {
await nextTick();
const links = wrapper.findAll("a");
expect(links.at(0)?.attributes("href")).toBe(
"https://example.com/noc-5555",
);
expect(links.at(1)?.attributes("href")).toBe(
"https://example.com/noc-5572",
);
});
});
<template>
<div v-if="nocMessages.length">
<b-alert
v-for="(message, index) in nocMessages"
:key="index"
model-value
show
dismissible
:variant="getBannerVariant(message.type)"
@closed="hideMessage(message.id)"
>
<i18n-t
scope="global"
:keypath="`banner.noc.text.${message.type}`"
tag="p"
>
<template #nocPortal>
<a :href="message.href ?? undefined" target="_blank">
<i>{{ $t("banner.noc.nocPortal") }}</i>
</a>
</template>
</i18n-t>
</b-alert>
</div>
</template>
<script lang="ts">
import { defineComponent, computed } from "vue";
import { useSystemStatusStore } from "@/store/systemStatus";
import { mapMessageTypeToBannerVariant } from "@/util/messageTypeMap";
import type { MessageType } from "@coscine/api-client/dist/types/Coscine.Api";
import type { BaseColorVariant } from "bootstrap-vue-next/dist/src/types";
export default defineComponent({
setup() {
const systemStatusStore = useSystemStatusStore();
/**
* Computes the list of visible NOC (Network Operations Center) messages.
* @returns {MessageDto[]} The list of visible NOC messages.
*/
const nocMessages = computed(() => systemStatusStore.visibleNocMessages);
/**
* Hides a NOC message by its ID.
* @param {string | undefined} id - The ID of the message to hide.
*/
const hideMessage = (id?: string) => {
if (id) systemStatusStore.hideMessage(id);
};
/**
* Maps a message type to a Bootstrap Vue variant.
* @param {MessageType | undefined} type - The type of the NOC message.
* @returns {keyof BaseColorVariant | null | undefined} The corresponding Bootstrap Vue variant.
*/
const getBannerVariant = (
type?: MessageType,
): keyof BaseColorVariant | null | undefined => {
return mapMessageTypeToBannerVariant(type);
};
return {
nocMessages,
hideMessage,
getBannerVariant,
};
},
});
</script>
<style scoped></style>
...@@ -14,6 +14,7 @@ import usePidStore from "@/modules/pid/store"; ...@@ -14,6 +14,7 @@ import usePidStore from "@/modules/pid/store";
import useProjectStore from "@/modules/project/store"; import useProjectStore from "@/modules/project/store";
import useResourceStore from "@/modules/resource/store"; import useResourceStore from "@/modules/resource/store";
import useSearchStore from "@/modules/search/store"; import useSearchStore from "@/modules/search/store";
import useSystemStatusStore from "@/store/systemStatus";
import useUserStore from "@/modules/user/store"; import useUserStore from "@/modules/user/store";
import { loadingCounterEventHandler } from "@/plugins/loadingCounter"; import { loadingCounterEventHandler } from "@/plugins/loadingCounter";
...@@ -29,6 +30,7 @@ export default defineComponent({ ...@@ -29,6 +30,7 @@ export default defineComponent({
loadingCounterEventHandler(useProjectStore); loadingCounterEventHandler(useProjectStore);
loadingCounterEventHandler(useResourceStore); loadingCounterEventHandler(useResourceStore);
loadingCounterEventHandler(useSearchStore); loadingCounterEventHandler(useSearchStore);
loadingCounterEventHandler(useSystemStatusStore);
loadingCounterEventHandler(useUserStore); loadingCounterEventHandler(useUserStore);
return { mainStore }; return { mainStore };
......
import { type MainState } from "@/store/types"; import type { MainState, Theme } from "@/store/types";
import { useLocalStorage } from "@vueuse/core"; import { useLocalStorage } from "@vueuse/core";
import { v4 as uuidv4 } from "uuid"; import { v4 as uuidv4 } from "uuid";
import * as jose from "jose"; import * as jose from "jose";
...@@ -28,14 +28,7 @@ export const getTestMainState: () => Promise<MainState> = async () => { ...@@ -28,14 +28,7 @@ export const getTestMainState: () => Promise<MainState> = async () => {
counter: 0, counter: 0,
}, },
locale: useLocalStorage("coscine.locale", "en"), locale: useLocalStorage("coscine.locale", "en"),
banner: { theme: useLocalStorage<Theme>("coscine.theme", "light"),
maintenance: {},
maintenanceVisibility: useLocalStorage(
"coscine.banner.maintenanceVisibility",
"",
),
dateString: useLocalStorage("coscine.banner.dateString", ""),
},
}, },
sidebarActive: useLocalStorage("coscine.sidebar.active", true), sidebarActive: useLocalStorage("coscine.sidebar.active", true),
}; };
......
import moment from "moment";
import { SystemStatusState } from "@/store/types";
import type { MessageDto } from "@coscine/api-client/dist/types/Coscine.Api";
export const getNocMessagesResponse: MessageDto[] = [
{
id: "noc-5555",
href: "https://example.com/noc-5555",
type: "Disturbance",
startDate: moment().subtract(1, "days").toISOString(), // yesterday
endDate: moment().add(1, "days").toISOString(), // tomorrow
},
{
id: "noc-5572",
href: "https://example.com/noc-5572",
type: "Maintenance",
startDate: moment().subtract(1, "days").toISOString(), // yesterday
endDate: moment().add(5, "hours").toISOString(), // in 5 hours
},
];
export const getInternalMessagesResponse: MessageDto[] = [
{
id: "int-6551c5a7ba1a3870450bb960f93ba912f175bc2a31992bc776413c4cb542f5b3",
body: {
de: "Das ist eine Testnachricht",
en: "This is a test message",
},
type: "Information",
startDate: moment().subtract(1, "days").toISOString(), // yesterday
endDate: moment().add(1, "days").toISOString(), // tomorrow
},
];
export const testSystemStatusState: SystemStatusState = {
banner: {
noc: {
messages: getNocMessagesResponse,
lastFetched: new Date(),
},
internal: {
messages: getInternalMessagesResponse,
lastFetched: new Date(),
},
hidden: useLocalStorage("coscine.system.status.banner.hidden", []),
},
};
...@@ -274,23 +274,30 @@ export default { ...@@ -274,23 +274,30 @@ export default {
}, },
banner: { banner: {
maintenance: { noc: {
type: { text: {
intervention: "Eingriff", Disturbance:
disturbance: "Störung", "Derzeit gibt es ein Problem, das Ihre Nutzung beeinträchtigen könnte. Unser Team arbeitet daran. Weitere Informationen finden Sie im {nocPortal}.",
partialDisturbance: "Teilstörung", PartialDisturbance:
interruption: "Unterbrechung", "Derzeit gibt es ein Problem, das Ihre Nutzung beeinträchtigen könnte. Unser Team arbeitet daran. Weitere Informationen finden Sie im {nocPortal}.",
limitedOperation: "Eingeschränkt betriebsfähig", Interruption:
maintenance: "Wartung", "Derzeit gibt es ein Problem, das Ihre Nutzung beeinträchtigen könnte. Unser Team arbeitet daran. Weitere Informationen finden Sie im {nocPortal}.",
partialMaintenance: "Teilwartung", Maintenance:
info: "Information", "Geplante Wartungsarbeiten finden derzeit statt, um unseren Service zu verbessern. Wir bemühen uns, Störungen zu minimieren. Vielen Dank für Ihr Verständnis. Weitere Details finden Sie im {nocPortal}.",
}, PartialMaintenance:
notificationDefaultText: "Geplante Wartungsarbeiten finden derzeit statt, um unseren Service zu verbessern. Wir bemühen uns, Störungen zu minimieren. Vielen Dank für Ihr Verständnis. Weitere Details finden Sie im {nocPortal}.",
"Derzeit kann es unter Umständen zu Einschränkungen bei der Nutzung von Coscine kommen. ", LimitedOperation:
linkText: "Details finden Sie ", "Wir arbeiten derzeit mit eingeschränkter Funktionalität. Unser Team bemüht sich, den vollen Service so schnell wie möglich wiederherzustellen. Wir danken für Ihr Verständnis. Weitere Informationen finden Sie im {nocPortal}.",
moreInformation: "im Statusmeldungsportal", Change:
}, "Eine kürzliche Änderung wurde in unserem System vorgenommen. Wir erwarten, dass dies Ihre Erfahrung verbessert. Weitere Details finden Sie im {nocPortal}.",
separator: ": ", Hint: "Wir haben Neuigkeiten, die für Sie interessant sein könnten. Bitte nehmen Sie sich einen Moment Zeit, um sie zu lesen. Weitere Details finden Sie im {nocPortal}.",
Information:
"Wir haben Neuigkeiten, die für Sie interessant sein könnten. Bitte nehmen Sie sich einen Moment Zeit, um sie zu lesen. Weitere Details finden Sie im {nocPortal}.",
Warning:
"Es wurde eine Systemwarnung ausgegeben. Bitte überprüfen Sie die neuesten Informationen im {nocPortal}.",
},
nocPortal: "Statusmeldungsportal",
},
}, },
email: { email: {
......
...@@ -272,23 +272,30 @@ export default { ...@@ -272,23 +272,30 @@ export default {
}, },
banner: { banner: {
maintenance: { noc: {
type: { text: {
intervention: "Intervention", Disturbance:
disturbance: "Disturbance", "We are currently facing an issue that may affect your experience. Our team is on it. For more information, please visit the {nocPortal}.",
partialDisturbance: "Partial disturbance", PartialDisturbance:
interruption: "Interruption", "We are currently facing an issue that may affect your experience. Our team is on it. For more information, please visit the {nocPortal}.",
limitedOperation: "Limited operation", Interruption:
maintenance: "Maintenance", "We are currently facing an issue that may affect your experience. Our team is on it. For more information, please visit the {nocPortal}.",
partialMaintenance: "Partial maintenance", Maintenance:
info: "Info", "Scheduled maintenance is in progress to enhance our service. We're working to minimize any disruption. Thank you for your understanding. Visit the {nocPortal} for further details.",
}, PartialMaintenance:
notificationDefaultText: "Scheduled maintenance is in progress to enhance our service. We're working to minimize any disruption. Thank you for your understanding. Visit the {nocPortal} for further details.",
"Currently, you may experience restrictions when using Coscine. ", LimitedOperation:
linkText: "Details can be found ", "We're currently operating with limited functionality. Our team is working to restore full service as soon as possible. We appreciate your understanding. For more details, visit the {nocPortal}.",
moreInformation: "on the Status Updates Website", Change:
}, "A recent change has been applied to our system. We expect this to enhance your experience. For more details, please visit the {nocPortal}.",
separator: ": ", Hint: "We have some news that may interest you. Please take a moment to read it. Further details can be found on the {nocPortal}.",
Information:
"We have some news that may interest you. Please take a moment to read it. Further details can be found on the {nocPortal}.",
Warning:
"A system warning has been issued. Please review the latest information on the {nocPortal}.",
},
nocPortal: "Status Updates Website",
},
}, },
email: { email: {
......
...@@ -31,11 +31,10 @@ describe("Login Store", () => { ...@@ -31,11 +31,10 @@ describe("Login Store", () => {
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
); );
localStorage.setItem("coscine.locale", "en"); localStorage.setItem("coscine.locale", "en");
localStorage.setItem("coscine.banner.maintenanceVisibility", "");
localStorage.setItem("coscine.login.storedData", ""); localStorage.setItem("coscine.login.storedData", "");
localStorage.setItem("coscine.sidebar.active", "true"); localStorage.setItem("coscine.sidebar.active", "true");
localStorage.setItem("coscine.banner.dateString", ""); localStorage.setItem("coscine.system.status.banner.hidden", "[]");
expect(localStorage.length).toBe(7); expect(localStorage.length).toBe(6);
loginStore.logout(); loginStore.logout();
......
...@@ -346,7 +346,7 @@ export default { ...@@ -346,7 +346,7 @@ export default {
dotCoscine: ".coscine", dotCoscine: ".coscine",
localMetadataCopyLabel: "Lokale Metadatenkopie", localMetadataCopyLabel: "Lokale Metadatenkopie",
localMetadataCopyHint: localMetadataCopyHint:
"Die Aktivierung lokaler Metadatenkopien kann sich auf Ihre genutzte S3-Speicherquota auswirken.", "Die Aktivierung lokaler Metadatenkopien wird zusätzliche Dateien in Ihren genutzten Ressourcen speichern.",
localMetadataCopyPopover: localMetadataCopyPopover:
"Für weitere Informationen zur lokalen Metadatenkopie siehe ", "Für weitere Informationen zur lokalen Metadatenkopie siehe ",
localMetadataCopyPopoverUrl: localMetadataCopyPopoverUrl:
...@@ -354,7 +354,7 @@ export default { ...@@ -354,7 +354,7 @@ export default {
enableLocalMetadataCopy: { enableLocalMetadataCopy: {
modal: { modal: {
title: "Lokale Metadatenkopie aktivieren", title: "Lokale Metadatenkopie aktivieren",
body: "Wenn Sie sich für lokale Kopien Ihrer Metadaten entscheiden, wird bei jedem Dateiupload eine Kopie in dem dafür vorgesehenen S3-Bucket unter dem Ordner {dotCoscineFolder} gespeichert. Obwohl diese Funktion Ihre Datenverwaltungsfähigkeiten erweitert, ist es wichtig zu beachten, dass diese Kopien einen Teil Ihres verfügbaren S3-Speicherplatzes verbrauchen werden. Der zusätzlich genutzte Speicherplatz ist in der Regel minimal, kann jedoch mit dem Volumen der verwalteten Dateien und Daten anwachsen. {br}{br}Die Aktivierung dieser Funktion gilt nur für zukünftige Metadaten-Uploads. Bereits vorhandene Metadaten vor der Aktivierung von Metadatenkopien bleiben sicher und unverändert in unserem System gespeichert, werden jedoch nicht in Ihren S3-Bucket kopiert. Dies stellt sicher, dass Ihre aktuelle Datenverwaltung und Speicherzuweisung nicht rückwirkend beeinflusst werden. Alle Ihre Metadaten werden weiterhin sicher auf unseren Servern gespeichert. Der Ordner {dotCoscineFolder} wird von der Nutzendenoberfläche nicht angezeigt. {br}{br}Das Deaktivieren der Funktion führt nicht zur Löschung bestehender Kopien.", body: "Wenn Sie sich für lokale Kopien Ihrer Metadaten entscheiden, wird bei jedem Dateiupload eine Kopie in dem dafür vorgesehenen S3-Bucket unter dem Ordner {dotCoscineFolder} gespeichert. Obwohl diese Funktion Ihre Datenverwaltungsfähigkeiten erweitert, ist es wichtig zu beachten, dass diese Kopien einen Teil Ihres verfügbaren Speicherplatzes verbrauchen werden. Der zusätzlich genutzte Speicherplatz ist in der Regel minimal, kann jedoch mit dem Volumen der verwalteten Dateien und Daten anwachsen. {br}{br}Die Aktivierung dieser Funktion gilt nur für zukünftige Metadaten-Uploads. Bereits vorhandene Metadaten vor der Aktivierung von Metadatenkopien bleiben sicher und unverändert in unserem System gespeichert, werden jedoch nicht in Ihren S3-Bucket kopiert. Dies stellt sicher, dass Ihre aktuelle Datenverwaltung und Speicherzuweisung nicht rückwirkend beeinflusst werden. Alle Ihre Metadaten werden weiterhin sicher auf unseren Servern gespeichert. Der Ordner {dotCoscineFolder} wird von der Nutzendenoberfläche nicht angezeigt. {br}{br}Das Deaktivieren der Funktion führt nicht zur Löschung bestehender Kopien.",
}, },
toast: { toast: {
title: "Lokale Metadatenkopie aktiviert", title: "Lokale Metadatenkopie aktiviert",
......
...@@ -32,11 +32,11 @@ export default { ...@@ -32,11 +32,11 @@ export default {
"No connection with the resource is possible. Please check the provided data.", "No connection with the resource is possible. Please check the provided data.",
}, },
disclaimerAPText1: disclaimerAPText1:
"In Step 2, you will be prompted to choose an Metadata Profile that you want to use for the chosen resource type. In case you don't know yet which Metadata Profile you would like to choose, you can take a look at the already existing ones here: ", "In Step 2, you will be prompted to choose a Metadata Profile that you want to use for the chosen resource type. In case you don't know yet which Metadata Profile you would like to choose, you can take a look at the already existing ones here: ",
disclaimerAPAimsUrl: disclaimerAPAimsUrl:
"https://coscine.rwth-aachen.de/coscine/apps/aimsfrontend/", "https://coscine.rwth-aachen.de/coscine/apps/aimsfrontend/",
disclaimerAPText2: disclaimerAPText2:
"To do this, simply select one from the list on the left and display it in metadata format for a better overview. In case you want to create a new Metadata profile because none of the already existing ones fit your data, follow the link above and see the description in the documentation as well for help: ", "To do this, simply select one from the list on the left and display it in metadata format for a better overview. In case you want to create a new Metadata Profile because none of the already existing ones fit your data, follow the link above and see the description in the documentation as well for help: ",
disclaimerAPHelpUrl: disclaimerAPHelpUrl:
"https://docs.coscine.de/en/metadata/generator/about/", "https://docs.coscine.de/en/metadata/generator/about/",
}, },
...@@ -337,7 +337,7 @@ export default { ...@@ -337,7 +337,7 @@ export default {
dotCoscine: ".coscine", dotCoscine: ".coscine",
localMetadataCopyLabel: "Local Metadata Copy", localMetadataCopyLabel: "Local Metadata Copy",
localMetadataCopyHint: localMetadataCopyHint:
"Activating local metadata copies may impact your S3 storage quota.", "Activating local metadata copies will store additional files within your used resources.",
localMetadataCopyPopover: localMetadataCopyPopover:
"For more information on local metadata copies see", "For more information on local metadata copies see",
localMetadataCopyPopoverUrl: localMetadataCopyPopoverUrl:
...@@ -345,7 +345,7 @@ export default { ...@@ -345,7 +345,7 @@ export default {
enableLocalMetadataCopy: { enableLocalMetadataCopy: {
modal: { modal: {
title: "Enable local metadata copy", title: "Enable local metadata copy",
body: "By opting in for local copies of your metadata, a copy will be saved in your designated S3 bucket under the folder {dotCoscineFolder} on file upload. While this feature enriches your data management capabilities, it's important to note that these copies will consume part of your available S3 storage quota. The additional storage used will generally be minimal, but it can grow with the volume of files and data you manage. {br}{br}Enabling this feature will only apply to future metadata uploads. Any existing metadata prior to enabling metadata copies will remain secure and unchanged in our system, but will not be copied to your S3 bucket. This ensures your current data management and storage allocation are not retroactively affected. All your metadata continues to be securely stored on our servers. The {dotCoscineFolder} folder will not be displayed by the user interface. {br}{br}Deactivating this feature does not delete existing copies. ", body: "By opting in for local copies of your metadata, a copy will be saved in your designated S3 bucket under the folder {dotCoscineFolder} on file upload. While this feature enriches your data management capabilities, it's important to note that these copies will consume part of your available storage. The additional storage used will generally be minimal, but it can grow with the volume of files and data you manage. {br}{br}Enabling this feature will only apply to future metadata uploads. Any existing metadata prior to enabling metadata copies will remain secure and unchanged in our system, but will not be copied to your S3 bucket. This ensures your current data management and storage allocation are not retroactively affected. All your metadata continues to be securely stored on our servers. The {dotCoscineFolder} folder will not be displayed by the user interface. {br}{br}Deactivating this feature does not delete existing copies. ",
}, },
toast: { toast: {
title: "Local metadata copy enabled", title: "Local metadata copy enabled",
......
...@@ -62,9 +62,10 @@ VueRouter.prototype.push = async function (location: RouteLocationRaw) { ...@@ -62,9 +62,10 @@ VueRouter.prototype.push = async function (location: RouteLocationRaw) {
// Import the relevant stores // Import the relevant stores
import useMainStore from "@/store/index"; import useMainStore from "@/store/index";
import useUserStore from "@/modules/user/store"; import useUserStore from "@/modules/user/store";
import useLoginStore from "@/modules/login/store";
import useSystemStatusStore from "@/store/systemStatus";
import i18n, { def } from "@/plugins/vue-i18n"; import i18n, { def } from "@/plugins/vue-i18n";
import type { DefaultLocaleMessageSchema, LocaleMessages } from "vue-i18n"; import type { DefaultLocaleMessageSchema, LocaleMessages } from "vue-i18n";
import useLoginStore from "@/modules/login/store";
import type { ProjectDto } from "@coscine/api-client/dist/types/Coscine.Api"; import type { ProjectDto } from "@coscine/api-client/dist/types/Coscine.Api";
router.beforeEach((to, _, next) => { router.beforeEach((to, _, next) => {
...@@ -83,6 +84,7 @@ router.beforeEach((to, _, next) => { ...@@ -83,6 +84,7 @@ router.beforeEach((to, _, next) => {
} }
// Define the relevant stores // Define the relevant stores
const mainStore = useMainStore(); const mainStore = useMainStore();
const systemStatusStore = useSystemStatusStore();
const loginStore = useLoginStore(); const loginStore = useLoginStore();
const userStore = useUserStore(); const userStore = useUserStore();
...@@ -90,8 +92,22 @@ router.beforeEach((to, _, next) => { ...@@ -90,8 +92,22 @@ router.beforeEach((to, _, next) => {
mainStore.setAccessTokenFromRoute(router, to); mainStore.setAccessTokenFromRoute(router, to);
// Handle contact change token from URL // Handle contact change token from URL
userStore.confirmUserEmail(to); userStore.confirmUserEmail(to);
// Collect ongoing Maintenance information // Collect current NOC information
mainStore.getMaintenance(); if (
systemStatusStore.shouldFetchMessages(
systemStatusStore.banner.noc.lastFetched,
)
) {
systemStatusStore.retrieveCurrentNocMessages();
}
// Collect current internal information
if (
systemStatusStore.shouldFetchMessages(
systemStatusStore.banner.internal.lastFetched,
)
) {
systemStatusStore.retrieveCurrentInternalMessages();
}
// Retrieve session status // Retrieve session status
loginStore.retrieveSessionStatus(); loginStore.retrieveSessionStatus();
......
...@@ -3,14 +3,12 @@ import { defineStore } from "pinia"; ...@@ -3,14 +3,12 @@ import { defineStore } from "pinia";
import type { Router, RouteLocationNormalized } from "vue-router"; import type { Router, RouteLocationNormalized } from "vue-router";
import type { Theme, MainState } from "./types"; import type { Theme, MainState } from "./types";
import { v4 as uuidv4 } from "uuid"; import { v4 as uuidv4 } from "uuid";
import * as jose from "jose";
import moment from "moment";
import { removeQueryParameterFromUrl } from "@/router"; import { removeQueryParameterFromUrl } from "@/router";
import { MaintenanceApi } from "@coscine/api-client";
import useLoginStore from "@/modules/login/store"; import useLoginStore from "@/modules/login/store";
import moment from "moment";
import * as jose from "jose";
import type { MaintenanceDto } from "@coscine/api-client/dist/types/Coscine.Api";
/* /*
Store variable name is "this.<id>Store" Store variable name is "this.<id>Store"
...@@ -36,14 +34,6 @@ export const useMainStore = defineStore({ ...@@ -36,14 +34,6 @@ export const useMainStore = defineStore({
counter: 0, counter: 0,
}, },
locale: useLocalStorage("coscine.locale", "en"), locale: useLocalStorage("coscine.locale", "en"),
banner: {
maintenance: {},
maintenanceVisibility: useLocalStorage(
"coscine.banner.maintenanceVisibility",
"",
),
dateString: useLocalStorage("coscine.banner.dateString", ""),
},
theme: useLocalStorage<Theme>("coscine.theme", "light"), theme: useLocalStorage<Theme>("coscine.theme", "light"),
}, },
sidebarActive: useLocalStorage("coscine.sidebar.active", true), sidebarActive: useLocalStorage("coscine.sidebar.active", true),
...@@ -112,44 +102,6 @@ export const useMainStore = defineStore({ ...@@ -112,44 +102,6 @@ export const useMainStore = defineStore({
this.coscine.loading.counter++; this.coscine.loading.counter++;
}, },
async getMaintenance() {
const apiResponse = await MaintenanceApi.getCurrentMaintenances();
// TODO: Make it work with multiple maintenance messages
const maintenance = apiResponse.data.data?.at(0); // Take the first maintenance
if (maintenance?.startsDate) {
const now = new Date(Date.now());
const startDate = new Date(maintenance.startsDate);
const endDate = maintenance.endsDate;
if (startDate <= now && (!endDate || new Date(endDate) >= now)) {
this.coscine.banner.dateString =
maintenance.startsDate + maintenance.endsDate;
this.coscine.banner.maintenance = maintenance;
}
}
// Inject banner
if (Object.keys(this.coscine.banner.maintenance).length === 0) {
// Javascript sets months to 0
const startsDate = new Date(2024, 3, 22, 0, 0, 0).toUTCString();
const endsDate = new Date(2024, 4, 7, 0, 0, 0).toUTCString();
const injectedBanner = {
body: `Your opinion matters! The survey on the use of Coscine will take place from April 22 to May 6, 2024. Take the opportunity and help us to optimize the platform. The survey takes approximately 15 minutes. <a href="https://s2survey.net/coscine_2024/" target="_blank">https://s2survey.net/coscine_2024/</a>
//
Ihre Meinung ist uns wichtig! Vom 22. April bis 6. Mai 2024 findet die Umfrage zur Nutzung von Coscine statt. Nutzen Sie die Chance und tragen Sie aktiv dazu bei, die Plattform zu optimieren. Die Umfrage dauert ca. 15 Minuten. <a href="https://s2survey.net/coscine_2024/" target="_blank">https://s2survey.net/coscine_2024/</a>`,
displayName: "User Survey",
startsDate: startsDate,
endsDate: endsDate,
type: "Info",
} satisfies MaintenanceDto;
const now = new Date(Date.now());
const startDate = new Date(startsDate);
if (startDate <= now && (!endsDate || new Date(endsDate) >= now)) {
this.coscine.banner.dateString =
injectedBanner.startsDate + injectedBanner.endsDate;
this.coscine.banner.maintenance = injectedBanner;
}
}
},
getCurrentTokenExpirationDuration(): number { getCurrentTokenExpirationDuration(): number {
const loginStore = useLoginStore(); const loginStore = useLoginStore();
......