From eec2d60a7641a0ef4956784d099ce1dd1c68b392 Mon Sep 17 00:00:00 2001 From: mlbonhomme <bonhomme@teklia.com> Date: Thu, 16 Jan 2025 12:53:27 +0100 Subject: [PATCH 1/4] simple mode / advanced mode / all version --- src/api/worker.ts | 3 + .../Process/Workers/Versions/List.vue | 174 ++++++++++++------ src/stores/workers.ts | 10 + tests/unit/stores/workers.spec.js | 16 ++ 4 files changed, 148 insertions(+), 55 deletions(-) diff --git a/src/api/worker.ts b/src/api/worker.ts index dab24d177..69d4b745f 100644 --- a/src/api/worker.ts +++ b/src/api/worker.ts @@ -34,6 +34,9 @@ export const listWorkerVersions = unique(async (workerId: UUID, params: WorkerVe // Retrieve a worker version export const retrieveWorkerVersion = unique(async (id: UUID): Promise<WorkerVersion> => (await axios.get(`/workers/versions/${id}/`)).data) +// Retrieve the recommended worker version for a worker +export const retrieveRecommendedWorkerVersion = unique(async(workerId: UUID): Promise<WorkerVersion> => (await axios.get(`/workers/${workerId}/versions/recommended/`)).data) + // Retrieve a worker export const retrieveWorker = unique(async (id: UUID): Promise<Worker> => (await axios.get(`/workers/${id}/`)).data) diff --git a/src/components/Process/Workers/Versions/List.vue b/src/components/Process/Workers/Versions/List.vue index 1f2a62822..4f607497c 100644 --- a/src/components/Process/Workers/Versions/List.vue +++ b/src/components/Process/Workers/Versions/List.vue @@ -1,58 +1,99 @@ <template> - <template v-if="worker.repository_url"> - <input - id="switchAll" - type="checkbox" - class="switch is-rtl is-rounded is-info" - v-model="advancedMode" - /> - <label class="is-pulled-right ml-3" for="switchAll">Display all versions</label> + <div class="columns"> + <div class="column"><h2 class="title is-4">Versions</h2></div> + <div class="column is-narrow"> + <span class="select"> + <select + v-model="advancedMode" + > + <option :value="false">Simple mode</option> + <option :value="true">Advanced mode</option> + </select> + </span> + </div> + </div> + <template v-if="advancedMode"> + <div class="columns"> + <div class="column"></div> + <div class="column is-narrow"> + <template v-if="worker.repository_url"> + <input + id="switchAll" + type="checkbox" + class="switch is-rtl is-rounded is-info" + v-model="allVersions" + /> + <label class="ml-3" for="switchAll">Display all versions</label> + </template> + <CreateForm :worker-id="worker.id" class="is-pulled-right ml-3" v-on:created="fetchVersions" /> + </div> + </div> + + <span class="is-clearfix"></span> + <div v-if="versionsError" class="notification is-warning">{{ versionsError }}</div> + <Paginator + v-else + :response="versionsPage" + :loading="loading" + v-slot="{ results }" + v-model:page="page" + :page-size="pageSize" + singular="version" + plural="versions" + > + <table class="table is-fullwidth is-hoverable"> + <thead> + <tr> + <th>Id</th> + <th>Version</th> + <th>Tag</th> + <th>Branch</th> + <th>State</th> + <th>Docker image</th> + <th>Created</th> + <th v-if="processId || selectable || haveRevisions(results)">Actions</th> + </tr> + </thead> + <tbody> + <Row + :process-id="processId" + :selectable="selectable" + :local="local" + :key="version.id" + :version="version" + v-for="version in results" + v-on:selected-version="$emit('selected-version', $event)" + /> + </tbody> + </table> + </Paginator> + </template> + <template v-else> + <div v-if="loading"> + <span class="loader is-size-2 mx-auto"></span> + </div> + <div class="columns is-vcentered" v-else-if="recommendedWorkerVersions[worker.id]"> + <div class="column is-narrow"> + Worker version <span class="tag">{{ recommendedWorkerVersions[worker.id].branch || recommendedWorkerVersions[worker.id].tag }}</span> (recommended) + </div> + <div class="column"> + <button class="button is-success ml-2"> + use this version + </button> + </div> + </div> + <div class="notification is-error" v-else-if="recommendedVersionError"> + An error occurred while loading the recommended worker version: {{ recommendedVersionError }} + </div> + <div class="notification is-warning" v-else> + There is no recommended worker version for this worker. Please select the "advanced mode" in the menu above to see all worker versions and select one. + </div> </template> - <CreateForm :worker-id="worker.id" class="is-pulled-right" v-on:created="fetchVersions" /> - <h2 class="title is-4">Versions</h2> - <span class="is-clearfix"></span> - <div v-if="versionsError" class="notification is-warning">{{ versionsError }}</div> - <Paginator - v-else - :response="versionsPage" - :loading="loading" - v-slot="{ results }" - v-model:page="page" - :page-size="pageSize" - singular="version" - plural="versions" - > - <table class="table is-fullwidth is-hoverable"> - <thead> - <tr> - <th>Id</th> - <th>Version</th> - <th>Tag</th> - <th>Branch</th> - <th>State</th> - <th>Docker image</th> - <th>Created</th> - <th v-if="processId || selectable || haveRevisions(results)">Actions</th> - </tr> - </thead> - <tbody> - <Row - :process-id="processId" - :selectable="selectable" - :local="local" - :key="version.id" - :version="version" - v-for="version in results" - v-on:selected-version="$emit('selected-version', $event)" - /> - </tbody> - </table> - </Paginator> </template> <script lang="ts"> import { PropType, defineComponent } from 'vue' -import { mapActions } from 'pinia' +import { mapActions, mapState } from 'pinia' import { errorParser } from '@/helpers' import Paginator from '@/components/Paginator.vue' import Row from './Row.vue' @@ -99,13 +140,17 @@ export default defineComponent({ }, data: () => ({ versionsError: null as string | null, + recommendedVersionError: null as string | null, versionsPage: null as PageNumberPagination<WorkerVersion> | null, loading: false, page: 1, - // If set to false, only display versions with a tag - advancedMode: false + // If set to false, only show the "recommended" version for this worker + advancedMode: false, + // If set to false, only display versions with a tag or from the master/main branch + allVersions: false }), computed: { + ...mapState(useWorkerStore, ['recommendedWorkerVersions']), pageSize () { // Use a small page size when not in process creation mode because members are displayed on the same column return this.processId ? 20 : 5 @@ -113,13 +158,14 @@ export default defineComponent({ }, methods: { ...mapActions(useNotificationStore, ['notify']), - ...mapActions(useWorkerStore, ['listVersions']), + ...mapActions(useWorkerStore, ['listVersions', 'getRecommendedWorkerVersion']), async fetchVersions () { + if (!this.advancedMode) return this.versionsPage = null this.versionsError = null this.loading = true let mode = 'complete' as WorkerVersionListParameters['mode'] - if (this.worker.repository_url) mode = this.advancedMode ? 'complete' : 'simple' + if (this.worker.repository_url) mode = this.allVersions ? 'complete' : 'simple' const payload: WorkerVersionListParameters = { page: this.page, page_size: this.pageSize, @@ -135,19 +181,37 @@ export default defineComponent({ }, haveRevisions (results: WorkerVersion[]) { return (results.some(workerVersion => workerVersion.revision_url !== null)) + }, + async fetchRecommendedVersion () { + this.loading = true + try { + await this.getRecommendedWorkerVersion(this.worker.id) + } catch (err) { + this.recommendedVersionError = errorParser(err) + } finally { + this.loading = false + } } }, watch: { page: 'fetchVersions', + advancedMode: { + immediate: true, + handler (newValue) { + if (String(newValue) === 'false') this.fetchRecommendedVersion() + else this.fetchVersions() + } + }, worker: { immediate: true, handler (newValue, oldValue) { if (oldValue?.id === newValue.id) return this.page = 1 - this.fetchVersions() + if (this.advancedMode) this.fetchVersions() + else this.fetchRecommendedVersion() } }, - advancedMode: { + allVersions: { handler () { this.page = 1 this.fetchVersions() diff --git a/src/stores/workers.ts b/src/stores/workers.ts index a2960a414..dc99803f4 100644 --- a/src/stores/workers.ts +++ b/src/stores/workers.ts @@ -19,6 +19,7 @@ import { listWorkerVersions, PageNumberPaginationParameters, retrieveFeatureWorkerVersion, + retrieveRecommendedWorkerVersion, retrieveWorker, retrieveWorkerConfiguration, retrieveWorkerRun, @@ -49,6 +50,9 @@ interface State { workerVersions: { [workerVersionId: UUID]: WorkerVersion }, + recommendedWorkerVersions: { + [workerId: UUID]: WorkerVersion + }, featureWorkerVersionIds: { [feature in ArkindexFeature]?: UUID }, @@ -63,6 +67,7 @@ export const useWorkerStore = defineStore('worker', { workerTypes: {}, workerConfigurations: {}, workerVersions: {}, + recommendedWorkerVersions: {}, featureWorkerVersionIds: {}, workerRuns: {} }), @@ -205,6 +210,11 @@ export const useWorkerStore = defineStore('worker', { this.workerVersions[workerVersionId] = await retrieveWorkerVersion(workerVersionId) }, + async getRecommendedWorkerVersion (workerId: UUID) { + const resp = await retrieveRecommendedWorkerVersion(workerId) + if (resp.id) this.recommendedWorkerVersions[workerId] = resp + }, + async getFeatureWorkerVersion (feature: ArkindexFeature) { try { const workerVersion = await retrieveFeatureWorkerVersion(feature) diff --git a/tests/unit/stores/workers.spec.js b/tests/unit/stores/workers.spec.js index 845746911..803a86849 100644 --- a/tests/unit/stores/workers.spec.js +++ b/tests/unit/stores/workers.spec.js @@ -561,5 +561,21 @@ describe('workers', () => { }) }) }) + + describe('getRecommendedWorkerVersion', () => { + it('retrieves the recommended worker version for a worker', async () => { + mock.onGet('/workers/workerid/versions/recommended/').reply(200, workerVersionsSample['results'][0]) + + await store.getRecommendedWorkerVersion('workerid') + + assert.deepStrictEqual(mock.history.all.map(req => pick(req, ['method', 'url'])), [ + { + method: 'get', + url: '/workers/workerid/versions/recommended/' + } + ]) + assert.deepStrictEqual(store.recommendedWorkerVersions, { workerid: workerVersionsSample['results'][0] }) + }) + }) }) }) -- GitLab From 069094496375915fe48a4f76cdf8e0c822e3fc27 Mon Sep 17 00:00:00 2001 From: mlbonhomme <bonhomme@teklia.com> Date: Thu, 16 Jan 2025 14:38:31 +0100 Subject: [PATCH 2/4] minmal worker version row --- .../Process/Workers/Versions/List.vue | 39 +++++--- .../Process/Workers/Versions/SimpleRow.vue | 97 +++++++++++++++++++ tests/unit/stores/workers.spec.js | 4 +- 3 files changed, 123 insertions(+), 17 deletions(-) create mode 100644 src/components/Process/Workers/Versions/SimpleRow.vue diff --git a/src/components/Process/Workers/Versions/List.vue b/src/components/Process/Workers/Versions/List.vue index 4f607497c..d118557d4 100644 --- a/src/components/Process/Workers/Versions/List.vue +++ b/src/components/Process/Workers/Versions/List.vue @@ -1,6 +1,6 @@ <template> - <div class="columns"> - <div class="column"><h2 class="title is-4">Versions</h2></div> + <div class="columns mb-0"> + <div class="column"><h2 class="title is-4">{{ versionsTitle }}</h2></div> <div class="column is-narrow"> <span class="select"> <select @@ -29,7 +29,6 @@ </div> </div> - <span class="is-clearfix"></span> <div v-if="versionsError" class="notification is-warning">{{ versionsError }}</div> <Paginator v-else @@ -72,21 +71,25 @@ <div v-if="loading"> <span class="loader is-size-2 mx-auto"></span> </div> - <div class="columns is-vcentered" v-else-if="recommendedWorkerVersions[worker.id]"> - <div class="column is-narrow"> - Worker version <span class="tag">{{ recommendedWorkerVersions[worker.id].branch || recommendedWorkerVersions[worker.id].tag }}</span> (recommended) - </div> - <div class="column"> - <button class="button is-success ml-2"> - use this version - </button> - </div> - </div> + <table class="table" v-else-if="recommendedWorkerVersions[worker.id]"> + <thead> + <tr> + <th>Id</th> + <th>Tag</th> + <th>Branch</th> + <th>State</th> + <th v-if="processId">Actions</th> + </tr> + </thead> + <tbody> + <SimpleRow :process-id="processId" :version="recommendedWorkerVersions[worker.id]" /> + </tbody> + </table> <div class="notification is-error" v-else-if="recommendedVersionError"> An error occurred while loading the recommended worker version: {{ recommendedVersionError }} </div> <div class="notification is-warning" v-else> - There is no recommended worker version for this worker. Please select the "advanced mode" in the menu above to see all worker versions and select one. + There is no recommended worker version for this worker. Please select the "advanced mode" in the menu above to see all worker versions. </div> </template> </template> @@ -97,6 +100,7 @@ import { mapActions, mapState } from 'pinia' import { errorParser } from '@/helpers' import Paginator from '@/components/Paginator.vue' import Row from './Row.vue' +import SimpleRow from './SimpleRow.vue' import { useNotificationStore, useWorkerStore } from '@/stores' import { WorkerVersionListParameters } from '@/api' import { PageNumberPagination } from '@/types' @@ -107,7 +111,8 @@ export default defineComponent({ components: { CreateForm, Paginator, - Row + Row, + SimpleRow }, emits: ['selected-version'], props: { @@ -154,6 +159,10 @@ export default defineComponent({ pageSize () { // Use a small page size when not in process creation mode because members are displayed on the same column return this.processId ? 20 : 5 + }, + versionsTitle () { + if (this.advancedMode) return 'Versions' + return 'Recommended version' } }, methods: { diff --git a/src/components/Process/Workers/Versions/SimpleRow.vue b/src/components/Process/Workers/Versions/SimpleRow.vue new file mode 100644 index 000000000..1c8b0d710 --- /dev/null +++ b/src/components/Process/Workers/Versions/SimpleRow.vue @@ -0,0 +1,97 @@ +<template> + <tr> + <td :class="{ 'shrink': version.revision_url }"> + <ItemId :item-id="version.id" /> + </td> + <td> + <span v-if="version.tag" class="tag">{{ version.tag }}</span> + <template v-else>—</template> + </td> + <td> + <span v-if="version.branch" class="tag">{{ version.branch }}</span> + <template v-else>—</template> + </td> + <td> + <span + v-if="version.state" + class="tag is-capitalized" + :class="stateClass(version.state)" + > + {{ version.state }} + </span> + </td> + <td class="shrink" v-if="processId"> + <div class="has-text-centered"> + <!-- Allows to select a worker version --> + <a + class="button is-success is-small" + v-on:click="addWorkerRun" + :class="{ 'is-loading': loading }" + title="Add the recommended worker version to the process" + > + <i class="icon-plus"></i> + </a> + </div> + </td> + </tr> +</template> + +<script> +import { + mapState as mapVuexState, + mapActions as mapVuexActions +} from 'vuex' +import { mapActions } from 'pinia' + +import { errorParser } from '@/helpers' +import { WORKER_VERSION_STATE_COLORS } from '@/config' +import { truncateMixin } from '@/mixins' +import { useNotificationStore } from '@/stores' + +import ItemId from '@/components/ItemId.vue' + +export default { + emits: ['selected-version'], + mixins: [ + truncateMixin + ], + components: { + ItemId + }, + props: { + version: { + type: Object, + required: true + }, + processId: { + type: String, + default: '' + } + }, + data: () => ({ + loading: false + }), + computed: { + ...mapVuexState('process', ['processWorkerRuns']) + }, + methods: { + ...mapVuexActions('process', ['createWorkerRun', 'deleteWorkerRun']), + ...mapActions(useNotificationStore, ['notify']), + async addWorkerRun () { + this.$emit('selected-version', this.version) + if (this.loading || !this.processId) return + this.loading = true + try { + await this.createWorkerRun({ processId: this.processId, workerRun: { worker_version_id: this.version.id, parents: [] } }) + } catch (err) { + this.notify({ type: 'error', text: errorParser(err) }) + } finally { + this.loading = false + } + }, + stateClass (state) { + return WORKER_VERSION_STATE_COLORS[state] + } + } +} +</script> diff --git a/tests/unit/stores/workers.spec.js b/tests/unit/stores/workers.spec.js index 803a86849..b539f2132 100644 --- a/tests/unit/stores/workers.spec.js +++ b/tests/unit/stores/workers.spec.js @@ -564,7 +564,7 @@ describe('workers', () => { describe('getRecommendedWorkerVersion', () => { it('retrieves the recommended worker version for a worker', async () => { - mock.onGet('/workers/workerid/versions/recommended/').reply(200, workerVersionsSample['results'][0]) + mock.onGet('/workers/workerid/versions/recommended/').reply(200, workerVersionsSample.results[0]) await store.getRecommendedWorkerVersion('workerid') @@ -574,7 +574,7 @@ describe('workers', () => { url: '/workers/workerid/versions/recommended/' } ]) - assert.deepStrictEqual(store.recommendedWorkerVersions, { workerid: workerVersionsSample['results'][0] }) + assert.deepStrictEqual(store.recommendedWorkerVersions, { workerid: workerVersionsSample.results[0] }) }) }) }) -- GitLab From d3b0ccbc79f68c331a0fa54ea25feb220be16715 Mon Sep 17 00:00:00 2001 From: mlbonhomme <bonhomme@teklia.com> Date: Tue, 21 Jan 2025 17:09:39 +0100 Subject: [PATCH 3/4] wip apply review --- .../Process/Workers/Versions/List.vue | 62 ++++++++++--------- src/stores/workers.ts | 15 ++++- tests/unit/stores/workers.spec.js | 14 +++++ 3 files changed, 60 insertions(+), 31 deletions(-) diff --git a/src/components/Process/Workers/Versions/List.vue b/src/components/Process/Workers/Versions/List.vue index d118557d4..3f8b96aef 100644 --- a/src/components/Process/Workers/Versions/List.vue +++ b/src/components/Process/Workers/Versions/List.vue @@ -67,31 +67,29 @@ </table> </Paginator> </template> - <template v-else> - <div v-if="loading"> - <span class="loader is-size-2 mx-auto"></span> - </div> - <table class="table" v-else-if="recommendedWorkerVersions[worker.id]"> - <thead> - <tr> - <th>Id</th> - <th>Tag</th> - <th>Branch</th> - <th>State</th> - <th v-if="processId">Actions</th> - </tr> - </thead> - <tbody> - <SimpleRow :process-id="processId" :version="recommendedWorkerVersions[worker.id]" /> - </tbody> - </table> - <div class="notification is-error" v-else-if="recommendedVersionError"> - An error occurred while loading the recommended worker version: {{ recommendedVersionError }} - </div> - <div class="notification is-warning" v-else> - There is no recommended worker version for this worker. Please select the "advanced mode" in the menu above to see all worker versions. - </div> - </template> + <div v-else-if="loading"> + <span class="loader is-size-2 mx-auto"></span> + </div> + <table class="table" v-else-if="recommendedVersion"> + <thead> + <tr> + <th>Id</th> + <th>Tag</th> + <th>Branch</th> + <th>State</th> + <th v-if="processId">Actions</th> + </tr> + </thead> + <tbody> + <SimpleRow :process-id="processId" :version="recommendedVersion" /> + </tbody> + </table> + <div class="notification is-danger" v-else-if="recommendedVersionError"> + An error occurred while loading the recommended worker version: {{ recommendedVersionError }} + </div> + <div class="notification is-warning" v-else> + There is no recommended worker version for this worker. Please select the "advanced mode" in the menu above to see all worker versions. + </div> </template> <script lang="ts"> @@ -149,9 +147,13 @@ export default defineComponent({ versionsPage: null as PageNumberPagination<WorkerVersion> | null, loading: false, page: 1, - // If set to false, only show the "recommended" version for this worker + /** + * If set to false, only show the "recommended" version for this worker + */ advancedMode: false, - // If set to false, only display versions with a tag or from the master/main branch + /** + * If set to false, only display versions with a tag or from the master/main branch + */ allVersions: false }), computed: { @@ -163,6 +165,10 @@ export default defineComponent({ versionsTitle () { if (this.advancedMode) return 'Versions' return 'Recommended version' + }, + recommendedVersion () { + if (this.recommendedWorkerVersions[this.worker.id]) return this.recommendedWorkerVersions[this.worker.id] + return undefined } }, methods: { @@ -207,7 +213,7 @@ export default defineComponent({ advancedMode: { immediate: true, handler (newValue) { - if (String(newValue) === 'false') this.fetchRecommendedVersion() + if (newValue === false) this.fetchRecommendedVersion() else this.fetchVersions() } }, diff --git a/src/stores/workers.ts b/src/stores/workers.ts index dc99803f4..19deeedf2 100644 --- a/src/stores/workers.ts +++ b/src/stores/workers.ts @@ -51,7 +51,7 @@ interface State { [workerVersionId: UUID]: WorkerVersion }, recommendedWorkerVersions: { - [workerId: UUID]: WorkerVersion + [workerId: UUID]: WorkerVersion | null }, featureWorkerVersionIds: { [feature in ArkindexFeature]?: UUID @@ -211,8 +211,17 @@ export const useWorkerStore = defineStore('worker', { }, async getRecommendedWorkerVersion (workerId: UUID) { - const resp = await retrieveRecommendedWorkerVersion(workerId) - if (resp.id) this.recommendedWorkerVersions[workerId] = resp + try { + const resp = await retrieveRecommendedWorkerVersion(workerId) + this.recommendedWorkerVersions[workerId] = resp + return resp + } catch (err) { + if (isAxiosError(err) && err.response?.status === 404) { + this.recommendedWorkerVersions[workerId] = null + return null + } + else throw err + } }, async getFeatureWorkerVersion (feature: ArkindexFeature) { diff --git a/tests/unit/stores/workers.spec.js b/tests/unit/stores/workers.spec.js index b539f2132..1ca877b8e 100644 --- a/tests/unit/stores/workers.spec.js +++ b/tests/unit/stores/workers.spec.js @@ -576,6 +576,20 @@ describe('workers', () => { ]) assert.deepStrictEqual(store.recommendedWorkerVersions, { workerid: workerVersionsSample.results[0] }) }) + + it('handles the case when no recommended version is found', async () => { + mock.onGet('/workers/workerid/versions/recommended/').reply(404, { detail: 'nothing here' }) + + await store.getRecommendedWorkerVersion('workerid') + + assert.deepStrictEqual(mock.history.all.map(req => pick(req, ['method', 'url'])), [ + { + method: 'get', + url: '/workers/workerid/versions/recommended/' + } + ]) + assert.deepStrictEqual(store.recommendedWorkerVersions, { workerid: null }) + }) }) }) }) -- GitLab From 6e4046bc4601633b9cc444aa08b3eef055be7b1a Mon Sep 17 00:00:00 2001 From: mlbonhomme <bonhomme@teklia.com> Date: Wed, 22 Jan 2025 14:04:05 +0100 Subject: [PATCH 4/4] switch to advanced mode when ther is no recommended worker version --- src/components/Process/Workers/Versions/List.vue | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/Process/Workers/Versions/List.vue b/src/components/Process/Workers/Versions/List.vue index 3f8b96aef..cd323a15f 100644 --- a/src/components/Process/Workers/Versions/List.vue +++ b/src/components/Process/Workers/Versions/List.vue @@ -5,6 +5,7 @@ <span class="select"> <select v-model="advancedMode" + :disabled="!recommendedVersion && !recommendedVersionError" > <option :value="false">Simple mode</option> <option :value="true">Advanced mode</option> @@ -200,7 +201,8 @@ export default defineComponent({ async fetchRecommendedVersion () { this.loading = true try { - await this.getRecommendedWorkerVersion(this.worker.id) + const resp = await this.getRecommendedWorkerVersion(this.worker.id) + if (!resp) this.advancedMode = true } catch (err) { this.recommendedVersionError = errorParser(err) } finally { @@ -222,8 +224,8 @@ export default defineComponent({ handler (newValue, oldValue) { if (oldValue?.id === newValue.id) return this.page = 1 - if (this.advancedMode) this.fetchVersions() - else this.fetchRecommendedVersion() + this.advancedMode = false + this.fetchRecommendedVersion() } }, allVersions: { -- GitLab