From 127d1beda488ee7482696bb50fecd231db332993 Mon Sep 17 00:00:00 2001 From: ml bonhomme <bonhomme@teklia.com> Date: Tue, 23 Apr 2024 13:23:15 +0000 Subject: [PATCH] No more frontend repository management --- src/api/index.ts | 1 - src/api/repository.ts | 13 --- src/api/rights.ts | 1 - src/components/Memberships/ListMembers.vue | 1 - src/components/Navbar.vue | 3 - src/components/Repos/DeleteModal.vue | 101 ------------------- src/components/Repos/Row.vue | 58 ----------- src/router/index.js | 20 ---- src/store/index.js | 2 - src/stores/index.ts | 1 - src/stores/repos.ts | 47 --------- src/types/worker.ts | 7 -- src/views/Process/Workers/List.vue | 3 +- src/views/Repos/List.vue | 107 --------------------- src/views/Repos/Rights.vue | 85 ---------------- tests/unit/samples.js | 16 --- tests/unit/stores/repos.spec.js | 92 ------------------ 17 files changed, 1 insertion(+), 557 deletions(-) delete mode 100644 src/api/repository.ts delete mode 100644 src/components/Repos/DeleteModal.vue delete mode 100644 src/components/Repos/Row.vue delete mode 100644 src/stores/repos.ts delete mode 100644 src/views/Repos/List.vue delete mode 100644 src/views/Repos/Rights.vue delete mode 100644 tests/unit/stores/repos.spec.js diff --git a/src/api/index.ts b/src/api/index.ts index d6eb60dad..a38547c72 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -20,7 +20,6 @@ export * from './mlresults' export * from './model' export * from './ponos' export * from './process' -export * from './repository' export * from './rights' export * from './search' export * from './selection.js' diff --git a/src/api/repository.ts b/src/api/repository.ts deleted file mode 100644 index d6a134b3e..000000000 --- a/src/api/repository.ts +++ /dev/null @@ -1,13 +0,0 @@ -import axios from 'axios' -import { PageNumberPaginationParameters, unique } from '.' -import { PageNumberPagination, UUID } from '@/types' -import { Repository } from '@/types/worker' - -// List repositories imported on Arkindex -export const listRepositories = unique(async (params: PageNumberPaginationParameters): Promise<PageNumberPagination<Repository>> => (await axios.get('/process/repos/', { params })).data) - -// Retrieve a repository -export const retrieveRepository = unique(async (id: UUID): Promise<Repository> => (await axios.get(`/process/repos/${id}/`)).data) - -// Delete a repository -export const deleteRepository = unique((id: UUID) => axios.delete(`/process/repos/${id}/`)) diff --git a/src/api/rights.ts b/src/api/rights.ts index 7a81c33ef..98f8e83ba 100644 --- a/src/api/rights.ts +++ b/src/api/rights.ts @@ -27,7 +27,6 @@ type MembershipContentTypeParameters = [ { corpus: UUID }, { group: UUID }, { model: UUID }, - { repository: UUID }, { worker: UUID } ] diff --git a/src/components/Memberships/ListMembers.vue b/src/components/Memberships/ListMembers.vue index 3e3199265..b3802d27e 100644 --- a/src/components/Memberships/ListMembers.vue +++ b/src/components/Memberships/ListMembers.vue @@ -119,7 +119,6 @@ export default { // User probably removed their own access, redirect depending on the content type const route = { name: 'corpus-list' } if (this.contentType === 'group') route.name = 'user-profile' - else if (this.contentType === 'repository') route.name = 'repos-list' this.$router.replace(route) } else { this.notify({ type: 'error', text: `An error occurred listing members: ${errorParser(err)}` }) diff --git a/src/components/Navbar.vue b/src/components/Navbar.vue index 81af3de40..8fac45f01 100644 --- a/src/components/Navbar.vue +++ b/src/components/Navbar.vue @@ -82,9 +82,6 @@ > <span>Models</span> </router-link> - <router-link :to="{ name: 'repos-list' }" class="navbar-item" active-class="is-active"> - Repositories - </router-link> <a href="/admin/" class="navbar-item" v-if="isAdmin"> Admin </a> diff --git a/src/components/Repos/DeleteModal.vue b/src/components/Repos/DeleteModal.vue deleted file mode 100644 index 315275011..000000000 --- a/src/components/Repos/DeleteModal.vue +++ /dev/null @@ -1,101 +0,0 @@ -<template> - <Modal - :model-value="modal" - :title="`Delete ${repo.url}`" - v-on:update:model-value="$emit('update:modal', false)" - > - <p> - Are you sure you want to delete repository <strong>{{ repo.url }}</strong>? - </p> - <br /> - <p> - This action is irreversible and will remove: - <ul class="list"> - <li v-if="workers">all workers imported from this repository and all their versions (<strong>{{ workers }}</strong>)</li> - <li>all references to this repository from any resource it created</li> - </ul> - </p> - <br /> - <p>Note: this will not affect resources created from this repository nor its workers (e.g. elements, classifications, metadata, transcriptions, entities).</p> - - <template v-slot:footer="{ close }"> - <span - class="button" - v-on:click="close" - title="Cancel deletion" - > - Cancel - </span> - <span - class="button is-danger" - :disabled="loading || null" - :class="{ 'is-loading': loading }" - title="Delete this repository, its associated workers and versions" - v-on:click="remove" - > - <i class="icon-trash"></i> - Delete - </span> - </template> - </Modal> -</template> - -<script lang="ts"> -import { PropType, defineComponent } from 'vue' -import { mapActions } from 'pinia' -import { errorParser } from '@/helpers' -import { useRepositoryStore, useNotificationStore } from '@/stores' -import { Repository } from '@/types/worker' -import Modal from '@/components/Modal.vue' - -export default defineComponent({ - components: { - Modal - }, - emits: { - 'update:modal': (value: boolean) => typeof value === 'boolean', - delete: (value: boolean) => typeof value === 'boolean' - }, - props: { - modal: { - type: Boolean, - required: true - }, - repo: { - type: Object as PropType<Repository>, - required: true - } - }, - data: () => ({ - loading: false - }), - methods: { - ...mapActions(useRepositoryStore, ['deleteRepository']), - ...mapActions(useNotificationStore, ['notify']), - async remove () { - this.loading = true - try { - await this.deleteRepository(this.repo.id) - // Redirect the user to the repositories list - this.$emit('delete', true) - } catch (err) { - this.notify({ type: 'error', text: `An error occurred deleting the repository: ${errorParser(err)}` }) - } finally { - this.loading = false - } - } - }, - computed: { - workers () { - if (!this.repo.workers.length) return - return this.repo.workers.map(w => w.name).join(', ') - } - } -}) -</script> - -<style scoped> -ul { - list-style: inside; -} -</style> diff --git a/src/components/Repos/Row.vue b/src/components/Repos/Row.vue deleted file mode 100644 index 4a3ca0c8b..000000000 --- a/src/components/Repos/Row.vue +++ /dev/null @@ -1,58 +0,0 @@ -<template> - <tr> - <td> - <a :href="repo.url" target="_blank">{{ repo.url }}</a> - </td> - <td> - <template v-if="!repo.workers.length"> - <i class="notification is-paddingless is-info">No worker available</i> - </template> - <div v-else v-for="worker in repo.workers" :key="worker.id"> - <router-link :to="{ name: 'worker-manage', params: { workerId: worker.id } }"> - {{ worker.name }} - </router-link> - </div> - </td> - <td v-if="hasFeature('enterprise')"> - <router-link :to="{ name: 'repo-rights', params: { repoId: repo.id } }"> - {{ repo.authorized_users }} user<template v-if="repo.authorized_users > 1">s</template> - </router-link> - </td> - <td class="shrink" v-if="isVerified"> - <div class="field"> - <p class="control"> - <button - class="button is-danger" - title="Delete this repository" - v-on:click="$emit('remove', repo)" - > - <i class="icon-trash"></i> - </button> - </p> - </div> - </td> - </tr> -</template> - -<script lang="ts"> -import { PropType, defineComponent } from 'vue' -import { mapGetters } from 'vuex' -import { Repository } from '@/types/worker' - -export default defineComponent({ - emits: { - remove (value: Repository) { - return value.id !== undefined - } - }, - props: { - repo: { - type: Object as PropType<Repository>, - required: true - } - }, - computed: { - ...mapGetters('auth', ['isVerified', 'hasFeature']) - } -}) -</script> diff --git a/src/router/index.js b/src/router/index.js index 0d3a1ae0f..cf43d2796 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -26,8 +26,6 @@ const CorpusList = () => import(/* webpackChunkName: "users" */ '@/views/Corpus/ const CorpusDetails = () => import(/* webpackChunkName: "users" */ '@/views/Corpus/Main') const ImportFromFiles = () => import(/* webpackChunkName: "users" */ '@/views/Imports/ImportFromFiles') const ImportFromBucket = () => import(/* webpackChunkName: "users" */ '@/views/Imports/ImportFromBucket') -const ReposList = () => import(/* webpackChunkName: "users" */ '@/views/Repos/List') -const ReposRights = () => import(/* webpackChunkName: "users" */ '@/views/Repos/Rights') const ProcessList = () => import(/* webpackChunkName: "users" */ '@/views/Process/List') const ModelsList = () => import(/* webpackChunkName: "users" */ '@/views/Model/List') const Model = () => import(/* webpackChunkName: "users" */ '@/views/Model') @@ -144,13 +142,6 @@ const routes = [ meta: { requiresLogin: true, requiresVerified: true }, props: true }, - { - path: '/process/repos', - name: 'repos-list', - component: ReposList, - meta: { requiresLogin: true, requiresVerified: true }, - props: true - }, { path: `/dataset/:datasetId(${UUID})`, name: 'dataset-details', @@ -158,17 +149,6 @@ const routes = [ meta: { requiresLogin: true, requiresVerified: true }, props: true }, - { - path: `/process/repos/:repoId(${UUID})/rights`, - name: 'repo-rights', - component: ReposRights, - meta: { - requiresLogin: true, - requiresVerified: true, - requiresFeatures: ['enterprise'] - }, - props: true - }, { path: '/process/workers', name: 'workers-list', diff --git a/src/store/index.js b/src/store/index.js index 54b01d3d4..59c479d53 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -14,7 +14,6 @@ import { useRightsStore, useSearchStore, useWorkerStore, - useRepositoryStore, useMetaDataStore } from '@/stores' @@ -54,7 +53,6 @@ export const piniaStores = [ useRightsStore, useSearchStore, useWorkerStore, - useRepositoryStore, useMetaDataStore ] diff --git a/src/stores/index.ts b/src/stores/index.ts index f13a26bfb..b57e903c8 100644 --- a/src/stores/index.ts +++ b/src/stores/index.ts @@ -15,5 +15,4 @@ export { useDatasetStore } from './dataset' export { useExportStore } from './exports' export { useEntityStore } from './entity' export { useTranscriptionStore } from './transcription' -export { useRepositoryStore } from './repos' export { useMetaDataStore } from './metadata' diff --git a/src/stores/repos.ts b/src/stores/repos.ts deleted file mode 100644 index d760b9253..000000000 --- a/src/stores/repos.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { defineStore } from 'pinia' -import * as api from '@/api' -import { useWorkerStore } from '@/stores' -import { UUID } from '@/types' -import { Repository, WorkerLight } from '@/types/worker' - -interface State { - /** - * Repository details mapped by ID - */ - repositories: { - [repositoryId: UUID]: Repository - } -} - -export const useRepositoryStore = defineStore('repos', { - state: (): State => ({ - repositories: {} - }), - actions: { - async retrieveRepository (id: UUID) { - const repo = await api.retrieveRepository(id) - this.repositories[repo.id] = repo - }, - async listRepository (page = 1) { - // List repositories imported on Arkindex - const data = await api.listRepositories({ page }) - this.repositories = { - ...this.repositories, - ...Object.fromEntries(data.results.map((repo: Repository) => [repo.id, repo])) - } - // Set workers in the appropriate store to retrieve them following a link later - const workerStore = useWorkerStore() - const workers = data.results.flatMap(({ workers }) => workers) - workerStore.workers = { - ...workerStore.workers, - ...Object.fromEntries(workers.map((worker: WorkerLight) => [worker.id, worker])) - } - return data - }, - async deleteRepository (id: UUID) { - const data = await api.deleteRepository(id) - delete this.repositories[id] - return data - } - } -}) diff --git a/src/types/worker.ts b/src/types/worker.ts index 5646d35c3..b87e2b22c 100644 --- a/src/types/worker.ts +++ b/src/types/worker.ts @@ -55,10 +55,3 @@ export type WorkerVersion = { revision: RevisionWithRefs version: null }) - -export interface Repository { - id: UUID, - url: string, - workers: Array<WorkerLight>, - authorized_users: number -} diff --git a/src/views/Process/Workers/List.vue b/src/views/Process/Workers/List.vue index 896b793c1..a97c608b6 100644 --- a/src/views/Process/Workers/List.vue +++ b/src/views/Process/Workers/List.vue @@ -160,7 +160,7 @@ import { import { errorParser } from '@/helpers' import { truncateMixin } from '@/mixins' -import { useWorkerStore, useNotificationStore, useRepositoryStore } from '@/stores' +import { useWorkerStore, useNotificationStore } from '@/stores' import VersionList from '@/components/Process/Workers/Versions/List' import Paginator from '@/components/Paginator.vue' import ListMembers from '@/components/Memberships/ListMembers.vue' @@ -234,7 +234,6 @@ export default { }), computed: { ...mapState(useWorkerStore, ['workerTypes']), - ...mapState(useRepositoryStore, ['repos']), ...mapVuexGetters('auth', ['hasFeature']), readMoreText () { if (this.expandDescription) return 'collapse description' diff --git a/src/views/Repos/List.vue b/src/views/Repos/List.vue deleted file mode 100644 index 1129e2eba..000000000 --- a/src/views/Repos/List.vue +++ /dev/null @@ -1,107 +0,0 @@ -<template> - <main class="container is-fluid"> - <h1 class="title">Repositories</h1> - <h2 class="subtitle">Manage Git repositories</h2> - <Paginator - :response="reposPage" - v-slot="{ results }" - :loading="loading" - > - <table class="table is-fullwidth is-hoverable"> - <thead> - <tr> - <th>URL</th> - <th>Workers</th> - <th v-if="hasFeature('enterprise')">Users</th> - <th v-if="isVerified">Actions</th> - </tr> - </thead> - <tbody> - <Row - v-for="repo in results" - :key="repo.id" - :repo="repo" - v-on:remove="repoDeletion = repo" - /> - </tbody> - </table> - </Paginator> - <DeleteModal - v-if="repoDeletion" - v-model:modal="deleteModal" - :repo="repoDeletion" - v-on:delete="handleDeletion" - /> - </main> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue' -import { useRepositoryStore, useNotificationStore } from '@/stores' -import { mapActions, mapState } from 'pinia' -import { mapGetters } from 'vuex' -import { errorParser } from '@/helpers' -import Paginator from '@/components/Paginator.vue' -import Row from '@/components/Repos/Row.vue' -import DeleteModal from '@/components/Repos/DeleteModal.vue' -import { PageNumberPagination } from '@/types' -import { Repository } from '@/types/worker' - -const Component = defineComponent({ - components: { - Paginator, - Row, - DeleteModal - }, - data: () => ({ - loading: false, - // Stores a repository to delete. Automatically prompt the deletion modal - repoDeletion: null as Repository | null, - deleteModal: false, - reposPage: null as PageNumberPagination<Repository> | null - }), - beforeRouteEnter ({ query }, from, next) { - /* - * In order to not get typing error related to 'vm', we name the component which allos us to tell TypeScript that vm - * is of the same type as that component. - */ - next(vm => (vm as InstanceType<typeof Component>).updatePage(parseInt(`${query.page ?? 1}`))) - }, - beforeRouteUpdate ({ query }) { - this.updatePage(parseInt(`${query.page ?? 1}`)) - }, - computed: { - ...mapGetters('auth', ['isVerified', 'hasFeature']), - ...mapState(useRepositoryStore, ['repositories']) - }, - methods: { - ...mapActions(useNotificationStore, ['notify']), - ...mapActions(useRepositoryStore, ['listRepository']), - async updatePage (page: number) { - this.loading = true - try { - this.reposPage = await this.listRepository(page) - } catch (err) { - this.notify({ type: 'error', text: `An error occurred listing repositories: ${errorParser(err)}` }) - } finally { - this.loading = false - } - }, - handleDeletion () { - // Reload repositories page - this.repoDeletion = null - this.updatePage(parseInt(`${this.$route.query.page ?? 1}`)) - } - }, - watch: { - repoDeletion (repo) { - if (repo) this.deleteModal = true - }, - deleteModal (open) { - if (!open) this.repoDeletion = null - } - } -}) - -export default Component -</script> diff --git a/src/views/Repos/Rights.vue b/src/views/Repos/Rights.vue deleted file mode 100644 index 447d4ed41..000000000 --- a/src/views/Repos/Rights.vue +++ /dev/null @@ -1,85 +0,0 @@ -<template> - <main class="container is-fluid"> - <div v-if="loading" class="notification is-info">Loading...</div> - <div v-else-if="!repo" class="notification is-danger">Not found.</div> - <template v-else> - <div class="level"> - <div class="level-left is-block"> - <div class="subtitle is-5">Repository</div> - <h1 class="title">{{ repo.url }}</h1> - </div> - <div - class="level-right button is-danger" - v-on:click="deleteModal = true" - > - <i class="icon-trash"></i> - Delete - </div> - </div> - - <hr /> - <h2 class="title is-4">Members</h2> - <ListMembers content-type="repository" :content-id="repoId" /> - <DeleteModal - v-model:modal="deleteModal" - :repo="repo" - v-on:delete="postDelete" - /> - </template> - </main> -</template> - -<script lang="ts"> -import { PropType, defineComponent } from 'vue' -import { UUID } from '@/types' -import { useNotificationStore, useRepositoryStore } from '@/stores' -import { mapState, mapActions } from 'pinia' -import { errorParser } from '@/helpers' -import ListMembers from '@/components/Memberships/ListMembers.vue' -import DeleteModal from '@/components/Repos/DeleteModal.vue' -import { Repository } from '@/types/worker' - -export default defineComponent({ - components: { - ListMembers, - DeleteModal - }, - props: { - repoId: { - type: String as PropType<UUID>, - required: true - } - }, - data: () => ({ - loading: false, - deleteModal: false - }), - created () { - if (!this.repo) this.load() - }, - computed: { - ...mapState(useRepositoryStore, ['repositories']), - repo (): Repository { - return this.repositories[this.repoId] - } - }, - methods: { - ...mapActions(useRepositoryStore, ['retrieveRepository']), - ...mapActions(useNotificationStore, ['notify']), - async load () { - try { - this.loading = true - await this.retrieveRepository(this.repoId) - } catch (err) { - this.notify({ type: 'error', text: `An error occurred retrieving the repository: ${errorParser(err)}` }) - } finally { - this.loading = false - } - }, - postDelete () { - // Replace the route as the repository does not exists anymore - this.$router.replace({ name: 'repos-list' }) - } - } -}) -</script> diff --git a/tests/unit/samples.js b/tests/unit/samples.js index 35ed4b638..07811f651 100644 --- a/tests/unit/samples.js +++ b/tests/unit/samples.js @@ -215,22 +215,6 @@ export const farmsSample = makeSampleResults([ name: 'Corn farm' }]) -export const repoSample = { - id: 'repoid', - url: 'http://repo', - corpus: 'himanis', - workers: [{ id: 'worker 1' }] -} - -export const reposSample = makeSampleResults([repoSample]) - -export const providersSample = [ - { - name: 'GitLabProvider', - display_name: 'GitLab' - } -] - export const credentialsSample = makeSampleResults([ { id: 'credid', diff --git a/tests/unit/stores/repos.spec.js b/tests/unit/stores/repos.spec.js deleted file mode 100644 index 5b5cb0343..000000000 --- a/tests/unit/stores/repos.spec.js +++ /dev/null @@ -1,92 +0,0 @@ -import axios from 'axios' -import { assert } from 'chai' -import { pick } from 'lodash' -import { setActivePinia, createPinia } from 'pinia' -import { useWorkerStore, useRepositoryStore } from '@/stores' -import { reposSample, repoSample } from '../samples' -import { FakeAxios } from '../testhelpers' - -describe('repos', () => { - describe('actions', () => { - let mock, store, workerStore - - before('Setting up Axios mock', () => { - mock = new FakeAxios(axios) - setActivePinia(createPinia()) - store = useRepositoryStore() - workerStore = useWorkerStore() - }) - - afterEach(() => { - // Remove any handlers, but leave mocking in place - mock.reset() - store.$reset() - workerStore.$reset() - }) - - after('Removing Axios mock', () => { - // Remove mocking entirely - mock.restore() - }) - - describe('listRepository', () => { - it('lists existing repositories', async () => { - const sample = { - ...reposSample, - count: 2, - results: [ - ...reposSample.results, - { - id: 'other_repo', - workers: [ - { - id: 'worker 2' - }, { - id: 'worker 3' - } - ] - } - ] - } - - mock.onGet('/process/repos/').reply(200, sample) - - const page = await store.listRepository() - assert.deepStrictEqual(mock.history.all.map(req => pick(req, ['method', 'url'])), [ - { - method: 'get', - url: '/process/repos/' - } - ]) - assert.deepStrictEqual(page, sample) - assert.deepStrictEqual(Object.keys(workerStore.workers), ['worker 1', 'worker 2', 'worker 3']) - }) - }) - - describe('deleteRepository', () => { - it('deletes a repo', async () => { - mock.onDelete('/process/repos/repoid/').reply(204) - - store.repositories = { repoid: {}, secondId: {} } - await store.deleteRepository('repoid') - - assert.deepStrictEqual(mock.history.all.map(req => pick(req, ['method', 'url'])), [ - { - method: 'delete', - url: '/process/repos/repoid/' - } - ]) - }) - }) - - describe('retrieveRepository', () => { - it('retrieves a repo', async () => { - mock.onGet('/process/repos/repoid/').reply(200, repoSample) - await store.retrieveRepository('repoid') - assert.deepStrictEqual(store.repositories, { - repoid: repoSample - }) - }) - }) - }) -}) -- GitLab