diff --git a/src/api/allowedMetadata.ts b/src/api/allowedMetadata.ts new file mode 100644 index 0000000000000000000000000000000000000000..8156aa61ecbd21f2cbbc01f1666c17a45ba596d5 --- /dev/null +++ b/src/api/allowedMetadata.ts @@ -0,0 +1,37 @@ +import axios from 'axios' +import { PageNumberPaginationParameters, unique } from '.' +import { AllowedMetaData, PageNumberPagination, UUID } from '@/types' + +export interface AllowedMetaDataParameters extends PageNumberPaginationParameters { + /** + * ID of the corpus to retrieve allowed metadata from. + */ + id: UUID +} + +interface AllowedMetaDataCreate extends Omit<AllowedMetaData, 'id'> { + corpusId: UUID +} + +// List metadata type-name couples allowed for manual annotation +export const listCorpusAllowedMetadata = unique( + async ({ id, ...params }: AllowedMetaDataParameters): Promise<PageNumberPagination<AllowedMetaData>> => + (await axios.get(`/corpus/${id}/allowed-metadata/`, { params })).data +) + +// Create a new metadata type-name couple allowed in a corpus. +export const createAllowedMetadata = unique( + async ({ corpusId, ...data }: AllowedMetaDataCreate): Promise<AllowedMetaData> => + (await axios.post(`/corpus/${corpusId}/allowed-metadata/`, data)).data +) + +// Delete a metadata type-name couple from the list of metadata allowed in a corpus. +export const deleteAllowedMetadata = unique( + async (corpusId: UUID, mdId: UUID) => await axios.delete(`/corpus/${corpusId}/allowed-metadata/${mdId}/`) +) + +// Update an existing metadata type-name couple allowed in a corpus. +export const updateAllowedMetadata = unique( + async (corpusId: UUID, allowedMetaDataId: UUID, data: Omit<AllowedMetaData, 'id'>): Promise<AllowedMetaData> => + (await axios.patch(`/corpus/${corpusId}/allowed-metadata/${allowedMetaDataId}/`, data)).data +) diff --git a/src/api/index.ts b/src/api/index.ts index a38547c72f4b1274abf4033cffcf2b662a47e4ae..f766eea1b95e7d269ea511ff3bd9870c198a5fde 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -14,7 +14,7 @@ export * from './files' export * from './image' export * from './ingest' export * from './job' -export * from './metadata' +export * from './allowedMetadata' export * from './mlclass' export * from './mlresults' export * from './model' @@ -30,6 +30,7 @@ export * from './worker' export * from './workerActivity' export * from './workerConfiguration' export * from './workerRun' +export * from './metadata' /** * Prevent running more than one promise at once on an asynchronous function. diff --git a/src/api/metadata.ts b/src/api/metadata.ts index b96e7041e1f8bcb9067897a866da3605337fe9b0..ddcc0f7d6273e1af79cfa13dc178bc9406df706c 100644 --- a/src/api/metadata.ts +++ b/src/api/metadata.ts @@ -1,44 +1,6 @@ import axios from 'axios' -import { PageNumberPaginationParameters, unique } from '.' -import { AllowedMetaData, MetaData, MetaDataCreate, TypedMetaData, PageNumberPagination, UUID } from '@/types' - -export interface AllowedMetaDataParameters extends PageNumberPaginationParameters { - /** - * ID of the corpus to retrieve allowed metadata from. - */ - id: UUID -} - -interface AllowedMetaDataCreate extends Omit<AllowedMetaData, 'id'> { - corpusId: UUID -} - -interface AllowedMetaDataEdit extends AllowedMetaData { - corpusId: UUID -} - -// List metadata type-name couples allowed for manual annotation -export const listCorpusAllowedMetadata = unique( - async ({ id, ...params }: AllowedMetaDataParameters): Promise<PageNumberPagination<AllowedMetaData>> => - (await axios.get(`/corpus/${id}/allowed-metadata/`, { params })).data -) - -// Create a new metadata type-name couple allowed in a corpus. -export const createAllowedMetadata = unique( - async ({ corpusId, ...data }: AllowedMetaDataCreate): Promise<AllowedMetaData> => - (await axios.post(`/corpus/${corpusId}/allowed-metadata/`, data)).data -) - -// Delete a metadata type-name couple from the list of metadata allowed in a corpus. -export const deleteAllowedMetadata = unique( - async (corpusId: UUID, mdId: UUID) => await axios.delete(`/corpus/${corpusId}/allowed-metadata/${mdId}/`) -) - -// Update an existing metadata type-name couple allowed in a corpus. -export const updateAllowedMetadata = unique( - async ({ corpusId, id, ...data }: AllowedMetaDataEdit): Promise<AllowedMetaData> => - (await axios.patch(`/corpus/${corpusId}/allowed-metadata/${id}/`, data)).data -) +import { unique } from '.' +import { MetaData, MetaDataCreate, TypedMetaData, UUID } from '@/types' // Create a metadata. export const createMetadata = unique( diff --git a/src/components/Corpus/AllowedMetaData/CreateForm.vue b/src/components/Corpus/AllowedMetaData/CreateForm.vue index 126ed24bafca637e98d19e6656d46b51b06c6a17..3d53631832594f77552da0378960590285f415d7 100644 --- a/src/components/Corpus/AllowedMetaData/CreateForm.vue +++ b/src/components/Corpus/AllowedMetaData/CreateForm.vue @@ -4,7 +4,7 @@ <input class="input" type="text" - :disabled="!canAdmin(corpus) || loading || null" + :disabled="!canAdmin(corpus) || loading || undefined" v-model="createMeta.name" required /> @@ -17,7 +17,7 @@ <div class="select"> <select v-model="createMeta.type" - :disabled="!canAdmin(corpus) || loading || null" + :disabled="!canAdmin(corpus) || loading || undefined" required > <option @@ -45,7 +45,7 @@ <button class="button is-primary" v-on:click="create" - :disabled="!allowCreate || null" + :disabled="!allowCreate || undefined" :title="allowCreate ? 'Create allowed metadata' : createDisabledTitle" > <i class="icon-plus"></i> @@ -54,17 +54,23 @@ </tr> </template> -<script> -import { mapMutations } from 'vuex' +<script lang='ts'> +import { defineComponent, PropType } from 'vue' +import { mapActions } from 'pinia' import { corporaMixin } from '@/mixins.js' -import { METADATA_TYPES } from '@/config' -export default { +import { METADATA_TYPES, UUID_REGEX } from '@/config' +import { useAllowedMetaDataStore, useNotificationStore } from '@/stores' +import { isAxiosError } from 'axios' +import { UUID } from '@/types' + +export default defineComponent({ mixins: [ corporaMixin ], props: { corpusId: { - type: String, + type: String as PropType<UUID>, + validator: value => typeof value === 'string' && UUID_REGEX.test(value), required: true } }, @@ -98,13 +104,13 @@ export default { } }, methods: { - ...mapMutations('notifications', ['notify']), + ...mapActions(useNotificationStore, ['notify']), + ...mapActions(useAllowedMetaDataStore, ['createAllowedMetadata']), async create () { if (!this.allowCreate) return - this.loading = true try { - await this.$store.dispatch('corpora/createAllowedMetadata', { corpusId: this.corpusId, ...this.createMeta }) + await this.createAllowedMetadata(this.corpusId, this.createMeta) this.createMeta = { name: '', type: '' @@ -115,13 +121,13 @@ export default { this.createMeta.name = '' this.createMeta.type = '' } catch (err) { - if (err.response.status === 400) this.createErrors = err.response.data + if (isAxiosError(err) && err.response && err.response.status === 400) this.createErrors = err.response.data } finally { this.loading = false } } } -} +}) </script> <style scoped> diff --git a/src/components/Corpus/AllowedMetaData/List.vue b/src/components/Corpus/AllowedMetaData/List.vue index 0cc7d950d3c5dd30d7f83b0f39713f206d70b58d..0d1e9982ae7f680b9a1e8412fac6bb5abdc2921c 100644 --- a/src/components/Corpus/AllowedMetaData/List.vue +++ b/src/components/Corpus/AllowedMetaData/List.vue @@ -9,7 +9,7 @@ </thead> <tbody> <MetaData - v-for="metaData in corpusAllowedMetadata[corpusId]" + v-for="metaData in allowedMetadata[corpusId]" :key="metaData.name" :metadata="metaData" :corpus-id="corpusId" @@ -21,20 +21,25 @@ </table> </template> -<script> -import { mapState, mapActions, mapMutations } from 'vuex' +<script lang='ts'> +import { mapState, mapActions } from 'pinia' +import { defineComponent, PropType } from 'vue' import { errorParser } from '@/helpers' -import MetaData from './Row' -import CreateForm from './CreateForm' +import MetaData from './Row.vue' +import CreateForm from './CreateForm.vue' +import { useNotificationStore, useAllowedMetaDataStore } from '@/stores' +import { UUID } from '@/types' +import { UUID_REGEX } from '@/config' -export default { +export default defineComponent({ components: { MetaData, CreateForm }, props: { corpusId: { - type: String, + type: String as PropType<UUID>, + validator: value => typeof value === 'string' && UUID_REGEX.test(value), required: true } }, @@ -42,16 +47,16 @@ export default { loading: false }), computed: { - ...mapState('corpora', ['corpusAllowedMetadata']) + ...mapState(useAllowedMetaDataStore, ['allowedMetadata']) }, methods: { - ...mapActions('corpora', ['listAllowedMetadata']), - ...mapMutations('notifications', ['notify']), + ...mapActions(useAllowedMetaDataStore, ['listAllowedMetadata']), + ...mapActions(useNotificationStore, ['notify']), async listMetaData () { - if (this.corpusAllowedMetadata[this.corpusId]) return + if (this.allowedMetadata[this.corpusId]) return this.loading = true try { - await this.listAllowedMetadata({ corpusId: this.corpusId }) + await this.listAllowedMetadata(this.corpusId) } catch (err) { this.notify({ type: 'error', text: `An error occurred listing allowed metadata: ${errorParser(err)}` }) } finally { @@ -65,5 +70,5 @@ export default { immediate: true } } -} +}) </script> diff --git a/src/components/Corpus/AllowedMetaData/Row.vue b/src/components/Corpus/AllowedMetaData/Row.vue index 9cb1a1a068fe2a976de5799910583b79ebb4259c..4a437874dc6096c7e850435c4dff8113d2d81fb2 100644 --- a/src/components/Corpus/AllowedMetaData/Row.vue +++ b/src/components/Corpus/AllowedMetaData/Row.vue @@ -1,12 +1,12 @@ <template> <tr v-if="metadata"> <!-- edit mode --> - <template v-if="editing" v-on:submit.prevent="save"> + <template v-if="editing"> <td> <input class="input" type="text" - :disabled="loading || null" + :disabled="loading || undefined" v-model="editMeta.name" required /> @@ -19,7 +19,7 @@ <div class="select"> <select v-model="editMeta.type" - :disabled="loading || null" + :disabled="loading || undefined" required > <option @@ -45,7 +45,7 @@ </td> </template> <!-- display mode --> - <template v-else v-on:submit.prevent="save"> + <template v-else> <td>{{ metadata.name }}</td> <td>{{ METADATA_TYPES[metadata.type].display }}</td> </template> @@ -66,7 +66,7 @@ v-else class="button mr-2" type="submit" - :disabled="!allowEdit || null" + :disabled="!allowEdit || undefined" :title="allowEdit ? 'Edit allowed metadata' : 'You must have an admin access to this project in order to perform this action.'" v-on:click="edit(metadata)" > @@ -74,7 +74,7 @@ </button> <button class="button has-text-danger" - :disabled="!allowDelete || null" + :disabled="!allowDelete || undefined" v-on:click="deleteModal = allowDelete" :title=" allowDelete @@ -106,12 +106,17 @@ </tr> </template> -<script> -import { mapActions } from 'vuex' +<script lang='ts'> +import { defineComponent, PropType } from 'vue' +import { mapActions } from 'pinia' import Modal from '@/components/Modal.vue' -import { METADATA_TYPES } from '@/config' +import { METADATA_TYPES, UUID_REGEX } from '@/config' import { corporaMixin } from '@/mixins.js' -export default { +import { useAllowedMetaDataStore } from '@/stores' +import { AllowedMetaData, UUID } from '@/types' +import { isAxiosError } from 'axios' + +export default defineComponent({ mixins: [ corporaMixin ], @@ -120,11 +125,12 @@ export default { }, props: { corpusId: { - type: String, + type: String as PropType<UUID>, + validator: value => typeof value === 'string' && UUID_REGEX.test(value), required: true }, metadata: { - type: Object, + type: Object as PropType<AllowedMetaData>, required: true } }, @@ -156,8 +162,7 @@ export default { this.editMeta.name.trim() !== this.metadata.name) }, disableButton () { - // disabled must be set to null, undefined, or an empty string to not actually disable. - let disable = null + let disable = false if (!this.allowEdit) disable = true else if (this.editing && !this.allowUpdate) disable = true return disable @@ -179,7 +184,8 @@ export default { } }, methods: { - edit (metadata) { + ...mapActions(useAllowedMetaDataStore, ['deleteAllowedMetadata', 'updateAllowedMetadata']), + edit (metadata: AllowedMetaData) { this.editing = true this.editMeta.name = metadata.name this.editMeta.type = metadata.type @@ -188,31 +194,32 @@ export default { if (!this.allowUpdate) return this.loading = true try { - await this.$store.dispatch('corpora/updateAllowedMetadata', { corpusId: this.corpusId, mdId: this.metadata.id, ...this.editMeta }) + await this.updateAllowedMetadata(this.corpusId, { id: this.metadata.id, ...this.editMeta }) this.editMeta = { type: '', name: '' } } catch (err) { - this.updateErrors = err.response.data + if (isAxiosError(err) && err.response) { + this.updateErrors = err.response.data + } } finally { this.loading = false this.editing = false } }, - ...mapActions('corpora', ['deleteAllowedMetadata']), async mdDelete () { if (!this.allowDelete) return this.deleteLoading = true try { - await this.deleteAllowedMetadata({ corpusId: this.corpusId, mdId: this.metadata.id }) + await this.deleteAllowedMetadata(this.corpusId, this.metadata.id) this.deleteModal = false } finally { this.deleteLoading = false } } } -} +}) </script> <style scoped> diff --git a/src/components/Element/Metadata/Metadata.vue b/src/components/Element/Metadata/Metadata.vue index 36c2d6afe0ae829887fdc92eefec8f714eb6399d..654003530ffde46817720b18301b0fc7cc0b1c98 100644 --- a/src/components/Element/Metadata/Metadata.vue +++ b/src/components/Element/Metadata/Metadata.vue @@ -62,7 +62,7 @@ Please make sure your metadata types and names are correct to preserve data integrity.<br /> It is highly recommended to define project-wide allowed metadata in the administration panel instead of using this mode. </div> - <div class="notification is-warning" v-if="!allowedMetadata.length && !freeEdit"> + <div class="notification is-warning" v-if="!corpusAllowedMetadata.length && !freeEdit"> There are no allowed metadata for this project. {{ isAdmin ? 'Use the administration panel to add them.' : 'Please contact your administrator.' }} </div> @@ -109,7 +109,7 @@ <select v-model="selectedAllowedMetadata"> <option value="" selected disabled>Select a metadata…</option> <option - v-for="(meta, index) in allowedMetadata" + v-for="(meta, index) in corpusAllowedMetadata" :key="index" :value="index" > @@ -194,7 +194,7 @@ import { import { METADATA_TYPES } from '@/config' import { corporaMixin } from '@/mixins' -import { useDisplayStore, useMetaDataStore } from '@/stores' +import { useDisplayStore, useMetaDataStore, useAllowedMetaDataStore } from '@/stores' import DateInput from '@/components/DateInput' import Modal from '@/components/Modal' @@ -235,12 +235,12 @@ export default { }), computed: { ...mapVuexGetters('auth', ['isAdmin']), - ...mapVuexState('corpora', ['corpusAllowedMetadata']), + ...mapState(useAllowedMetaDataStore, ['allowedMetadata']), ...mapVuexState('elements', ['elements']), ...mapState(useDisplayStore, ['lastMetadataName', 'lastMetadataType', 'dropdowns']), ...mapState(useMetaDataStore, ['metadata']), - allowedMetadata () { - return this.corpusAllowedMetadata[this.corpusId] || [] + corpusAllowedMetadata () { + return this.allowedMetadata[this.corpusId] || [] }, element () { return this.elements[this.elementId] @@ -273,12 +273,13 @@ export default { ...mapVuexMutations('notifications', ['notify']), ...mapActions(useDisplayStore, ['setLastMetadata']), ...mapActions(useMetaDataStore, ['updateMetadata', 'createMetadata', 'deleteMetadata', 'listMetadata']), + ...mapActions(useAllowedMetaDataStore, ['listAllowedMetadata']), isEditable (md) { return this.isAdmin || (this.canWrite(this.corpus) && this.isAllowed(md)) }, isAllowed (md = this.selectedMetadata) { // Check if metadata is part of allowed ones - return this.allowedMetadata.some(allowed => allowed.type === md.type && allowed.name === md.name) + return this.corpusAllowedMetadata.some(allowed => allowed.type === md.type && allowed.name === md.name) }, validateForm () { this.formErrors = {} @@ -381,7 +382,7 @@ export default { * If it is not found, it switches to free edit mode for admins, or unselects. */ findAllowedMetadata ({ type, name }) { - const foundIndex = this.allowedMetadata.findIndex( + const foundIndex = this.corpusAllowedMetadata.findIndex( allowed => allowed.type === type && allowed.name === name ) if (foundIndex < 0) { @@ -397,7 +398,7 @@ export default { }, updateSelectedAllowedMetadata () { if (!this.selectedMetadata || !Number.isInteger(this.selectedAllowedMetadata) || this.selectedAllowedMetadata < 0) return - const allowedMetadata = { name: this.allowedMetadata[this.selectedAllowedMetadata].name, type: this.allowedMetadata[this.selectedAllowedMetadata].type } + const allowedMetadata = { name: this.corpusAllowedMetadata[this.selectedAllowedMetadata].name, type: this.corpusAllowedMetadata[this.selectedAllowedMetadata].type } if (!allowedMetadata) return this.selectedMetadata = { ...this.selectedMetadata, @@ -422,8 +423,8 @@ export default { immediate: true, handler () { // Do fetch allowed metadata instantly after retrieving the corpus - if (!this.corpus.id || this.allowedMetadata.length) return - this.$store.dispatch('corpora/listAllowedMetadata', { corpusId: this.corpusId }) + if (!this.corpus.id || this.corpusAllowedMetadata.length) return + this.listAllowedMetadata(this.corpusId) } }, opened: { diff --git a/src/filterbar.js b/src/filterbar.js index bc5457346fb98166777762460b7175297eb022db..4c2bd6322d2baf094700782e8d8ca9d91ffa20bf 100644 --- a/src/filterbar.js +++ b/src/filterbar.js @@ -10,7 +10,7 @@ * of their methods and wreaking havoc upon the filter bar. */ import * as api from '@/api' -import { useMLResultsStore } from '@/stores' +import { useMLResultsStore, useAllowedMetaDataStore } from '@/stores' import { UUID_REGEX } from '@/config' /** @@ -325,11 +325,12 @@ export class MetadataNameFilter extends Filter { * @returns {Promise<string[]>} Suggested metadata names. */ async autocomplete () { + const allowedMetadataStore = useAllowedMetaDataStore() const corpusId = this.store.state.navigation.corpusId // Load the corpus' allowed metadata if needed - await this.store.dispatch('corpora/listAllowedMetadata', { corpusId }) + await allowedMetadataStore.listAllowedMetadata(corpusId) // Retrieve unique metadata names; there might be multiple AllowedMetadata with the same name and different types - return [...new Set(this.store.state.corpora.corpusAllowedMetadata[corpusId].map(({ name }) => name))] + return [...new Set(allowedMetadataStore.allowedMetadata[corpusId].map(({ name }) => name))] } } diff --git a/src/store/corpora.js b/src/store/corpora.js index 91d821e8a9d416ed6a85902cf87652823e157807..0088674aa0539fec04f15e04c87a1fb6552a8514 100644 --- a/src/store/corpora.js +++ b/src/store/corpora.js @@ -11,8 +11,6 @@ export const initialState = () => ({ * Prevents trying to reload the corpora when there are none available. */ corporaLoaded: false, - // { [corpusId]: AllowedMetadata[] } - corpusAllowedMetadata: {}, // { [corpusId]: EntityTypes[] } corpusEntityTypes: {} }) @@ -109,37 +107,6 @@ export const mutations = { state.corpusEntityTypes[corpusId] = typesList }, - setCorpusAllowedMetadata (state, { corpusId, results }) { - const updatedList = state.corpusAllowedMetadata[corpusId] || [] - results.forEach(newMeta => { - // Prevent duplicating allowed metadata - if (!updatedList.some(meta => meta.type === newMeta.type && meta.name === newMeta.name)) updatedList.push(newMeta) - }) - // Merge corpus allowed metadata - state.corpusAllowedMetadata = { - ...state.corpusAllowedMetadata, - [corpusId]: updatedList - } - }, - - updateCorpusAllowedMetadata (state, { corpusId, data }) { - if (!state.corpusAllowedMetadata[corpusId]) throw new Error(`Allowed metadata for corpus ${corpusId} not found`) - const mdList = [...state.corpusAllowedMetadata[corpusId]] - const index = mdList.findIndex(item => item.id === data.id) - if (index < 0) throw new Error(`Allowed metadata ${data.id} not found in corpus ${corpusId}`) - mdList.splice(index, 1, data) - state.corpusAllowedMetadata[corpusId] = mdList - }, - - removeCorpusAllowedMetadata (state, { corpusId, mdId }) { - if (!state.corpusAllowedMetadata[corpusId]) throw new Error(`Allowed metadata for corpus ${corpusId} not found`) - const mdList = [...state.corpusAllowedMetadata[corpusId]] - const index = mdList.findIndex(item => item.id === mdId) - if (index < 0) throw new Error(`Allowed metadata ${mdId} not found in corpus ${corpusId}`) - mdList.splice(index, 1) - state.corpusAllowedMetadata[corpusId] = mdList - }, - addDefaultCorpus (state, { id, name }) { state.corpora = { ...state.corpora, @@ -263,50 +230,6 @@ export const actions = { } }, - async listAllowedMetadata ({ state, commit, dispatch }, { corpusId, page = 1 }) { - // Do not start fetching allowed metadata if they have been retrieved already - if (page === 1 && state.corpusAllowedMetadata[corpusId]) return - // Automatically list all allowed metadata for a corpus through pagination - const data = await api.listCorpusAllowedMetadata({ id: corpusId, page }) - commit('setCorpusAllowedMetadata', { corpusId, results: data.results }) - if (!data || !data.number || page !== data.number) { - // Avoid any loop - throw new Error(`Pagination failed listing available metadata for corpus "${corpusId}"`) - } - // Load other pages - if (data.next) await dispatch('listAllowedMetadata', { corpusId, page: page + 1 }) - }, - - async createAllowedMetadata ({ commit }, { corpusId, ...metadata }) { - try { - const data = await api.createAllowedMetadata({ ...metadata, corpusId }) - commit('setCorpusAllowedMetadata', { corpusId, results: [data] }) - } catch (err) { - commit('notifications/notify', { type: 'error', text: errorParser(err) }, { root: true }) - throw err - } - }, - - async updateAllowedMetadata ({ commit }, { corpusId, mdId, ...metadata }) { - try { - const data = await api.updateAllowedMetadata({ corpusId, id: mdId, ...metadata }) - commit('updateCorpusAllowedMetadata', { corpusId, data }) - } catch (err) { - commit('notifications/notify', { type: 'error', text: errorParser(err) }, { root: true }) - throw err - } - }, - - async deleteAllowedMetadata ({ commit }, { corpusId, mdId }) { - try { - await api.deleteAllowedMetadata(corpusId, mdId) - commit('removeCorpusAllowedMetadata', { corpusId, mdId }) - } catch (err) { - commit('notifications/notify', { type: 'error', text: errorParser(err) }, { root: true }) - throw err - } - }, - async createType ({ commit }, { corpus, ...type }) { try { const data = await api.createType({ corpus, ...type }) diff --git a/src/stores/allowedMetadata.ts b/src/stores/allowedMetadata.ts new file mode 100644 index 0000000000000000000000000000000000000000..e691f51ea15eaaec82f694794cbbf98c5773280f --- /dev/null +++ b/src/stores/allowedMetadata.ts @@ -0,0 +1,76 @@ +import { defineStore } from 'pinia' +import { UUID, AllowedMetaData } from '@/types' +import * as api from '@/api' +import { useNotificationStore } from '.' +import { errorParser } from '@/helpers' + +interface State { + allowedMetadata: { + [corpusId: UUID]: AllowedMetaData[] + } +} + +export const useAllowedMetaDataStore = defineStore('allowedMetadata', { + state: (): State => ({ + allowedMetadata: {} + }), + actions: { + setCorpusAllowedMetadata (corpusId: UUID, allowedMetadata: AllowedMetaData[]) { + const updatedList = this.allowedMetadata[corpusId] || [] + allowedMetadata.forEach(newMeta => { + // Prevent duplicating allowed metadata + if (!updatedList.some(meta => meta.type === newMeta.type && meta.name === newMeta.name)) updatedList.push(newMeta) + }) + // Merge + this.allowedMetadata[corpusId] = updatedList + }, + async listAllowedMetadata (corpusId: UUID, page = 1) { + // Do not start fetching if they have already been retrieved already + if (page === 1 && this.allowedMetadata[corpusId]) return + // Automatically list all allowed metadata for a corpus through pagination + const data = await api.listCorpusAllowedMetadata({ id: corpusId, page }) + this.setCorpusAllowedMetadata(corpusId, data.results) + if (!data || !data.number || page !== data.number) { + // Avoid any loop + throw new Error(`Pagination failed listing available metadata for corpus "${corpusId}"`) + } + // Load other page + if (data.next) await this.listAllowedMetadata(corpusId, page + 1) + }, + async createAllowedMetadata (corpusId: UUID, metadata: Omit<AllowedMetaData, 'id'>) { + try { + const data = await api.createAllowedMetadata({ corpusId, ...metadata }) + this.setCorpusAllowedMetadata(corpusId, [data]) + } catch (err) { + useNotificationStore().notify({ type: 'error', text: errorParser(err) }) + throw err + } + }, + async updateAllowedMetadata (corpusId: UUID, metadata: AllowedMetaData) { + try { + const data = await api.updateAllowedMetadata(corpusId, metadata.id, metadata) + if (!this.allowedMetadata[corpusId]) throw new Error(`Allowed metadata for corpus ${corpusId} not found.`) + const mdList = [...this.allowedMetadata[corpusId]] + const index = mdList.findIndex(item => item.id === data.id) + if (index < 0) throw new Error(`Allowed metadata ${metadata.id} not found in corpus ${corpusId}`) + mdList.splice(index, 1, data) + this.allowedMetadata[corpusId] = mdList + } catch (err) { + useNotificationStore().notify({ type: 'error', text: errorParser(err) }) + throw err + } + }, + async deleteAllowedMetadata (corpusId: UUID, metaDataId: UUID) { + try { + await api.deleteAllowedMetadata(corpusId, metaDataId) + if (!this.allowedMetadata[corpusId]) throw new Error(`Allowed metadata for corpus ${corpusId} not found.`) + const index = this.allowedMetadata[corpusId].findIndex(item => item.id === metaDataId) + if (index < 0) throw new Error(`Allowed metadata ${metaDataId} not found in corpus ${corpusId}.`) + this.allowedMetadata[corpusId].splice(index, 1) + } catch (err) { + useNotificationStore().notify({ type: 'error', text: errorParser(err) }) + throw err + } + } + } +}) diff --git a/src/stores/index.ts b/src/stores/index.ts index b57e903c8d6bd02d48e10a2a47effbf853a1c496..a3d50742ca97f4b8ef663acf579583cdd8133a65 100644 --- a/src/stores/index.ts +++ b/src/stores/index.ts @@ -16,3 +16,4 @@ export { useExportStore } from './exports' export { useEntityStore } from './entity' export { useTranscriptionStore } from './transcription' export { useMetaDataStore } from './metadata' +export { useAllowedMetaDataStore } from './allowedMetadata' diff --git a/tests/unit/filterbar.spec.js b/tests/unit/filterbar.spec.js index 5b7ff8a9581209dc363f970572314b2eaef26c82..2daa1ab34f336ceb64a3ceddc572ba0e8b2b0abc 100644 --- a/tests/unit/filterbar.spec.js +++ b/tests/unit/filterbar.spec.js @@ -2,7 +2,7 @@ import { assert } from 'chai' import axios from 'axios' import store from './store/index.spec.js' import { createPinia, setActivePinia } from 'pinia' -import { useMLResultsStore } from '@/stores' +import { useMLResultsStore, useAllowedMetaDataStore } from '@/stores' import { FakeAxios, assertRejects } from './testhelpers.js' import { workerVersionsCacheSample, workerVersionsSample } from './samples.js' import { @@ -22,12 +22,13 @@ import { } from '@/filterbar' describe('filterbar.js', () => { - let mock, mlResultsStore + let mock, mlResultsStore, allowedMetadataStore before(() => { setActivePinia(createPinia()) mock = new FakeAxios(axios) mlResultsStore = useMLResultsStore() + allowedMetadataStore = useAllowedMetaDataStore() }) beforeEach(() => { @@ -39,6 +40,8 @@ describe('filterbar.js', () => { afterEach(() => { store.reset() + allowedMetadataStore.$reset() + mlResultsStore.$reset() mock.reset() }) @@ -963,7 +966,7 @@ describe('filterbar.js', () => { ]) }) - it('fetches worker versions only when needed', async () => { + it('fetches worker versions when needed', async () => { store.state.navigation.corpusId = 'corpusid' mlResultsStore.corpusWorkerVersionsCache = {} mock.onGet('/corpus/corpusid/versions/').reply(200, workerVersionsCacheSample) @@ -1058,7 +1061,7 @@ describe('filterbar.js', () => { assert.strictEqual(err.toString(), "Error: Unknown worker version ID: 'the wrong version'") }) - it('fetches worker versions only when needed', async () => { + it('fetches worker versions when needed', async () => { store.state.navigation.corpusId = 'corpusid' mlResultsStore.corpusWorkerVersionsCache = {} mock.onGet('/corpus/corpusid/versions/').reply(200, workerVersionsCacheSample) @@ -1120,7 +1123,7 @@ describe('filterbar.js', () => { assert.strictEqual(await filter.deserialize('the wrong version'), undefined) }) - it('fetches worker versions only when needed', async () => { + it('fetches worker versions when needed', async () => { store.state.navigation.corpusId = 'corpusid' mlResultsStore.corpusWorkerVersionsCache = {} mock.onGet('/corpus/corpusid/versions/').reply(200, workerVersionsCacheSample) @@ -1145,7 +1148,7 @@ describe('filterbar.js', () => { assert.strictEqual(filter.store, store) }) - const allowedMetadata = [ + const testAllowedMetadata = [ { name: 'le url', type: 'url' @@ -1162,7 +1165,7 @@ describe('filterbar.js', () => { }) it('autocompletes with allowed metadata', async () => { - store.state.corpora.corpusAllowedMetadata.corpusid = allowedMetadata + allowedMetadataStore.allowedMetadata.corpusid = testAllowedMetadata const filter = new MetadataNameFilter(store) @@ -1176,7 +1179,7 @@ describe('filterbar.js', () => { mock.onGet('/corpus/corpusid/allowed-metadata/').reply(200, { count: 2, number: 1, - results: allowedMetadata + results: testAllowedMetadata }) const filter = new MetadataNameFilter(store) @@ -1186,26 +1189,14 @@ describe('filterbar.js', () => { 'le number' ]) - assert.deepStrictEqual(store.history, [ - { - action: 'corpora/listAllowedMetadata', - payload: { - corpusId: 'corpusid' - } - }, - { - mutation: 'corpora/setCorpusAllowedMetadata', - payload: { - corpusId: 'corpusid', - results: allowedMetadata - } - } - ]) + assert.deepStrictEqual(allowedMetadataStore.allowedMetadata, { + corpusid: testAllowedMetadata + }) }) }) it('allows any name', () => { - store.state.corpora.corpusAllowedMetadata.corpusid = allowedMetadata + allowedMetadataStore.allowedMetadata.corpusid = testAllowedMetadata const filter = new MetadataNameFilter(store) diff --git a/tests/unit/store/corpora.spec.js b/tests/unit/store/corpora.spec.js index 6e2252d5d25df43c483c7381205eb2b25eee4877..03c14e6391cdbca68f242f3d88a7f2d934b81374 100644 --- a/tests/unit/store/corpora.spec.js +++ b/tests/unit/store/corpora.spec.js @@ -310,162 +310,6 @@ describe('corpora', () => { }) }) - describe('setCorpusAllowedMetadata', () => { - it('updates allowed metadata on a corpus', () => { - const state = { - corpusAllowedMetadata: { - corpus1: [ - { type: 'A', name: 'A' }, - { type: 'B', name: 'B' } - ] - } - } - const addedAllowedMeta = [ - { type: 'A', name: 'A' }, - { type: 'A', name: 'B' }, - { type: 'B', name: 'A' } - ] - - mutations.setCorpusAllowedMetadata(state, { corpusId: 'corpus1', results: addedAllowedMeta }) - - assert.deepStrictEqual(state, { - corpusAllowedMetadata: { - corpus1: [ - { type: 'A', name: 'A' }, - { type: 'B', name: 'B' }, - { type: 'A', name: 'B' }, - { type: 'B', name: 'A' } - ] - } - }) - }) - }) - - describe('updateCorpusAllowedMetadata', () => { - it('updates an existing allowed metadata (name, type) couple in state.store.corpusAllowedMetadata[corpusId]', async () => { - const state = { - corpora: {}, - corporaLoaded: false, - corpusAllowedMetadata: { - someCorpus: [ - { id: 'oneId', name: 'oneName', type: 'oneType' }, - { id: 'twoId', name: 'twoName', type: 'twoType' }, - { id: 'threeId', name: 'threeName', type: 'threeType' } - ] - } - } - const expected = { - corpora: {}, - corporaLoaded: false, - corpusAllowedMetadata: { - someCorpus: [ - { id: 'oneId', name: 'oneName', type: 'oneType' }, - { id: 'twoId', name: 'twoName', type: 'twoType' }, - { id: 'threeId', name: 'fourName', type: 'threeType' } - ] - } - } - mutations.updateCorpusAllowedMetadata(state, { corpusId: 'someCorpus', data: { id: 'threeId', type: 'threeType', name: 'fourName' } }) - assert.deepStrictEqual(state, expected) - }) - it('throws an error if the metadata id is not found in the corpus allowed metadata', async () => { - const state = { - corpora: {}, - corporaLoaded: false, - corpusAllowedMetadata: { - someCorpus: [ - { id: 'oneId', name: 'oneName', type: 'oneType' }, - { id: 'twoId', name: 'twoName', type: 'twoType' }, - { id: 'threeId', name: 'threeName', type: 'threeType' } - ] - } - } - assert.throws( - () => mutations.updateCorpusAllowedMetadata(state, { corpusId: 'someCorpus', data: { id: 'missingId', name: 'someName', type: 'someType' } }), - 'Allowed metadata missingId not found in corpus someCorpus' - ) - }) - it('throws an error if there is no state.store.corpusAllowedMetadata[corpusId]', async () => { - const state = { - corpora: {}, - corporaLoaded: false, - corpusAllowedMetadata: {} - } - assert.throws( - () => mutations.updateCorpusAllowedMetadata(state, { corpusId: 'someCorpus', mdId: 'missingId' }), - 'Allowed metadata for corpus someCorpus not found' - ) - }) - }) - - describe('removeCorpusAllowedMetadata', () => { - it('removes an allowed metadata from state.store.corpusAllowedMetadata[corpusId]', async () => { - const state = { - corpora: {}, - corporaLoaded: false, - corpusAllowedMetadata: { - someCorpus: [ - { id: 'oneId', name: 'oneName', type: 'oneType' }, - { id: 'twoId', name: 'twoName', type: 'twoType' }, - { id: 'threeId', name: 'threeName', type: 'threeType' } - ] - } - } - const expected = { - corpora: {}, - corporaLoaded: false, - corpusAllowedMetadata: { - someCorpus: [ - { id: 'oneId', name: 'oneName', type: 'oneType' }, - { id: 'threeId', name: 'threeName', type: 'threeType' } - ] - } - } - mutations.removeCorpusAllowedMetadata(state, { corpusId: 'someCorpus', mdId: 'twoId' }) - assert.deepStrictEqual(state, expected) - }) - it('throws an error if the metadata id is not found in the corpus allowed metadata', async () => { - const state = { - corpora: {}, - corporaLoaded: false, - corpusAllowedMetadata: { - someCorpus: [ - { id: 'oneId', name: 'oneName', type: 'oneType' }, - { id: 'twoId', name: 'twoName', type: 'twoType' }, - { id: 'threeId', name: 'threeName', type: 'threeType' } - ] - } - } - const expected = { - corpora: {}, - corporaLoaded: false, - corpusAllowedMetadata: { - someCorpus: [ - { id: 'oneId', name: 'oneName', type: 'oneType' }, - { id: 'twoId', name: 'twoName', type: 'twoType' }, - { id: 'threeId', name: 'threeName', type: 'threeType' } - ] - } - } - assert.throws( - () => mutations.removeCorpusAllowedMetadata(state, { corpusId: 'someCorpus', mdId: 'missingId' }), - 'Allowed metadata missingId not found in corpus someCorpus' - ) - assert.deepStrictEqual(state, expected) - }) - it('handles errors', async () => { - const state = { - corpora: {}, - corporaLoaded: false, - corpusAllowedMetadata: {} - } - assert.throws( - () => mutations.removeCorpusAllowedMetadata(state, { corpusId: 'someCorpus', mdId: 'oneId' }), - 'Allowed metadata for corpus someCorpus not found' - ) - }) - }) - describe('setCorpusEntityTypes', () => { it('updates entity types on a corpus', () => { const state = { @@ -1428,194 +1272,5 @@ describe('corpora', () => { ]) }) }) - - describe('listAllowedMetadata', () => { - it('recursively lists allowed metadata for a corpus', async () => { - const pages = [{ - count: 3, - number: 1, - next: 'nexpage', - results: [{ type: 'A', name: 'A' }, { type: 'A', name: 'B' }] - }, { - count: 3, - number: 2, - next: null, - results: [{ type: 'B', name: 'B' }] - }] - mock.onGet('/corpus/corpusid/allowed-metadata/', { params: { page: 1 } }).reply(200, pages[0]) - mock.onGet('/corpus/corpusid/allowed-metadata/', { params: { page: 2 } }).reply(200, pages[1]) - - await store.dispatch('corpora/listAllowedMetadata', { corpusId: 'corpusid' }) - - assert.deepStrictEqual(store.history, [ - { action: 'corpora/listAllowedMetadata', payload: { corpusId: 'corpusid' } }, - { mutation: 'corpora/setCorpusAllowedMetadata', payload: { corpusId: 'corpusid', results: pages[0].results } }, - { action: 'corpora/listAllowedMetadata', payload: { corpusId: 'corpusid', page: 2 } }, - { mutation: 'corpora/setCorpusAllowedMetadata', payload: { corpusId: 'corpusid', results: pages[1].results } } - ]) - - assert.deepStrictEqual(store.state.corpora.corpusAllowedMetadata, { - corpusid: [ - { name: 'A', type: 'A' }, - { name: 'B', type: 'A' }, - { name: 'B', type: 'B' } - ] - }) - }) - }) - - describe('createAllowedMetadata', () => { - it('creates a new allowed metadata in a corpus', async () => { - store.state.corpora.corpusAllowedMetadata = { - someCorpus: [ - { id: 'oneId', name: 'oneName', type: 'oneType' } - ] - } - const response = { - id: 'metaId', - type: 'someType', - name: 'someName' - } - mock.onPost('/corpus/someCorpus/allowed-metadata/').reply(201, response) - - await store.dispatch('corpora/createAllowedMetadata', { corpusId: 'someCorpus', type: 'someType', name: 'someName' }) - - assert.deepStrictEqual(store.state.corpora.corpusAllowedMetadata, { - someCorpus: [ - { id: 'oneId', name: 'oneName', type: 'oneType' }, - { id: 'metaId', name: 'someName', type: 'someType' } - ] - }) - assert.deepStrictEqual(store.history, [ - { - action: 'corpora/createAllowedMetadata', - payload: { corpusId: 'someCorpus', type: 'someType', name: 'someName' } - }, - { - mutation: 'corpora/setCorpusAllowedMetadata', - payload: { corpusId: 'someCorpus', results: [{ id: 'metaId', type: 'someType', name: 'someName' }] } - } - ]) - }) - - it('handles errors', async () => { - mock.onPost('/corpus/someCorpus/allowed-metadata/').reply(400) - await assertRejects(async () => store.dispatch('corpora/createAllowedMetadata', { corpusId: 'someCorpus', type: 'someType', name: 'someName' })) - - assert.deepStrictEqual(store.history, [ - { - action: 'corpora/createAllowedMetadata', - payload: { corpusId: 'someCorpus', type: 'someType', name: 'someName' } - }, - { - mutation: 'notifications/notify', - payload: { - type: 'error', - text: 'Request failed with status code 400' - } - } - ]) - }) - }) - - describe('deleteAllowedMetadata', () => { - it('removes an allowed metadata (name, type) couple from a corpus', async () => { - store.state.corpora.corpusAllowedMetadata = { - someCorpus: [ - { id: 'oneId', name: 'oneName', type: 'oneType' }, - { id: 'twoId', name: 'twoName', type: 'twoType' }, - { id: 'threeId', name: 'threeName', type: 'threeType' } - ] - } - mock.onDelete('/corpus/someCorpus/allowed-metadata/twoId/').reply(204) - await store.dispatch('corpora/deleteAllowedMetadata', { corpusId: 'someCorpus', mdId: 'twoId' }) - assert.deepStrictEqual(store.state.corpora.corpusAllowedMetadata, { - someCorpus: [ - { id: 'oneId', name: 'oneName', type: 'oneType' }, - { id: 'threeId', name: 'threeName', type: 'threeType' } - ] - }) - assert.deepStrictEqual(store.history, [ - { - action: 'corpora/deleteAllowedMetadata', - payload: { corpusId: 'someCorpus', mdId: 'twoId' } - }, - { - mutation: 'corpora/removeCorpusAllowedMetadata', - payload: { corpusId: 'someCorpus', mdId: 'twoId' } - } - ]) - }) - it('handles errors', async () => { - mock.onDelete('/corpus/someCorpus/allowed-metadata/missingId/').reply(400) - await assertRejects(async () => await store.dispatch('corpora/deleteAllowedMetadata', { corpusId: 'someCorpus', mdId: 'missingId' })) - assert.deepStrictEqual(store.history, [ - { - action: 'corpora/deleteAllowedMetadata', - payload: { corpusId: 'someCorpus', mdId: 'missingId' } - }, - { - mutation: 'notifications/notify', - payload: { - type: 'error', - text: 'Request failed with status code 400' - } - } - ]) - }) - }) - - describe('updateAllowedMetadata', () => { - it('updates an existing allowed metadata (name, type) couple in a corpus', async () => { - store.state.corpora.corpusAllowedMetadata = { - someCorpus: [ - { id: 'oneId', name: 'oneName', type: 'oneType' }, - { id: 'twoId', name: 'twoName', type: 'twoType' }, - { id: 'threeId', name: 'threeName', type: 'threeType' } - ] - } - const response = { - id: 'threeId', - type: 'fourType', - name: 'threeName' - } - mock.onPatch('/corpus/someCorpus/allowed-metadata/threeId/').reply(200, response) - await store.dispatch('corpora/updateAllowedMetadata', { corpusId: 'someCorpus', mdId: 'threeId', type: 'fourType', name: 'threeName' }) - assert.deepStrictEqual(store.state.corpora.corpusAllowedMetadata, { - someCorpus: [ - { id: 'oneId', name: 'oneName', type: 'oneType' }, - { id: 'twoId', name: 'twoName', type: 'twoType' }, - { id: 'threeId', name: 'threeName', type: 'fourType' } - ] - }) - assert.deepStrictEqual(store.history, [ - { - action: 'corpora/updateAllowedMetadata', - payload: { corpusId: 'someCorpus', mdId: 'threeId', type: 'fourType', name: 'threeName' } - }, - { - mutation: 'corpora/updateCorpusAllowedMetadata', - payload: { corpusId: 'someCorpus', data: { id: 'threeId', type: 'fourType', name: 'threeName' } } - } - ]) - }) - it('handles errors', async () => { - mock.onPatch('/corpus/someCorpus/allowed-metadata/missingId/').reply(400) - await assertRejects(async () => await store.dispatch('corpora/updateAllowedMetadata', { corpusId: 'someCorpus', mdId: 'missingId', name: 'someName', type: 'someType' })) - assert.deepStrictEqual(store.history, [ - { - action: 'corpora/updateAllowedMetadata', - payload: { corpusId: 'someCorpus', mdId: 'missingId', name: 'someName', type: 'someType' } - }, - { - mutation: 'notifications/notify', - payload: { - type: 'error', - text: 'Request failed with status code 400' - } - } - ]) - }) - }) }) }) diff --git a/tests/unit/stores/allowedMetadata.spec.js b/tests/unit/stores/allowedMetadata.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..f30c365750feea4a7aef4e8a633808b8dbab8a17 --- /dev/null +++ b/tests/unit/stores/allowedMetadata.spec.js @@ -0,0 +1,169 @@ +import { assert } from 'chai' +import axios from 'axios' +import { useAllowedMetaDataStore, useNotificationStore } from '@/stores' +import { assertRejects, FakeAxios } from '../testhelpers' +import { createPinia, setActivePinia } from 'pinia' + +describe('allowedMetadata', () => { + describe('actions', () => { + let mock, store, notificationStore + + before('Setting up mocks', () => { + mock = new FakeAxios(axios) + setActivePinia(createPinia()) + store = useAllowedMetaDataStore() + notificationStore = useNotificationStore() + }) + + afterEach(() => { + // Remove any handlers, but leave mocking in place + mock.reset() + store.$reset() + notificationStore.$reset() + }) + + after('Removing Axios mock', () => { + // Remove mocking entirely + mock.restore() + }) + + describe('setCorpusAllowedMetadata', () => { + it('updates allowed metadata on a corpus', () => { + store.allowedMetadata = { + corpus1: [ + { type: 'A', name: 'A' }, + { type: 'B', name: 'B' } + ] + } + const addedAllowedMeta = [ + { type: 'A', name: 'A' }, + { type: 'A', name: 'B' }, + { type: 'B', name: 'A' } + ] + + store.setCorpusAllowedMetadata('corpus1', addedAllowedMeta) + + assert.deepStrictEqual(store.allowedMetadata, { + corpus1: [ + { type: 'A', name: 'A' }, + { type: 'B', name: 'B' }, + { type: 'A', name: 'B' }, + { type: 'B', name: 'A' } + ] + }) + }) + }) + + describe('listAllowedMetadata', () => { + it('recursively lists allowed metadata for a corpus', async () => { + const pages = [{ + count: 3, + number: 1, + next: 'nexpage', + results: [{ type: 'A', name: 'A' }, { type: 'A', name: 'B' }] + }, { + count: 3, + number: 2, + next: null, + results: [{ type: 'B', name: 'B' }] + }] + mock.onGet('/corpus/corpusid/allowed-metadata/', { params: { page: 1 } }).reply(200, pages[0]) + mock.onGet('/corpus/corpusid/allowed-metadata/', { params: { page: 2 } }).reply(200, pages[1]) + + await store.listAllowedMetadata('corpusid') + + assert.deepStrictEqual(store.allowedMetadata, { + corpusid: [ + { name: 'A', type: 'A' }, + { name: 'B', type: 'A' }, + { name: 'B', type: 'B' } + ] + }) + }) + }) + + describe('createAllowedMetadata', () => { + it('creates a new allowed metadata in a corpus', async () => { + store.allowedMetadata = { + someCorpus: [ + { id: 'oneId', name: 'oneName', type: 'oneType' } + ] + } + const response = { + id: 'metaId', + type: 'someType', + name: 'someName' + } + mock.onPost('/corpus/someCorpus/allowed-metadata/').reply(201, response) + + await store.createAllowedMetadata('someCorpus', { type: 'someType', name: 'someName' }) + + assert.deepStrictEqual(store.allowedMetadata, { + someCorpus: [ + { id: 'oneId', name: 'oneName', type: 'oneType' }, + { id: 'metaId', name: 'someName', type: 'someType' } + ] + }) + }) + + it('handles errors', async () => { + mock.onPost('/corpus/someCorpus/allowed-metadata/').reply(400) + await assertRejects(async () => store.createAllowedMetadata('someCorpus', { type: 'someType', name: 'someName' })) + }) + }) + + describe('deleteAllowedMetadata', () => { + it('removes an allowed metadata (name, type) couple from a corpus', async () => { + store.allowedMetadata = { + someCorpus: [ + { id: 'oneId', name: 'oneName', type: 'oneType' }, + { id: 'twoId', name: 'twoName', type: 'twoType' }, + { id: 'threeId', name: 'threeName', type: 'threeType' } + ] + } + mock.onDelete('/corpus/someCorpus/allowed-metadata/twoId/').reply(204) + await store.deleteAllowedMetadata('someCorpus', 'twoId') + assert.deepStrictEqual(store.allowedMetadata, { + someCorpus: [ + { id: 'oneId', name: 'oneName', type: 'oneType' }, + { id: 'threeId', name: 'threeName', type: 'threeType' } + ] + }) + }) + it('handles errors', async () => { + mock.onDelete('/corpus/someCorpus/allowed-metadata/missingId/').reply(400) + await assertRejects(async () => await store.deleteAllowedMetadata('someCorpus', 'missingId')) + }) + }) + + describe('updateAllowedMetadata', () => { + it('updates an existing allowed metadata (name, type) couple in a corpus', async () => { + store.allowedMetadata = { + someCorpus: [ + { id: 'oneId', name: 'oneName', type: 'oneType' }, + { id: 'twoId', name: 'twoName', type: 'twoType' }, + { id: 'threeId', name: 'threeName', type: 'threeType' } + ] + } + const response = { + id: 'threeId', + type: 'fourType', + name: 'threeName' + } + mock.onPatch('/corpus/someCorpus/allowed-metadata/threeId/').reply(200, response) + await store.updateAllowedMetadata('someCorpus', { id: 'threeId', type: 'fourType', name: 'threeName' }) + assert.deepStrictEqual(store.allowedMetadata, { + someCorpus: [ + { id: 'oneId', name: 'oneName', type: 'oneType' }, + { id: 'twoId', name: 'twoName', type: 'twoType' }, + { id: 'threeId', name: 'threeName', type: 'fourType' } + ] + }) + }) + it('handles errors', async () => { + mock.onPatch('/corpus/someCorpus/allowed-metadata/missingId/').reply(400) + await assertRejects(async () => await store.updateAllowedMetadata('someCorpus', { id: 'missingId', name: 'someName', type: 'someType' })) + }) + }) + }) +})