diff --git a/src/components/banner/Maintenance.vue b/src/components/banner/Maintenance.vue index 17f95b4d6e36cab4861a9724645483aae1969d55..aa1ec34dd49fbe4dca0f5395a408710ac05fd16f 100644 --- a/src/components/banner/Maintenance.vue +++ b/src/components/banner/Maintenance.vue @@ -6,15 +6,18 @@ variant="warning" @dismissed="saveVisibility" > - <i18n :path="messagePath" tag="p"> - <template #link> - <a - :href="maintenance.url ? maintenance.url : undefined" - target="_blank" - >{{ $t("banner.maintenance.moreInformation") }}</a - > - </template> - </i18n> + <p> + <span class="font-weight-bold"> + {{ `${messageType}${$t("banner.separator")}`}} + </span> + {{ messageBody }} + <span v-if="maintenance.url"> + {{ $t("banner.maintenance.linkText") }} + <a :href="maintenance.url" target="_blank" + >{{ $t("banner.maintenance.moreInformation") }} + </a> + </span> + </p> </b-alert> </template> @@ -30,12 +33,6 @@ export default defineComponent({ return { mainStore }; }, - data() { - return { - messagePath: "", - }; - }, - computed: { maintenance(): MaintenanceReturnObject { return this.mainStore.coscine.banner.maintenance; @@ -48,41 +45,69 @@ export default defineComponent({ ); }, show(): boolean { - return this.visibility && this.maintenance.type ? true : false; + return this.visibility && this.maintenance.type && this.maintenance.body + ? true + : false; }, - }, - - watch: { - maintenance() { - this.messagePath = this.createNotificationText(); + locale(): string { + return this.$root.$i18n.locale; }, - }, + messageBody(): string | undefined { + const notificationText = this.createNotificationText()?.trim(); + // empty texts + if (!notificationText) { + return this.$t("banner.maintenance.notificationDefaultText").toString(); + } - methods: { - saveVisibility() { - this.mainStore.coscine.banner.maintenanceVisibility = - this.mainStore.coscine.banner.dateString; + const languageSpecificNotificationTexts = notificationText.split("//"); + if (languageSpecificNotificationTexts?.length === 1) { + return languageSpecificNotificationTexts[0]; + } + + return this.$root.$i18n.locale === "de" + ? languageSpecificNotificationTexts[1] + : languageSpecificNotificationTexts[0]; }, - createNotificationText(): string { - if (this.maintenance && this.maintenance.type) { + messageType(): string { + if (this.maintenance.type) { switch (this.maintenance.type) { case "Eingriff": - return "banner.maintenance.notificationDeployment"; + return this.$t("banner.maintenance.type.intervention").toString(); case "Störung": - return "banner.maintenance.notificationMaintenance"; + return this.$t("banner.maintenance.type.disturbance").toString(); case "Teilstörung": - return "banner.maintenance.notificationPartiaMulfunction"; + return this.$t( + "banner.maintenance.type.partialDisturbance" + ).toString(); case "Unterbrechung": - return "banner.maintenance.notificationInteruption"; + return this.$t("banner.maintenance.type.interruption").toString(); case "eingeschränkt betriebsfähig": - return "banner.maintenance.notificationLimitedOperability"; + return this.$t( + "banner.maintenance.type.limitedOperation" + ).toString(); case "Wartung": - return "banner.maintenance.notificationMaintenance"; + return this.$t("banner.maintenance.type.maintenance").toString(); case "Teilwartung": - return "banner.maintenance.notificationPartialMaintenance"; + return this.$t( + "banner.maintenance.type.partialMaintenance" + ).toString(); default: - return "banner.maintenance.notificationDefaultText"; + 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 ""; } diff --git a/src/components/elements/SidebarMenu.vue b/src/components/elements/SidebarMenu.vue index e29384935687dca5e5d783e12c9a98b1b1eff65f..06d9c1a5097ef42be4cd10e1d4fd003517d3d634 100644 --- a/src/components/elements/SidebarMenu.vue +++ b/src/components/elements/SidebarMenu.vue @@ -51,7 +51,7 @@ import type { Vue.use(VueSidebarMenu); -// TODO: Waiting for role implementation (is owner for showing e.g. settings) +// TODO: Waiting for role implementation (is owner for showing e.g. configuration & metadata) export default defineComponent({ setup() { @@ -208,15 +208,15 @@ export default defineComponent({ icon: "bi bi-gear", child: [ { - title: this.$t("sidebar.projectSettings").toString(), + title: this.$t("sidebar.configurationMetadata").toString(), href: { - name: "project-settings", + name: "project-config-metadata", params: { slug: this.project.slug }, }, icon: "bi bi-pencil", }, { - title: this.$t("sidebar.manageUsers").toString(), + title: this.$t("sidebar.users").toString(), href: { name: "project-members", params: { slug: this.project.slug }, @@ -224,7 +224,7 @@ export default defineComponent({ icon: "bi bi-people", }, { - title: this.$t("sidebar.manageQuota").toString(), + title: this.$t("sidebar.quota").toString(), href: { name: "project-quota", params: { slug: this.project.slug }, diff --git a/src/data/mockup/testResource.ts b/src/data/mockup/testResource.ts index fd1910e5618dd7bfd4c5082dc4b7212f29de7007..b3a86e60e56459a3bfd703a94e5ed435f59c305b 100644 --- a/src/data/mockup/testResource.ts +++ b/src/data/mockup/testResource.ts @@ -64,13 +64,13 @@ export const getTestResource: () => Promise<VisitedResourceObject> = rawApplicationProfile: ap, resourceName: "TestResource", resourceTypeOption: { id: "5234" }, - storedColumns: "", + storedColumns: null, type: { displayName: testResourceType.displayName, id: testResourceType.id, }, usageRights: "", - usedQuota: null, + quota: null, visibility: { id: "1234", displayName: "Project Members" }, }; return resourceObject; @@ -82,6 +82,7 @@ export const getTestResourceState: () => Promise<ResourceState> = async () => { allResources: [testResource], classes: {}, currentId: "eeb8d803-46a1-49ba-a47c-81cd4f49cd65", + enabledResourceTypes: [testResourceType], resourceTypes: [testResourceType], visitedResources: { "eeb8d803-46a1-49ba-a47c-81cd4f49cd65": testResource, diff --git a/src/i18n/de.ts b/src/i18n/de.ts index bd3956eef9c867c8d347ee05d5848428adc0e94c..a1dea1955ee5e9642dd9a5b7e8ff300164d2ef0b 100644 --- a/src/i18n/de.ts +++ b/src/i18n/de.ts @@ -39,9 +39,9 @@ export default { archived: "Archiviert", - projectSettings: "Projekteinstellungen", - manageUsers: "Benutzerverwaltung", - manageQuota: "Quota verwalten", + configurationMetadata: "Konfiguration & Metadaten", + users: "Mitglieder", + quota: "Quota", } as VueI18n.LocaleMessageObject, buttons: { @@ -153,9 +153,9 @@ export default { project: { page: "{projectName}", create: "Projekt erstellen", - settings: "Projekt bearbeiten", - quota: "Quota Management", - members: "Benutzerverwaltung", + configMetadata: "Projektkonfiguration & -metadaten", + quota: "Projektquota", + members: "Projektmitglieder", }, resource: { page: "{resourceName}", @@ -252,24 +252,21 @@ export default { Verbesserungsvorschläge können Sie gerne an [{link}] senden.`, }, maintenance: { + type: { + intervention: "Eingriff", + disturbance: "Störung", + partialDisturbance: "Teilstörung", + interruption: "Unterbrechung", + limitedOperation: "Eingeschränkt betriebsfähig", + maintenance: "Wartung", + partialMaintenance: "Teilwartung", + }, notificationDefaultText: - "Derzeit kann es unter Umständen zu Einschränkungen bei der Nutzung von Coscine kommen. Details finden Sie unter [{link}].", - notificationDeployment: - "Derzeit kann es unter Umständen zu Einschränkungen bei der Nutzung von Coscine kommen.", - notificationMalfunction: - "Durch eine Störung ist Coscine derzeit nicht verfügbar. Details finden Sie unter [{link}].", - notificationPartiaMulfunction: - "Durch eine Teilstörung ist Coscine derzeit nicht verfügbar. Details finden Sie unter [{link}].", - notificationInteruption: - "Durch die Unterbrechung eines Dienstes ist Coscine derzeit nicht verfügbar. Details finden Sie unter [{link}].", - notificationLimitedOperability: - "Coscine ist derzeit nur eingeschränkt verfügbar. Details finden Sie unter [{link}].", - notificationMaintenance: - "Coscine wird derzeit gewartet. Es können Einschränkungen bei der Nutzung auftreten. Details finden Sie unter [{link}].", - notificationPartialMaintenance: - "Coscine wird derzeit gewartet. Es können Einschränkungen bei der Nutzung auftreten. Details finden Sie unter [{link}].", - moreInformation: "weitere Informationen", + "Derzeit kann es unter Umständen zu Einschränkungen bei der Nutzung von Coscine kommen. ", + linkText: "Details finden Sie ", + moreInformation: "hier", }, + separator: ": ", }, email: { diff --git a/src/i18n/en.ts b/src/i18n/en.ts index b3411e70967bf78751258b002fe04e4d59858a15..5df82688da19efd7b33df1a119e6171145e432bb 100644 --- a/src/i18n/en.ts +++ b/src/i18n/en.ts @@ -37,9 +37,9 @@ export default { subProject: "Sub-Project | Sub-Projects", settings: "Settings", - projectSettings: "Project Settings", - manageUsers: "Manage Users", - manageQuota: "Manage Quota", + configurationMetadata: "Configuration & Metadata", + users: "Members", + quota: "Quota", } as VueI18n.LocaleMessageObject, buttons: { @@ -151,9 +151,9 @@ export default { project: { page: "{projectName}", create: "Create Project", - settings: "Project Settings", - quota: "Quota Management", - members: "User Management", + configMetadata: "Project Configuration & Metadata", + quota: "Project Quota", + members: "Project Members", }, resource: { page: "{resourceName}", @@ -249,24 +249,21 @@ export default { pilot program. If you have feedback you are welcome to send it to [{link}].`, }, maintenance: { + type: { + intervention: "Intervention", + disturbance: "Disturbance", + partialDisturbance: "Partial disturbance", + interruption: "Interruption", + limitedOperation: "Limited operation", + maintenance: "Maintenance", + partialMaintenance: "Partial maintenance", + }, notificationDefaultText: - "Currently, you may experience restrictions when using Coscine. Details can be found at [{link}].", - notificationDeployment: - "Currently, you may experience restrictions when using Coscine.", - notificationMalfunction: - "Due to a malfunction Coscine is currently unavailable. Details can be found at [{link}].", - notificationPartiaMulfunction: - "Due to a partial malfunction Coscine is currently unavailable. Details can be found at [{link}].", - notificationInteruption: - "Due to a interuption of a service Coscine is currently unavailable. Details can be found at [{link}].", - notificationLimitedOperability: - "Coscine is currently offering limited operability. Details can be found at [{link}].", - notificationMaintenance: - "Coscine is currently undergoing maintenance. You may experience restrictions in its use. Details can be found at [{link}].", - notificationPartialMaintenance: - "Coscine is currently undergoing maintenance. You may experience restrictions in its use. Details can be found at [{link}].", - moreInformation: "more information", + "Currently, you may experience restrictions when using Coscine. ", + linkText: "Details can be found ", + moreInformation: "here", }, + separator: ": ", }, email: { diff --git a/src/modules/project/i18n/de.ts b/src/modules/project/i18n/de.ts index f8b5b3f79c5d783b53e1d37a0ec9ed6b579ae624..88d752c11d0996c418bebe33663d263c639c0877 100644 --- a/src/modules/project/i18n/de.ts +++ b/src/modules/project/i18n/de.ts @@ -27,7 +27,7 @@ export default { // Members.vue members: { - title: "Benutzerverwaltung", + title: "Projektmitglieder", membersTabTitle: "Projektmitglieder", externalUsersTabTitle: "Eingeladene Benutzer", pendingStatus: "Wartet", @@ -115,7 +115,7 @@ export default { // Quota.vue quota: { - headline: "Quota Management", + headline: "Projektquota", resources: "Ressourcen", rangeText: "{allocated} GB zugeteilt", gb: "{number} GB", @@ -170,9 +170,9 @@ export default { "Beim Ändern der Quota ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut. Wenn der Fehler weiter auftritt, wenden Sie sich bitte an Ihre Organization.", }, - // Settings.vue - settings: { - // page title is fetched from `sidebar.projectSettings` + // ConfigurationMetadata.vue + configMetadata: { + headline: "Projektkonfiguration & -metadaten", modal: { deleteModalHeadline: "Projekt wirklich entfernen?", @@ -210,9 +210,6 @@ export default { projectSlugLabel: "@:(form.project.projectSlug)@:(form.project.labelSymbol)", - projectId: "Projekt Id", - projectIdLabel: "@:(form.project.projectId)@:(form.project.labelSymbol)", - projectPersistentId: "Persistent Identifier (PID)", projectPersistentIdLabel: "@:(form.project.projectPersistentId)@:(form.project.labelSymbol)", diff --git a/src/modules/project/i18n/en.ts b/src/modules/project/i18n/en.ts index 360800b192f17d4709abb54931ca64816efb3d48..3a6c7724b670537026857a5c2dc4a2b27599b704 100644 --- a/src/modules/project/i18n/en.ts +++ b/src/modules/project/i18n/en.ts @@ -25,7 +25,7 @@ export default { // Members.vue members: { - title: "User Management", + title: "Project Members", membersTabTitle: "Project Members", externalUsersTabTitle: "Invited Users", pendingStatus: "Pending", @@ -110,7 +110,7 @@ export default { // Quota.vue quota: { - headline: "Quota Management", + headline: "Project Quota", resources: "Resources", rangeText: "{allocated} GB allocated", gb: "{number} GB", @@ -166,9 +166,9 @@ export default { "An error occurred while extending the quota. Please try again. If the error persists, contact your organization.", }, - // Settings.vue - settings: { - // page title is fetched from `sidebar.projectSettings` + // ConfigurationMetadata.vue + configMetadata: { + headline: "Project Configuration & Metadata", modal: { deleteModalHeadline: "Do you really want to delete this project?", @@ -205,9 +205,6 @@ export default { projectSlugLabel: "@:(form.project.projectSlug)@:(form.project.labelSymbol)", - projectId: "Project Id", - projectIdLabel: "@:(form.project.projectId)@:(form.project.labelSymbol)", - projectPersistentId: "Persistent Identifier (PID)", projectPersistentIdLabel: "@:(form.project.projectPersistentId)@:(form.project.labelSymbol)", diff --git a/src/modules/project/pages/Settings.spec.ts b/src/modules/project/pages/ConfigurationMetadata.spec.ts similarity index 91% rename from src/modules/project/pages/Settings.spec.ts rename to src/modules/project/pages/ConfigurationMetadata.spec.ts index 2291b04a5c56d84e38e542c63d0444d0b57a8bd2..fc7e70ef2896615dbf036eed06ba9da976ce9251 100644 --- a/src/modules/project/pages/Settings.spec.ts +++ b/src/modules/project/pages/ConfigurationMetadata.spec.ts @@ -16,7 +16,7 @@ import { PiniaVuePlugin } from "pinia"; /* Additional Dependencies */ /* Tested Component */ -import Settings from "./Settings.vue"; +import ConfigurationMetadata from "./ConfigurationMetadata.vue"; import type Vue from "vue"; /* Import of relevant mockup data */ @@ -27,7 +27,7 @@ import { testUserState } from "@/data/mockup/testUser"; const localVue = createLocalVue(); localVue.use(PiniaVuePlugin); -describe("Settings.vue", () => { +describe("ConfigurationMetadata.vue", () => { /* Describe Pre-initialization steps */ /* Description of the test */ @@ -35,7 +35,7 @@ describe("Settings.vue", () => { /* Test Pre-initialization steps */ /* Mount the Component */ - const wrapper = mount(Settings as unknown as typeof Vue, { + const wrapper = mount(ConfigurationMetadata as unknown as typeof Vue, { pinia: createTestingPinia({ createSpy: vitest.fn, initialState: { diff --git a/src/modules/project/pages/Settings.vue b/src/modules/project/pages/ConfigurationMetadata.vue similarity index 98% rename from src/modules/project/pages/Settings.vue rename to src/modules/project/pages/ConfigurationMetadata.vue index ef3c3b3b5f9e245cfce09582647da7ef15d776bf..553fff03719d0a08c81568885a625a335890d1ed 100644 --- a/src/modules/project/pages/Settings.vue +++ b/src/modules/project/pages/ConfigurationMetadata.vue @@ -1,6 +1,6 @@ <template> <div id="project"> - <CoscineHeadline :headline="$t('sidebar.projectSettings')" /> + <CoscineHeadline :headline="$t('page.configMetadata.headline')" /> <b-row> <div class="col-sm-2" /> <div class="col-sm-8"> @@ -18,8 +18,6 @@ :is-loading="isLoading" /> - <div class="h-divider" /> - <FormMetadata v-model="projectForm" :disabled="!isOwner" diff --git a/src/modules/project/pages/CreateProject.vue b/src/modules/project/pages/CreateProject.vue index 49a78d8e1d64020490eb9d73a1956d906ad86925..d4ca9e940427925f4bea96392ebd8dfd1222d233 100644 --- a/src/modules/project/pages/CreateProject.vue +++ b/src/modules/project/pages/CreateProject.vue @@ -23,8 +23,6 @@ @validation="formValidations.naming = $event" /><!-- TODO: Fix @validation assignment and typing --> - <div class="h-divider" /> - <FormMetadata v-model="projectForm" :is-loading="isLoading" diff --git a/src/modules/project/pages/components/FormMetadata.vue b/src/modules/project/pages/components/FormMetadata.vue index 44b9402b398ae0817f7c36c492e818f4f98e1cea..0f76e00a26653b6090d31055fffa4f16d631a4b3 100644 --- a/src/modules/project/pages/components/FormMetadata.vue +++ b/src/modules/project/pages/components/FormMetadata.vue @@ -1,8 +1,5 @@ <template> <div> - <!-- Project Metadata Section --> - <CoscineHeadline :headline="$t('form.project.activatedImportFromParent')" /> - <!-- Copy Metadata --> <CoscineFormGroup v-if="parentProject" diff --git a/src/modules/project/pages/components/FormReadonlyData.vue b/src/modules/project/pages/components/FormReadonlyData.vue index 73dc334360f22c61259c6c43dbda9f37012f30d0..193ef4caeb8b8af90eeb207bed29a4fe5b2a2389 100644 --- a/src/modules/project/pages/components/FormReadonlyData.vue +++ b/src/modules/project/pages/components/FormReadonlyData.vue @@ -15,21 +15,6 @@ /> </CoscineFormGroup> - <!-- Project Id --> - <CoscineFormGroup - label-for="Id" - :label="$t('form.project.projectIdLabel')" - :is-loading="isLoading" - type="input" - > - <b-form-input - id="ProjectId" - v-model="projectForm.id" - :readonly="readonly" - required - /> - </CoscineFormGroup> - <!-- Project Persistent Identifier --> <CoscineFormGroup label-for="ProjectPersistentId" diff --git a/src/modules/project/pages/components/modals/DeleteProjectModal.vue b/src/modules/project/pages/components/modals/DeleteProjectModal.vue index 8d5f320bf2ba57bf96a2da333ca22d18e6126903..30799c37a1c4a8174fd1982593e384424420c18c 100644 --- a/src/modules/project/pages/components/modals/DeleteProjectModal.vue +++ b/src/modules/project/pages/components/modals/DeleteProjectModal.vue @@ -32,7 +32,7 @@ @keyup.enter.prevent="clickDelete" ></b-form-input> <div class="invalid-tooltip"> - {{ $t("page.settings.modal.deleteModalHelp") }} + {{ $t("page.configMetadata.modal.deleteModalHelp") }} </div> </b-form-group> @@ -55,11 +55,11 @@ import { defineComponent, type PropType } from "vue"; export default defineComponent({ props: { descriptionKey: { - default: "page.settings.modal.deleteModalDescription", + default: "page.configMetadata.modal.deleteModalDescription", type: String, }, titleKey: { - default: "page.settings.modal.deleteModalHeadline", + default: "page.configMetadata.modal.deleteModalHeadline", type: String, }, open: { diff --git a/src/modules/project/routes.ts b/src/modules/project/routes.ts index 80dd7a2e22c25cb051d6708269657439a738edb2..60794525e7a07af072d8d2ee7805b8bba79d483c 100644 --- a/src/modules/project/routes.ts +++ b/src/modules/project/routes.ts @@ -7,7 +7,7 @@ const ListProjects = () => import("./pages/ListProjects.vue"); const ProjectPage = () => import("./pages/ProjectPage.vue"); const Quota = () => import("./pages/Quota.vue"); const Members = () => import("./pages/Members.vue"); -const Settings = () => import("./pages/Settings.vue"); +const ConfigurationMetadata = () => import("./pages/ConfigurationMetadata.vue"); import { ResourceRoutes } from "@/modules/resource/routes"; import { ProjectI18nMessages } from "@/modules/project/i18n/index"; @@ -68,12 +68,12 @@ export const ProjectRoutes: RouteConfig[] = [ }, }, { - path: "settings", - name: "project-settings", - component: Settings, + path: "config-metadata", + name: "project-config-metadata", + component: ConfigurationMetadata, meta: { requiresAuth: true, - breadCrumb: "project.settings", + breadCrumb: "project.configMetadata", }, }, { diff --git a/src/modules/project/store.ts b/src/modules/project/store.ts index c9be530180a846e2057321959cf4fffac9ac0353..c340289cd32e4ff6e699e22dd35b5422c26b7c0c 100644 --- a/src/modules/project/store.ts +++ b/src/modules/project/store.ts @@ -482,7 +482,17 @@ export const useProjectStore = defineStore({ const notificationStore = useNotificationStore(); try { const apiResponse = await LicenseApi.licenseIndex(); - this.licenses = apiResponse.data; + const sortedLicenses = apiResponse.data; + sortedLicenses.sort((a, b) => { + if (a.displayName && b.displayName) { + const valueA = a.displayName.toUpperCase(); + const valueB = b.displayName.toUpperCase(); + return valueA < valueB ? -1 : valueA > valueB ? 1 : 0; + } else { + return 0; + } + }); + this.licenses = sortedLicenses; } catch (error) { // Handle other Status Codes notificationStore.postApiErrorNotification(error as AxiosError); diff --git a/src/modules/resource/components/create-resource/resource-type/GitLab.vue b/src/modules/resource/components/create-resource/resource-type/GitLab.vue index 2009c8ba6dbda1e86afbc3534db25da73804a976..93c0b985619f394f2f445454f736e7fd541200f2 100644 --- a/src/modules/resource/components/create-resource/resource-type/GitLab.vue +++ b/src/modules/resource/components/create-resource/resource-type/GitLab.vue @@ -146,8 +146,7 @@ <template #hint> <span v-if=" - gitlabInformation.reference && - !gitlabInformation.reference.can_push + gitlabInformation.reference && !gitlabInformation.reference.can_push " class="text-danger" > diff --git a/src/modules/resource/components/resource-page/FilesView.spec.ts b/src/modules/resource/components/resource-page/FilesView.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..c1d1625139940d3b3fae1b242e42930e8b5cc608 --- /dev/null +++ b/src/modules/resource/components/resource-page/FilesView.spec.ts @@ -0,0 +1,88 @@ +/* Testing imports */ +import { createLocalVue, mount } from "@vue/test-utils"; +import { createTestingPinia } from "@pinia/testing"; + +/* Vue i18n */ +import i18n, { def } from "@/plugins/vue-i18n"; +import { ResourceI18nMessages } from "@/modules/resource/i18n/index"; +i18n.availableLocales.forEach((locale) => { + i18n.setLocaleMessage(locale, def[locale]); // default locale messages + i18n.mergeLocaleMessage(locale, ResourceI18nMessages[locale]); // append the locale messages for the component +}); + +/* Pinia */ +import { PiniaVuePlugin } from "pinia"; + +/* Tested Component */ +import FilesView from "./FilesView.vue"; + +import type Vue from "vue"; + +import { testUserState } from "@/data/mockup/testUser"; +import { getTestResourceState } from "@/data/mockup/testResource"; +import { testProjectState } from "@/data/mockup/testProject"; +import useResourceStore from "../../store"; +import { getMetadataResponse } from "@/data/mockup/responses/getMetadata"; + +function sleep(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +/* Create a local Vue instance */ +const localVue = createLocalVue(); +localVue.use(PiniaVuePlugin); + +describe("FilesView.vue", () => { + /* Checks for local storage setting of columns */ + test("testLocalStorageSetting", async () => { + const testingPinia = createTestingPinia({ + createSpy: vitest.fn, + initialState: { + project: testProjectState, + resource: await getTestResourceState(), + user: testUserState, + }, + }); + + const resourceStore = useResourceStore(testingPinia); + vi.mocked(resourceStore.getMetadata).mockReturnValue( + Promise.resolve(getMetadataResponse) + ); + vi.mocked(resourceStore.getClass).mockReturnValue( + Promise.resolve({ en: [], de: [] }) + ); + + const wrapper = mount(FilesView as unknown as typeof Vue, { + pinia: testingPinia, + i18n, + localVue, + }); + + await wrapper.vm.$nextTick(); + + // Wait for 1 second until everything is set up + await sleep(1000); + + expect(resourceStore.setStoredColumns).toBeCalledTimes(1); + + const selectButton = wrapper.find("#addColumnDropDown__BV_toggle_"); + await selectButton.trigger("click"); + + const checkBox = wrapper.find("#addColumnDropDown .custom-control-input"); + await checkBox.trigger("click"); + + // The previous clicks should have done that, workaround for now + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (wrapper.vm as any).columns[0].active = true; + + await wrapper.vm.$nextTick(); + + // Wait for 1 second until everything is set up + await sleep(1000); + + const additionalColumnHeader = wrapper.find(".additionalColumnHeader"); + expect(additionalColumnHeader.exists()).toBeTruthy(); + + expect(resourceStore.setStoredColumns).toBeCalledTimes(2); + }); +}); diff --git a/src/modules/resource/components/resource-page/FilesView.vue b/src/modules/resource/components/resource-page/FilesView.vue index b4ef0f72cfe3abbdd4f02cfe6d1b8b4fb26b8d61..78899862457e9a10ccec0bdbcf34da5afc9aced6 100644 --- a/src/modules/resource/components/resource-page/FilesView.vue +++ b/src/modules/resource/components/resource-page/FilesView.vue @@ -15,6 +15,8 @@ :locale="$i18n.locale" :filter="filter" :filter-included-fields="filterFields" + :sort-by.sync="sortBy" + :sort-desc.sync="sortDesc" selectable striped bordered @@ -151,8 +153,8 @@ import MetadataManagerUtil from "../../utils/MetadataManagerUtil"; import fileSaver from "file-saver"; import type { ResourceTypeInformation } from "@coscine/api-client/dist/types/Coscine.Api.Resources"; -import type { VisitedResourceObject } from "../../types"; -import type { BFormRow, BTable, BvTableField } from "bootstrap-vue"; +import type { CustomTableField, VisitedResourceObject } from "../../types"; +import type { BFormRow, BTable } from "bootstrap-vue"; import { FileUtil } from "../../utils/FileUtil"; import { v4 as uuidv4 } from "uuid"; @@ -161,18 +163,13 @@ import factory from "rdf-ext"; import type { Dataset, Literal } from "@rdfjs/types"; import type { BilingualLabels } from "@coscine/api-client/dist/types/Coscine.Api.Metadata"; -interface CustomTableField extends BvTableField { - key: string; - active: boolean; -} - export default defineComponent({ components: { FilesViewHeader, }, props: { folderContents: { - default() { + default: () => { return []; }, type: Array as PropType<FolderContent[]>, @@ -182,12 +179,15 @@ export default defineComponent({ type: String, }, fileListEdit: { - default() { + default: () => { return []; }, type: Array as PropType<FolderContent[]>, }, - isUploading: Boolean, + isUploading: { + default: false, + type: Boolean, + }, }, setup() { const resourceStore = useResourceStore(); @@ -203,6 +203,8 @@ export default defineComponent({ filter: "", folderPath: [] as string[], selectableFiles: [] as FolderContent[], + sortBy: "", + sortDesc: false, }; }, @@ -321,14 +323,23 @@ export default defineComponent({ applicationProfile() { this.getColumns(); }, + filter() { + this.saveInLocalStorage(); + }, headers() { this.saveInLocalStorage(); }, + locale() { + this.getColumns(); + }, resource() { this.getData(); }, - locale() { - this.getColumns(); + sortBy() { + this.saveInLocalStorage(); + }, + sortDesc() { + this.saveInLocalStorage(); }, }, created() { @@ -477,21 +488,27 @@ export default defineComponent({ metadata: factory.dataset() as unknown as Dataset, }); }, - saveInLocalStorage() { - if (this.columns.length > 0 && this.resource) { - this.resource.storedColumns = JSON.stringify(this.columns); - } + async saveInLocalStorage() { + await this.resourceStore.setStoredColumns({ + columns: this.columns, + filter: this.filter, + sortBy: this.sortBy, + sortDesc: this.sortDesc, + }); }, - loadFromLocalStorage() { - let element = null; - if (this.resource && this.resource.storedColumns) { - element = this.resource.storedColumns; - } - let json: Array<CustomTableField> = []; - if (element !== null) { - json = JSON.parse(element); + loadFromLocalStorage(): Array<CustomTableField> { + let element: Array<CustomTableField> = []; + if (this.resourceStore.currentStoredColumns) { + // Deal with old values + if (Array.isArray(this.resourceStore.currentStoredColumns)) { + return this.resourceStore.currentStoredColumns; + } + element = this.resourceStore.currentStoredColumns.columns; + this.filter = this.resourceStore.currentStoredColumns.filter; + this.sortBy = this.resourceStore.currentStoredColumns.sortBy; + this.sortDesc = this.resourceStore.currentStoredColumns.sortDesc; } - return json; + return element; }, removeColumn(row: BFormRow) { for (const column of this.columns) { diff --git a/src/modules/resource/components/resource-page/FilesViewHeader.vue b/src/modules/resource/components/resource-page/FilesViewHeader.vue index b59d2f5eee62262e3b73a0127c94ed9376f7e2e7..26c88bd779804fd0bbb6b5302926d964f5e504e3 100644 --- a/src/modules/resource/components/resource-page/FilesViewHeader.vue +++ b/src/modules/resource/components/resource-page/FilesViewHeader.vue @@ -190,7 +190,10 @@ export default defineComponent({ GitLab: () => import("./resource-type/GitLab.vue"), }, props: { - isUploading: Boolean, + isUploading: { + default: false, + type: Boolean, + }, value: { default: "", type: String, diff --git a/src/modules/resource/components/resource-page/MetadataManager.vue b/src/modules/resource/components/resource-page/MetadataManager.vue index 33175f251ab49e74ed57bc9a94442982838c9eff..eb9ce0c2da8f37162f10443270beaf98a86ca529 100644 --- a/src/modules/resource/components/resource-page/MetadataManager.vue +++ b/src/modules/resource/components/resource-page/MetadataManager.vue @@ -163,6 +163,7 @@ export default defineComponent({ type: Boolean, }, isUploading: { + default: false, type: Boolean, }, fileListEdit: { diff --git a/src/modules/resource/components/resource-page/metadata/MetadataManagerTable.vue b/src/modules/resource/components/resource-page/metadata/MetadataManagerTable.vue index e34c8e65e627de5f2f3a5dbdd8cb4a00eabdb2ad..1f2e2783f362d2deefa3b956657e5ce3d839c84b 100644 --- a/src/modules/resource/components/resource-page/metadata/MetadataManagerTable.vue +++ b/src/modules/resource/components/resource-page/metadata/MetadataManagerTable.vue @@ -55,7 +55,7 @@ </b-col> </b-row> <b-row id="metadataManagerFileName" class="text-center"> - <b-col> + <b-col id="metadataManagerFileNameInfo"> <span v-if=" currentFileId >= 0 && @@ -123,6 +123,11 @@ export default defineComponent({ font-weight: bold; width: 100%; } +#metadataManagerFileNameInfo { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} #metadataManagerShownFilesTable { margin-right: -10px; } diff --git a/src/modules/resource/store.ts b/src/modules/resource/store.ts index ce093137e27f763185f2a8b2398cc0c4fec01856..37521bcd944d0a0ad118373029e9c4285ad1e16a 100644 --- a/src/modules/resource/store.ts +++ b/src/modules/resource/store.ts @@ -1,7 +1,11 @@ import { defineStore } from "pinia"; -import type { ResourceState, VisitedResourceObject } from "./types"; +import type { + ResourceState, + StoredColumnValues, + VisitedResourceObject, +} from "./types"; import type { Dataset } from "@rdfjs/types"; -import { reactive } from "vue"; +import { nextTick, reactive } from "vue"; import { BlobApi, @@ -84,6 +88,15 @@ export const useResourceStore = defineStore({ return null; } }, + + currentStoredColumns(): StoredColumnValues | null { + if (this.currentId && this.currentResource) { + if (this.currentResource.storedColumns) { + return JSON.parse(this.currentResource.storedColumns); + } + } + return null; + }, }, /* -------------------------------------------------------------------------------------- @@ -332,10 +345,16 @@ export const useResourceStore = defineStore({ ...resource, fullApplicationProfile: null, quota: null, - storedColumns: useLocalStorage( + rawApplicationProfile: null, + storedColumns: useLocalStorage<string>( `coscine.rcv.storedColumns.${resource.id}`, - null - ).value, + JSON.stringify({ + columns: [], + filter: "", + sortBy: "", + sortDesc: false, + } satisfies StoredColumnValues) + ), }); this.visitedResources[resource.id] = visitedResource; } else { @@ -344,6 +363,15 @@ export const useResourceStore = defineStore({ } }, + async setStoredColumns(storedColumnValues: StoredColumnValues) { + if (this.currentResource && this.currentResource.storedColumns) { + // Use Ticks since otherwise, the current data might not be used + await nextTick(); + this.currentResource.storedColumns = JSON.stringify(storedColumnValues); + await nextTick(); + } + }, + async deleteFile( resource: ResourceObject | null, absoluteFilePath: string diff --git a/src/modules/resource/types.ts b/src/modules/resource/types.ts index a45f9ef4295099a4506345b0f275a73de96091be..24d77089272a6defa255c05a584c2f31809e7f09 100644 --- a/src/modules/resource/types.ts +++ b/src/modules/resource/types.ts @@ -7,6 +7,7 @@ import type { ResourceTypeInformation, } from "@coscine/api-client/dist/types/Coscine.Api.Resources"; import type { Dataset } from "@rdfjs/types"; +import type { BvTableField } from "bootstrap-vue"; export interface VisitedResourceObject extends ResourceObject { rawApplicationProfile: Dataset | null; @@ -54,6 +55,18 @@ export interface GitlabInformation { reference: Branch | null; } +export interface CustomTableField extends BvTableField { + key: string; + active: boolean; +} + +export interface StoredColumnValues { + columns: CustomTableField[]; + filter: string; + sortBy: string; + sortDesc: boolean; +} + export interface ResourceState { /* -------------------------------------------------------------------------------------- diff --git a/yarn.lock b/yarn.lock index 48277c8a887834c42b9fb095f39eaf39df765680..41a0e95ca87a8cd6cefebcb85743c302ec769c8d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -316,11 +316,11 @@ __metadata: linkType: hard "@coscine/api-client@npm:^2.11.0, @coscine/api-client@npm:^2.9.2": - version: 2.11.0 - resolution: "@coscine/api-client@npm:2.11.0" + version: 2.12.0 + resolution: "@coscine/api-client@npm:2.12.0" dependencies: axios: ^0.21.1 - checksum: 8a4c630adab854866ebc7d9c5806b61f6442b429e96f530f78ac7256440385f6d2594b66e30cb542a5a6162d92a512999d1a6f37509c699c44ea606fa04d6a21 + checksum: a51ba5dcb01cfc59fdee255c963140621bcc3837201cea2b32d36b4de3feac6a915a688e5e18eef19717c1669376ca60496bc7650da0803fc881c8e4dab11eca languageName: node linkType: hard