From 1e912e724aeef38a89accbb36581e9e555b5b773 Mon Sep 17 00:00:00 2001 From: Alvaro Tinoco Date: Thu, 23 Jan 2025 18:39:39 +0100 Subject: [PATCH 1/8] =?UTF-8?q?=F0=9F=91=BD=EF=B8=8F=20Update=20OpenAPI=20?= =?UTF-8?q?client?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- openapi.yml | 391 +++++++++++++++++++++++++- package.json | 2 +- src/generated-client/api.ts | 374 +++++++++++++++++++++++- src/generated-client/base.ts | 2 +- src/generated-client/common.ts | 2 +- src/generated-client/configuration.ts | 2 +- src/generated-client/index.ts | 2 +- src/mock-server/handlers.js | 270 +++++++++++++++++- 8 files changed, 1022 insertions(+), 23 deletions(-) diff --git a/openapi.yml b/openapi.yml index 95eaea1cc..e11effd15 100644 --- a/openapi.yml +++ b/openapi.yml @@ -1,7 +1,7 @@ openapi: 3.0.3 info: title: OSIDB API - version: 4.6.1 + version: 4.6.5 description: REST API autogenerated docs for the OSIDB and its components paths: /auth/token: @@ -4725,6 +4725,219 @@ paths: version: type: string description: '' + /osidb/api/v1/flaws/{flaw_id}/labels: + get: + operationId: osidb_api_v1_flaws_labels_list + parameters: + - in: path + name: flaw_id + schema: + type: string + format: uuid + required: true + - name: limit + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - name: offset + required: false + in: query + description: The initial index from which to return the results. + schema: + type: integer + tags: + - osidb + security: + - OsidbTokenAuthentication: [] + - {} + responses: + '200': + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedFlawCollaboratorList' + - type: object + properties: + dt: + type: string + format: date-time + env: + type: string + revision: + type: string + version: + type: string + description: '' + post: + operationId: osidb_api_v1_flaws_labels_create + parameters: + - in: path + name: flaw_id + schema: + type: string + format: uuid + required: true + tags: + - osidb + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/FlawCollaboratorPost' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/FlawCollaboratorPost' + multipart/form-data: + schema: + $ref: '#/components/schemas/FlawCollaboratorPost' + required: true + security: + - OsidbTokenAuthentication: [] + responses: + '201': + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/FlawCollaborator' + - type: object + properties: + dt: + type: string + format: date-time + env: + type: string + revision: + type: string + version: + type: string + description: '' + /osidb/api/v1/flaws/{flaw_id}/labels/{id}: + get: + operationId: osidb_api_v1_flaws_labels_retrieve + parameters: + - in: path + name: flaw_id + schema: + type: string + format: uuid + required: true + - in: path + name: id + schema: + type: string + required: true + tags: + - osidb + security: + - OsidbTokenAuthentication: [] + - {} + responses: + '200': + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/FlawCollaborator' + - type: object + properties: + dt: + type: string + format: date-time + env: + type: string + revision: + type: string + version: + type: string + description: '' + put: + operationId: osidb_api_v1_flaws_labels_update + parameters: + - in: path + name: flaw_id + schema: + type: string + format: uuid + required: true + - in: path + name: id + schema: + type: string + required: true + tags: + - osidb + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/FlawCollaboratorPost' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/FlawCollaboratorPost' + multipart/form-data: + schema: + $ref: '#/components/schemas/FlawCollaboratorPost' + required: true + security: + - OsidbTokenAuthentication: [] + responses: + '200': + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/FlawCollaborator' + - type: object + properties: + dt: + type: string + format: date-time + env: + type: string + revision: + type: string + version: + type: string + description: '' + delete: + operationId: osidb_api_v1_flaws_labels_destroy + parameters: + - in: path + name: flaw_id + schema: + type: string + format: uuid + required: true + - in: path + name: id + schema: + type: string + required: true + tags: + - osidb + security: + - OsidbTokenAuthentication: [] + responses: + '204': + content: + application/json: + schema: + type: object + properties: + dt: + type: string + format: date-time + env: + type: string + revision: + type: string + version: + type: string + description: '' /osidb/api/v1/flaws/{flaw_id}/package_versions: get: operationId: osidb_api_v1_flaws_package_versions_list @@ -5704,6 +5917,81 @@ paths: version: type: string description: '' + /osidb/api/v1/labels: + get: + operationId: osidb_api_v1_labels_list + parameters: + - name: limit + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - name: offset + required: false + in: query + description: The initial index from which to return the results. + schema: + type: integer + tags: + - osidb + security: + - OsidbTokenAuthentication: [] + - {} + responses: + '200': + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedFlawLabelList' + - type: object + properties: + dt: + type: string + format: date-time + env: + type: string + revision: + type: string + version: + type: string + description: '' + /osidb/api/v1/labels/{uuid}: + get: + operationId: osidb_api_v1_labels_retrieve + parameters: + - in: path + name: uuid + schema: + type: string + format: uuid + description: A UUID string identifying this flaw label. + required: true + tags: + - osidb + security: + - OsidbTokenAuthentication: [] + - {} + responses: + '200': + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/FlawLabel' + - type: object + properties: + dt: + type: string + format: date-time + env: + type: string + revision: + type: string + version: + type: string + description: '' /osidb/api/v1/manifest: get: operationId: osidb_api_v1_manifest_retrieve @@ -7720,7 +8008,7 @@ components: type: array items: $ref: '#/components/schemas/FlawCollaborator' - nullable: true + readOnly: true embargoed: type: boolean description: The embargoed boolean attribute is technically read-only as @@ -7778,6 +8066,7 @@ components: - created_dt - cvss_scores - embargoed + - labels - package_versions - references - title @@ -8068,8 +8357,43 @@ components: type: object description: FlawCollaborator serializer properties: + uuid: + type: string + format: uuid + readOnly: true + flaw: + type: string + format: uuid + writeOnly: true + label: + type: string + maxLength: 255 + state: + $ref: '#/components/schemas/StateEnum' + contributor: + type: string + maxLength: 255 + relevant: + type: boolean + type: + type: string + readOnly: true + required: + - flaw + - label + - type + - uuid + FlawCollaboratorPost: + type: object + description: FlawCollaborator serializer + properties: + uuid: + type: string + format: uuid + readOnly: true label: type: string + maxLength: 255 state: $ref: '#/components/schemas/StateEnum' contributor: @@ -8077,6 +8401,7 @@ components: maxLength: 255 required: - label + - uuid FlawComment: type: object description: FlawComment serializer for use by flaw_comments endpoint @@ -8164,6 +8489,19 @@ components: - embargoed - text - uuid + FlawLabel: + type: object + description: FlawLabel serializer + properties: + name: + type: string + maxLength: 255 + type: + type: string + readOnly: true + required: + - name + - type FlawPackageVersion: type: object description: Package model serializer @@ -8370,7 +8708,7 @@ components: type: array items: $ref: '#/components/schemas/FlawCollaborator' - nullable: true + readOnly: true embargoed: type: boolean description: The embargoed boolean attribute is technically read-only as @@ -8423,6 +8761,7 @@ components: - created_dt - cvss_scores - embargoed + - labels - package_versions - references - title @@ -8869,6 +9208,29 @@ components: type: array items: $ref: '#/components/schemas/FlawCVSS' + PaginatedFlawCollaboratorList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?offset=400&limit=100 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?offset=200&limit=100 + results: + type: array + items: + $ref: '#/components/schemas/FlawCollaborator' PaginatedFlawCommentList: type: object required: @@ -8892,6 +9254,29 @@ components: type: array items: $ref: '#/components/schemas/FlawComment' + PaginatedFlawLabelList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?offset=400&limit=100 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?offset=200&limit=100 + results: + type: array + items: + $ref: '#/components/schemas/FlawLabel' PaginatedFlawList: type: object required: diff --git a/package.json b/package.json index 1897fda37..fb129edfd 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "type-check": "vue-tsc --noEmit -p tsconfig.vitest.json --composite false", "vibe-check": "npx vite-bundle-visualizer", "generate-openapi-client": "openapi-generator-cli generate -i openapi.yml --output src/generated-client -g typescript-axios --global-property models,supportingFiles", - "generate-osidb-mock": "msw-auto-mock ./openapi.yml -o src/mock-server --regex -t flaws,trackers,auth --base-url http://localhost:3000", + "generate-osidb-mock": "msw-auto-mock ./openapi.yml -o src/mock-server --regex -t flaws,trackers,auth,labels --base-url http://localhost:3000", "generate-mock-worker": "msw init", "lint": "yarn lint-style && yarn lint-ts-vue", "lint-ts-vue": "eslint .", diff --git a/src/generated-client/api.ts b/src/generated-client/api.ts index c7e93fa69..acb0b33ac 100644 --- a/src/generated-client/api.ts +++ b/src/generated-client/api.ts @@ -4,7 +4,7 @@ * OSIDB API * REST API autogenerated docs for the OSIDB and its components * - * The version of the OpenAPI document: 4.6.1 + * The version of the OpenAPI document: 4.6.5 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). @@ -1947,7 +1947,7 @@ export interface Flaw { * @type {Array} * @memberof Flaw */ - 'labels'?: Array | null; + 'labels': Array; /** * The embargoed boolean attribute is technically read-only as it just indirectly modifies the ACLs but is mandatory as it controls the access to the resource. * @type {boolean} @@ -2413,6 +2413,18 @@ export type FlawClassificationStateEnum = typeof FlawClassificationStateEnum[key * @interface FlawCollaborator */ export interface FlawCollaborator { + /** + * + * @type {string} + * @memberof FlawCollaborator + */ + 'uuid': string; + /** + * + * @type {string} + * @memberof FlawCollaborator + */ + 'flaw': string; /** * * @type {string} @@ -2431,6 +2443,51 @@ export interface FlawCollaborator { * @memberof FlawCollaborator */ 'contributor'?: string; + /** + * + * @type {boolean} + * @memberof FlawCollaborator + */ + 'relevant'?: boolean; + /** + * + * @type {string} + * @memberof FlawCollaborator + */ + 'type': string; +} + + +/** + * FlawCollaborator serializer + * @export + * @interface FlawCollaboratorPost + */ +export interface FlawCollaboratorPost { + /** + * + * @type {string} + * @memberof FlawCollaboratorPost + */ + 'uuid': string; + /** + * + * @type {string} + * @memberof FlawCollaboratorPost + */ + 'label': string; + /** + * + * @type {StateEnum} + * @memberof FlawCollaboratorPost + */ + 'state'?: StateEnum; + /** + * + * @type {string} + * @memberof FlawCollaboratorPost + */ + 'contributor'?: string; } @@ -2557,6 +2614,25 @@ export interface FlawCommentPost { */ 'embargoed': boolean; } +/** + * FlawLabel serializer + * @export + * @interface FlawLabel + */ +export interface FlawLabel { + /** + * + * @type {string} + * @memberof FlawLabel + */ + 'name': string; + /** + * + * @type {string} + * @memberof FlawLabel + */ + 'type': string; +} /** * @type FlawMajorIncidentState * @export @@ -2853,7 +2929,7 @@ export interface FlawPost { * @type {Array} * @memberof FlawPost */ - 'labels'?: Array | null; + 'labels': Array; /** * The embargoed boolean attribute is technically read-only as it just indirectly modifies the ACLs but is mandatory as it controls the access to the resource. * @type {boolean} @@ -4351,7 +4427,7 @@ export interface OsidbApiV1FlawsCreate201Response { * @type {Array} * @memberof OsidbApiV1FlawsCreate201Response */ - 'labels'?: Array | null; + 'labels': Array; /** * The embargoed boolean attribute is technically read-only as it just indirectly modifies the ACLs but is mandatory as it controls the access to the resource. * @type {boolean} @@ -4585,6 +4661,136 @@ export interface OsidbApiV1FlawsCvssScoresList200Response { */ 'version'?: string; } +/** + * + * @export + * @interface OsidbApiV1FlawsLabelsCreate201Response + */ +export interface OsidbApiV1FlawsLabelsCreate201Response { + /** + * + * @type {string} + * @memberof OsidbApiV1FlawsLabelsCreate201Response + */ + 'uuid': string; + /** + * + * @type {string} + * @memberof OsidbApiV1FlawsLabelsCreate201Response + */ + 'flaw': string; + /** + * + * @type {string} + * @memberof OsidbApiV1FlawsLabelsCreate201Response + */ + 'label': string; + /** + * + * @type {StateEnum} + * @memberof OsidbApiV1FlawsLabelsCreate201Response + */ + 'state'?: StateEnum; + /** + * + * @type {string} + * @memberof OsidbApiV1FlawsLabelsCreate201Response + */ + 'contributor'?: string; + /** + * + * @type {boolean} + * @memberof OsidbApiV1FlawsLabelsCreate201Response + */ + 'relevant'?: boolean; + /** + * + * @type {string} + * @memberof OsidbApiV1FlawsLabelsCreate201Response + */ + 'type': string; + /** + * + * @type {string} + * @memberof OsidbApiV1FlawsLabelsCreate201Response + */ + 'dt'?: string; + /** + * + * @type {string} + * @memberof OsidbApiV1FlawsLabelsCreate201Response + */ + 'env'?: string; + /** + * + * @type {string} + * @memberof OsidbApiV1FlawsLabelsCreate201Response + */ + 'revision'?: string; + /** + * + * @type {string} + * @memberof OsidbApiV1FlawsLabelsCreate201Response + */ + 'version'?: string; +} + + +/** + * + * @export + * @interface OsidbApiV1FlawsLabelsList200Response + */ +export interface OsidbApiV1FlawsLabelsList200Response { + /** + * + * @type {number} + * @memberof OsidbApiV1FlawsLabelsList200Response + */ + 'count': number; + /** + * + * @type {string} + * @memberof OsidbApiV1FlawsLabelsList200Response + */ + 'next'?: string | null; + /** + * + * @type {string} + * @memberof OsidbApiV1FlawsLabelsList200Response + */ + 'previous'?: string | null; + /** + * + * @type {Array} + * @memberof OsidbApiV1FlawsLabelsList200Response + */ + 'results': Array; + /** + * + * @type {string} + * @memberof OsidbApiV1FlawsLabelsList200Response + */ + 'dt'?: string; + /** + * + * @type {string} + * @memberof OsidbApiV1FlawsLabelsList200Response + */ + 'env'?: string; + /** + * + * @type {string} + * @memberof OsidbApiV1FlawsLabelsList200Response + */ + 'revision'?: string; + /** + * + * @type {string} + * @memberof OsidbApiV1FlawsLabelsList200Response + */ + 'version'?: string; +} /** * * @export @@ -4910,6 +5116,104 @@ export interface OsidbApiV1FlawsReferencesList200Response { */ 'version'?: string; } +/** + * + * @export + * @interface OsidbApiV1LabelsList200Response + */ +export interface OsidbApiV1LabelsList200Response { + /** + * + * @type {number} + * @memberof OsidbApiV1LabelsList200Response + */ + 'count': number; + /** + * + * @type {string} + * @memberof OsidbApiV1LabelsList200Response + */ + 'next'?: string | null; + /** + * + * @type {string} + * @memberof OsidbApiV1LabelsList200Response + */ + 'previous'?: string | null; + /** + * + * @type {Array} + * @memberof OsidbApiV1LabelsList200Response + */ + 'results': Array; + /** + * + * @type {string} + * @memberof OsidbApiV1LabelsList200Response + */ + 'dt'?: string; + /** + * + * @type {string} + * @memberof OsidbApiV1LabelsList200Response + */ + 'env'?: string; + /** + * + * @type {string} + * @memberof OsidbApiV1LabelsList200Response + */ + 'revision'?: string; + /** + * + * @type {string} + * @memberof OsidbApiV1LabelsList200Response + */ + 'version'?: string; +} +/** + * + * @export + * @interface OsidbApiV1LabelsRetrieve200Response + */ +export interface OsidbApiV1LabelsRetrieve200Response { + /** + * + * @type {string} + * @memberof OsidbApiV1LabelsRetrieve200Response + */ + 'name': string; + /** + * + * @type {string} + * @memberof OsidbApiV1LabelsRetrieve200Response + */ + 'type': string; + /** + * + * @type {string} + * @memberof OsidbApiV1LabelsRetrieve200Response + */ + 'dt'?: string; + /** + * + * @type {string} + * @memberof OsidbApiV1LabelsRetrieve200Response + */ + 'env'?: string; + /** + * + * @type {string} + * @memberof OsidbApiV1LabelsRetrieve200Response + */ + 'revision'?: string; + /** + * + * @type {string} + * @memberof OsidbApiV1LabelsRetrieve200Response + */ + 'version'?: string; +} /** * * @export @@ -5633,6 +5937,37 @@ export interface PaginatedFlawCVSSList { */ 'results': Array; } +/** + * + * @export + * @interface PaginatedFlawCollaboratorList + */ +export interface PaginatedFlawCollaboratorList { + /** + * + * @type {number} + * @memberof PaginatedFlawCollaboratorList + */ + 'count': number; + /** + * + * @type {string} + * @memberof PaginatedFlawCollaboratorList + */ + 'next'?: string | null; + /** + * + * @type {string} + * @memberof PaginatedFlawCollaboratorList + */ + 'previous'?: string | null; + /** + * + * @type {Array} + * @memberof PaginatedFlawCollaboratorList + */ + 'results': Array; +} /** * * @export @@ -5664,6 +5999,37 @@ export interface PaginatedFlawCommentList { */ 'results': Array; } +/** + * + * @export + * @interface PaginatedFlawLabelList + */ +export interface PaginatedFlawLabelList { + /** + * + * @type {number} + * @memberof PaginatedFlawLabelList + */ + 'count': number; + /** + * + * @type {string} + * @memberof PaginatedFlawLabelList + */ + 'next'?: string | null; + /** + * + * @type {string} + * @memberof PaginatedFlawLabelList + */ + 'previous'?: string | null; + /** + * + * @type {Array} + * @memberof PaginatedFlawLabelList + */ + 'results': Array; +} /** * * @export diff --git a/src/generated-client/base.ts b/src/generated-client/base.ts index 8035c6b0d..7d677de30 100644 --- a/src/generated-client/base.ts +++ b/src/generated-client/base.ts @@ -4,7 +4,7 @@ * OSIDB API * REST API autogenerated docs for the OSIDB and its components * - * The version of the OpenAPI document: 4.6.1 + * The version of the OpenAPI document: 4.6.5 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/src/generated-client/common.ts b/src/generated-client/common.ts index 7925b8321..1204a2fa5 100644 --- a/src/generated-client/common.ts +++ b/src/generated-client/common.ts @@ -4,7 +4,7 @@ * OSIDB API * REST API autogenerated docs for the OSIDB and its components * - * The version of the OpenAPI document: 4.6.1 + * The version of the OpenAPI document: 4.6.5 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/src/generated-client/configuration.ts b/src/generated-client/configuration.ts index 0d72fdb10..25a43aa08 100644 --- a/src/generated-client/configuration.ts +++ b/src/generated-client/configuration.ts @@ -4,7 +4,7 @@ * OSIDB API * REST API autogenerated docs for the OSIDB and its components * - * The version of the OpenAPI document: 4.6.1 + * The version of the OpenAPI document: 4.6.5 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/src/generated-client/index.ts b/src/generated-client/index.ts index 13b3427e9..09790b0f9 100644 --- a/src/generated-client/index.ts +++ b/src/generated-client/index.ts @@ -4,7 +4,7 @@ * OSIDB API * REST API autogenerated docs for the OSIDB and its components * - * The version of the OpenAPI document: 4.6.1 + * The version of the OpenAPI document: 4.6.5 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/src/mock-server/handlers.js b/src/mock-server/handlers.js index c3b454ab6..2035f46de 100644 --- a/src/mock-server/handlers.js +++ b/src/mock-server/handlers.js @@ -172,6 +172,39 @@ export const handlers = [ return HttpResponse.json(...resultArray[next() % resultArray.length]); }), + http.get(`${baseURL}/osidb/api/v1/flaws/*/labels`, async () => { + const resultArray = [ + [await getOsidbApiV1FlawsLabelsList200Response(), { status: 200 }], + ]; + + return HttpResponse.json(...resultArray[next() % resultArray.length]); + }), + http.post(`${baseURL}/osidb/api/v1/flaws/*/labels`, async () => { + const resultArray = [ + [await getOsidbApiV1FlawsLabelsCreate201Response(), { status: 201 }], + ]; + + return HttpResponse.json(...resultArray[next() % resultArray.length]); + }), + http.get(`${baseURL}/osidb/api/v1/flaws/*/labels/*`, async () => { + const resultArray = [ + [await getOsidbApiV1FlawsLabelsRetrieve200Response(), { status: 200 }], + ]; + + return HttpResponse.json(...resultArray[next() % resultArray.length]); + }), + http.put(`${baseURL}/osidb/api/v1/flaws/*/labels/*`, async () => { + const resultArray = [ + [await getOsidbApiV1FlawsLabelsUpdate200Response(), { status: 200 }], + ]; + + return HttpResponse.json(...resultArray[next() % resultArray.length]); + }), + http.delete(`${baseURL}/osidb/api/v1/flaws/*/labels/*`, async () => { + const resultArray = [[undefined, { status: 204 }]]; + + return HttpResponse.json(...resultArray[next() % resultArray.length]); + }), http.get(`${baseURL}/osidb/api/v1/flaws/*/package_versions`, async () => { const resultArray = [ [ @@ -291,6 +324,20 @@ export const handlers = [ return HttpResponse.json(...resultArray[next() % resultArray.length]); }), + http.get(`${baseURL}/osidb/api/v1/labels`, async () => { + const resultArray = [ + [await getOsidbApiV1LabelsList200Response(), { status: 200 }], + ]; + + return HttpResponse.json(...resultArray[next() % resultArray.length]); + }), + http.get(`${baseURL}/osidb/api/v1/labels/*`, async () => { + const resultArray = [ + [await getOsidbApiV1LabelsRetrieve200Response(), { status: 200 }], + ]; + + return HttpResponse.json(...resultArray[next() % resultArray.length]); + }), http.get(`${baseURL}/osidb/api/v1/trackers`, async () => { const resultArray = [ [await getOsidbApiV1TrackersList200Response(), { status: 200 }], @@ -636,6 +683,7 @@ export function getOsidbApiV1FlawsList200Response() { created_dt: faker.date.past(), updated_dt: faker.date.past(), })), + purl: faker.internet.url(), embargoed: faker.datatype.boolean(), alerts: [ ...new Array( @@ -744,7 +792,12 @@ export function getOsidbApiV1FlawsList200Response() { ].map((_) => ({ description: faker.lorem.words(), flaw: faker.string.uuid(), - type: faker.helpers.arrayElement(["ARTICLE", "EXTERNAL", "SOURCE"]), + type: faker.helpers.arrayElement([ + "ARTICLE", + "EXTERNAL", + "SOURCE", + "UPSTREAM", + ]), url: faker.internet.url(), uuid: faker.string.uuid(), embargoed: faker.datatype.boolean(), @@ -793,6 +846,19 @@ export function getOsidbApiV1FlawsList200Response() { created_dt: faker.date.past(), updated_dt: faker.date.past(), })), + labels: [ + ...new Array( + faker.number.int({ min: 1, max: MAX_ARRAY_LENGTH }), + ).keys(), + ].map((_) => ({ + uuid: faker.string.uuid(), + flaw: faker.string.uuid(), + label: faker.string.alpha({ length: { min: 0, max: 255 } }), + state: faker.helpers.arrayElement(["NEW", "REQ", "SKIP", "DONE"]), + contributor: faker.string.alpha({ length: { min: 0, max: 255 } }), + relevant: faker.datatype.boolean(), + type: faker.lorem.words(), + })), embargoed: faker.datatype.boolean(), created_dt: faker.date.past(), updated_dt: faker.date.past(), @@ -1077,6 +1143,7 @@ export function getOsidbApiV1FlawsCreate201Response() { created_dt: faker.date.past(), updated_dt: faker.date.past(), })), + purl: faker.internet.url(), embargoed: faker.datatype.boolean(), alerts: [ ...new Array( @@ -1175,7 +1242,12 @@ export function getOsidbApiV1FlawsCreate201Response() { ].map((_) => ({ description: faker.lorem.words(), flaw: faker.string.uuid(), - type: faker.helpers.arrayElement(["ARTICLE", "EXTERNAL", "SOURCE"]), + type: faker.helpers.arrayElement([ + "ARTICLE", + "EXTERNAL", + "SOURCE", + "UPSTREAM", + ]), url: faker.internet.url(), uuid: faker.string.uuid(), embargoed: faker.datatype.boolean(), @@ -1222,6 +1294,17 @@ export function getOsidbApiV1FlawsCreate201Response() { created_dt: faker.date.past(), updated_dt: faker.date.past(), })), + labels: [ + ...new Array(faker.number.int({ min: 1, max: MAX_ARRAY_LENGTH })).keys(), + ].map((_) => ({ + uuid: faker.string.uuid(), + flaw: faker.string.uuid(), + label: faker.string.alpha({ length: { min: 0, max: 255 } }), + state: faker.helpers.arrayElement(["NEW", "REQ", "SKIP", "DONE"]), + contributor: faker.string.alpha({ length: { min: 0, max: 255 } }), + relevant: faker.datatype.boolean(), + type: faker.lorem.words(), + })), embargoed: faker.datatype.boolean(), created_dt: faker.date.past(), updated_dt: faker.date.past(), @@ -1401,7 +1484,7 @@ export function getOsidbApiV1FlawsCommentsList200Response() { text: faker.lorem.words(), uuid: faker.string.uuid(), external_system_id: faker.lorem.words(), - order: faker.number.int({ min: -2147483648, max: 2147483647 }), + order: faker.number.int(), creator: faker.string.alpha({ length: { min: 0, max: 100 } }), is_private: faker.datatype.boolean(), alerts: [ @@ -1434,7 +1517,7 @@ export function getOsidbApiV1FlawsCommentsCreate201Response() { text: faker.lorem.words(), uuid: faker.string.uuid(), external_system_id: faker.lorem.words(), - order: faker.number.int({ min: -2147483648, max: 2147483647 }), + order: faker.number.int(), creator: faker.string.alpha({ length: { min: 0, max: 100 } }), is_private: faker.datatype.boolean(), alerts: [ @@ -1464,7 +1547,7 @@ export function getOsidbApiV1FlawsCommentsRetrieve200Response() { text: faker.lorem.words(), uuid: faker.string.uuid(), external_system_id: faker.lorem.words(), - order: faker.number.int({ min: -2147483648, max: 2147483647 }), + order: faker.number.int(), creator: faker.string.alpha({ length: { min: 0, max: 100 } }), is_private: faker.datatype.boolean(), alerts: [ @@ -1626,6 +1709,86 @@ export function getOsidbApiV1FlawsCvssScoresDestroy200Response() { }; } +export function getOsidbApiV1FlawsLabelsList200Response() { + return { + count: 123, + next: "http://api.example.org/accounts/?offset=400&limit=100", + previous: "http://api.example.org/accounts/?offset=200&limit=100", + results: [ + ...new Array(faker.number.int({ min: 1, max: MAX_ARRAY_LENGTH })).keys(), + ].map((_) => ({ + uuid: faker.string.uuid(), + flaw: faker.string.uuid(), + label: faker.string.alpha({ length: { min: 0, max: 255 } }), + state: faker.helpers.arrayElement(["NEW", "REQ", "SKIP", "DONE"]), + contributor: faker.string.alpha({ length: { min: 0, max: 255 } }), + relevant: faker.datatype.boolean(), + type: faker.lorem.words(), + })), + dt: faker.date.past(), + env: faker.lorem.words(), + revision: faker.lorem.words(), + version: faker.lorem.words(), + }; +} + +export function getOsidbApiV1FlawsLabelsCreate201Response() { + return { + uuid: faker.string.uuid(), + flaw: faker.string.uuid(), + label: faker.string.alpha({ length: { min: 0, max: 255 } }), + state: faker.helpers.arrayElement(["NEW", "REQ", "SKIP", "DONE"]), + contributor: faker.string.alpha({ length: { min: 0, max: 255 } }), + relevant: faker.datatype.boolean(), + type: faker.lorem.words(), + dt: faker.date.past(), + env: faker.lorem.words(), + revision: faker.lorem.words(), + version: faker.lorem.words(), + }; +} + +export function getOsidbApiV1FlawsLabelsRetrieve200Response() { + return { + uuid: faker.string.uuid(), + flaw: faker.string.uuid(), + label: faker.string.alpha({ length: { min: 0, max: 255 } }), + state: faker.helpers.arrayElement(["NEW", "REQ", "SKIP", "DONE"]), + contributor: faker.string.alpha({ length: { min: 0, max: 255 } }), + relevant: faker.datatype.boolean(), + type: faker.lorem.words(), + dt: faker.date.past(), + env: faker.lorem.words(), + revision: faker.lorem.words(), + version: faker.lorem.words(), + }; +} + +export function getOsidbApiV1FlawsLabelsUpdate200Response() { + return { + uuid: faker.string.uuid(), + flaw: faker.string.uuid(), + label: faker.string.alpha({ length: { min: 0, max: 255 } }), + state: faker.helpers.arrayElement(["NEW", "REQ", "SKIP", "DONE"]), + contributor: faker.string.alpha({ length: { min: 0, max: 255 } }), + relevant: faker.datatype.boolean(), + type: faker.lorem.words(), + dt: faker.date.past(), + env: faker.lorem.words(), + revision: faker.lorem.words(), + version: faker.lorem.words(), + }; +} + +export function getOsidbApiV1FlawsLabelsDestroy204Response() { + return { + dt: faker.date.past(), + env: faker.lorem.words(), + revision: faker.lorem.words(), + version: faker.lorem.words(), + }; +} + export function getOsidbApiV1FlawsPackageVersionsList200Response() { return { count: 123, @@ -1743,7 +1906,12 @@ export function getOsidbApiV1FlawsReferencesList200Response() { ].map((_) => ({ description: faker.lorem.words(), flaw: faker.string.uuid(), - type: faker.helpers.arrayElement(["ARTICLE", "EXTERNAL", "SOURCE"]), + type: faker.helpers.arrayElement([ + "ARTICLE", + "EXTERNAL", + "SOURCE", + "UPSTREAM", + ]), url: faker.internet.url(), uuid: faker.string.uuid(), embargoed: faker.datatype.boolean(), @@ -1774,7 +1942,12 @@ export function getOsidbApiV1FlawsReferencesCreate201Response() { return { description: faker.lorem.words(), flaw: faker.string.uuid(), - type: faker.helpers.arrayElement(["ARTICLE", "EXTERNAL", "SOURCE"]), + type: faker.helpers.arrayElement([ + "ARTICLE", + "EXTERNAL", + "SOURCE", + "UPSTREAM", + ]), url: faker.internet.url(), uuid: faker.string.uuid(), embargoed: faker.datatype.boolean(), @@ -1802,7 +1975,12 @@ export function getOsidbApiV1FlawsReferencesRetrieve200Response() { return { description: faker.lorem.words(), flaw: faker.string.uuid(), - type: faker.helpers.arrayElement(["ARTICLE", "EXTERNAL", "SOURCE"]), + type: faker.helpers.arrayElement([ + "ARTICLE", + "EXTERNAL", + "SOURCE", + "UPSTREAM", + ]), url: faker.internet.url(), uuid: faker.string.uuid(), embargoed: faker.datatype.boolean(), @@ -1830,7 +2008,12 @@ export function getOsidbApiV1FlawsReferencesUpdate200Response() { return { description: faker.lorem.words(), flaw: faker.string.uuid(), - type: faker.helpers.arrayElement(["ARTICLE", "EXTERNAL", "SOURCE"]), + type: faker.helpers.arrayElement([ + "ARTICLE", + "EXTERNAL", + "SOURCE", + "UPSTREAM", + ]), url: faker.internet.url(), uuid: faker.string.uuid(), embargoed: faker.datatype.boolean(), @@ -2116,6 +2299,7 @@ export function getOsidbApiV1FlawsRetrieve200Response() { created_dt: faker.date.past(), updated_dt: faker.date.past(), })), + purl: faker.internet.url(), embargoed: faker.datatype.boolean(), alerts: [ ...new Array( @@ -2214,7 +2398,12 @@ export function getOsidbApiV1FlawsRetrieve200Response() { ].map((_) => ({ description: faker.lorem.words(), flaw: faker.string.uuid(), - type: faker.helpers.arrayElement(["ARTICLE", "EXTERNAL", "SOURCE"]), + type: faker.helpers.arrayElement([ + "ARTICLE", + "EXTERNAL", + "SOURCE", + "UPSTREAM", + ]), url: faker.internet.url(), uuid: faker.string.uuid(), embargoed: faker.datatype.boolean(), @@ -2261,6 +2450,17 @@ export function getOsidbApiV1FlawsRetrieve200Response() { created_dt: faker.date.past(), updated_dt: faker.date.past(), })), + labels: [ + ...new Array(faker.number.int({ min: 1, max: MAX_ARRAY_LENGTH })).keys(), + ].map((_) => ({ + uuid: faker.string.uuid(), + flaw: faker.string.uuid(), + label: faker.string.alpha({ length: { min: 0, max: 255 } }), + state: faker.helpers.arrayElement(["NEW", "REQ", "SKIP", "DONE"]), + contributor: faker.string.alpha({ length: { min: 0, max: 255 } }), + relevant: faker.datatype.boolean(), + type: faker.lorem.words(), + })), embargoed: faker.datatype.boolean(), created_dt: faker.date.past(), updated_dt: faker.date.past(), @@ -2542,6 +2742,7 @@ export function getOsidbApiV1FlawsUpdate200Response() { created_dt: faker.date.past(), updated_dt: faker.date.past(), })), + purl: faker.internet.url(), embargoed: faker.datatype.boolean(), alerts: [ ...new Array( @@ -2640,7 +2841,12 @@ export function getOsidbApiV1FlawsUpdate200Response() { ].map((_) => ({ description: faker.lorem.words(), flaw: faker.string.uuid(), - type: faker.helpers.arrayElement(["ARTICLE", "EXTERNAL", "SOURCE"]), + type: faker.helpers.arrayElement([ + "ARTICLE", + "EXTERNAL", + "SOURCE", + "UPSTREAM", + ]), url: faker.internet.url(), uuid: faker.string.uuid(), embargoed: faker.datatype.boolean(), @@ -2687,6 +2893,17 @@ export function getOsidbApiV1FlawsUpdate200Response() { created_dt: faker.date.past(), updated_dt: faker.date.past(), })), + labels: [ + ...new Array(faker.number.int({ min: 1, max: MAX_ARRAY_LENGTH })).keys(), + ].map((_) => ({ + uuid: faker.string.uuid(), + flaw: faker.string.uuid(), + label: faker.string.alpha({ length: { min: 0, max: 255 } }), + state: faker.helpers.arrayElement(["NEW", "REQ", "SKIP", "DONE"]), + contributor: faker.string.alpha({ length: { min: 0, max: 255 } }), + relevant: faker.datatype.boolean(), + type: faker.lorem.words(), + })), embargoed: faker.datatype.boolean(), created_dt: faker.date.past(), updated_dt: faker.date.past(), @@ -2724,6 +2941,35 @@ export function getOsidbApiV1FlawsUpdate200Response() { }; } +export function getOsidbApiV1LabelsList200Response() { + return { + count: 123, + next: "http://api.example.org/accounts/?offset=400&limit=100", + previous: "http://api.example.org/accounts/?offset=200&limit=100", + results: [ + ...new Array(faker.number.int({ min: 1, max: MAX_ARRAY_LENGTH })).keys(), + ].map((_) => ({ + name: faker.person.fullName(), + type: faker.lorem.words(), + })), + dt: faker.date.past(), + env: faker.lorem.words(), + revision: faker.lorem.words(), + version: faker.lorem.words(), + }; +} + +export function getOsidbApiV1LabelsRetrieve200Response() { + return { + name: faker.person.fullName(), + type: faker.lorem.words(), + dt: faker.date.past(), + env: faker.lorem.words(), + revision: faker.lorem.words(), + version: faker.lorem.words(), + }; +} + export function getOsidbApiV1TrackersList200Response() { return { count: 123, @@ -3030,6 +3276,7 @@ export function getTrackersApiV1FileCreate200Response() { created_dt: faker.date.past(), updated_dt: faker.date.past(), })), + purl: faker.internet.url(), embargoed: faker.datatype.boolean(), alerts: [ ...new Array( @@ -3155,6 +3402,7 @@ export function getTrackersApiV1FileCreate200Response() { created_dt: faker.date.past(), updated_dt: faker.date.past(), })), + purl: faker.internet.url(), embargoed: faker.datatype.boolean(), alerts: [ ...new Array( From 239b2d957051ace9ee6d4177784961e21a10c9cd Mon Sep 17 00:00:00 2001 From: Alvaro Tinoco Date: Thu, 23 Jan 2025 18:41:13 +0100 Subject: [PATCH 2/8] =?UTF-8?q?=F0=9F=8F=B7=EF=B8=8F=20Update=20label=20sc?= =?UTF-8?q?hema?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/types/zodFlaw.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/types/zodFlaw.ts b/src/types/zodFlaw.ts index 913832f57..ac5410491 100644 --- a/src/types/zodFlaw.ts +++ b/src/types/zodFlaw.ts @@ -133,6 +133,11 @@ export const ZodHistoryItemSchema = z.object({ pgh_diff: z.record(z.array(z.any())).nullable(), }); +export enum FlawLabelTypeEnum { + CONTEXT_BASED = 'context_based', + PRODUCT_FAMILY = 'product_family', +} +export type ZodFlawLabelType = NonNullable[number]; export type ZodFlawType = z.infer; export type FlawSchemaType = typeof ZodFlawSchema; @@ -177,10 +182,12 @@ export const ZodFlawSchema = z.object({ comments: z.array(ZodFlawCommentSchema), cvss_scores: z.array(FlawCVSSSchema), labels: z.array(z.object({ + uuid: z.string().optional(), label: z.string(), state: z.nativeEnum(StateEnum), - collaborator: z.string().optional(), + contributor: z.string().optional(), relevant: z.boolean(), + type: z.nativeEnum(FlawLabelTypeEnum), })).nullish(), references: z.array(FlawReferenceSchema), acknowledgments: z.array(FlawAcknowledgmentSchema), From c4d608fe4525bd02fb277a88fe4c5f54ace3038c Mon Sep 17 00:00:00 2001 From: Alvaro Tinoco Date: Thu, 23 Jan 2025 18:41:51 +0100 Subject: [PATCH 3/8] =?UTF-8?q?=F0=9F=90=9B=20Do=20not=20parse=20JSON=20in?= =?UTF-8?q?=20'no=20content'=20responses?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/services/OsidbAuthService.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/services/OsidbAuthService.ts b/src/services/OsidbAuthService.ts index 07300c299..ca2597743 100644 --- a/src/services/OsidbAuthService.ts +++ b/src/services/OsidbAuthService.ts @@ -97,6 +97,10 @@ export async function osidbFetch(config: OsidbFetchOptions, factoryOptions?: Osi }); } + if (response.status === 204) { + return { response, data: undefined }; + } + return { data: await response.json(), response }; } From 445a2887366c998bafbe30c6d5b57b57db666c65 Mon Sep 17 00:00:00 2001 From: Alvaro Tinoco Date: Fri, 24 Jan 2025 16:22:04 +0100 Subject: [PATCH 4/8] =?UTF-8?q?=F0=9F=A7=B1=20Add=20'labels'=20property=20?= =?UTF-8?q?to=20flaw=20mocks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/__tests__/__fixtures__/sampleFlawFull.json | 1 + src/__tests__/__fixtures__/sampleFlawRequired.json | 1 + src/composables/useFlaw.ts | 1 + 3 files changed, 3 insertions(+) diff --git a/src/__tests__/__fixtures__/sampleFlawFull.json b/src/__tests__/__fixtures__/sampleFlawFull.json index d18767114..19c7755f9 100644 --- a/src/__tests__/__fixtures__/sampleFlawFull.json +++ b/src/__tests__/__fixtures__/sampleFlawFull.json @@ -386,6 +386,7 @@ "parent_model": "flaw" } ], + "labels": [], "dt": "2024-01-17T22:31:19.131516Z", "revision": "b61be72c3b93b2f307d8f4ebfd7db64ec4c81f9c", "version": "3.5.2", diff --git a/src/__tests__/__fixtures__/sampleFlawRequired.json b/src/__tests__/__fixtures__/sampleFlawRequired.json index fbf80dc87..701a2f6f1 100644 --- a/src/__tests__/__fixtures__/sampleFlawRequired.json +++ b/src/__tests__/__fixtures__/sampleFlawRequired.json @@ -33,6 +33,7 @@ "workflow": "DEFAULT", "state": "NEW" }, + "labels": [], "owner": "", "task_key": "", "team_id": "", diff --git a/src/composables/useFlaw.ts b/src/composables/useFlaw.ts index 6e7be98d3..618eb88d7 100644 --- a/src/composables/useFlaw.ts +++ b/src/composables/useFlaw.ts @@ -41,6 +41,7 @@ export function blankFlaw(): ZodFlawType { mitigation: '', task_key: '', comments: [], + labels: [], references: [], acknowledgments: [], alerts: [], From 206bce430465f59d1364c0942ef5f6e973b2b170 Mon Sep 17 00:00:00 2001 From: Alvaro Tinoco Date: Fri, 24 Jan 2025 16:23:20 +0100 Subject: [PATCH 5/8] =?UTF-8?q?=E2=9C=A8=20Create=20FlawLabels=20component?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../FlawLabels/FlawLabelTableEditingRow.vue | 112 +++++++++ .../FlawLabels/FlawLabelsContributor.vue | 100 ++++++++ src/components/FlawLabels/FlawLabelsTable.vue | 222 ++++++++++++++++++ .../__tests__/FlawLabelsContributor.spec.ts | 70 ++++++ .../__tests__/FlawLabelsTable.spec.ts | 100 ++++++++ .../FlawLabelsTableEditingRow.spec.ts | 34 +++ .../FlawLabelsContributor.spec.ts.snap | 7 + .../FlawLabelsTable.spec.ts.snap | 193 +++++++++++++++ .../FlawLabelsTableEditingRow.spec.ts.snap | 17 ++ .../__snapshots__/FlawForm.spec.ts.snap | 20 ++ .../__tests__/useFlawLabels.spec.ts | 94 ++++++++ src/composables/useFlawLabels.ts | 82 +++++++ src/services/LabelsService.ts | 49 ++++ src/services/__tests__/LabelsService.spec.ts | 135 +++++++++++ src/utils/helpers.ts | 20 +- .../__snapshots__/FlawCreateView.spec.ts.snap | 1 + 16 files changed, 1248 insertions(+), 8 deletions(-) create mode 100644 src/components/FlawLabels/FlawLabelTableEditingRow.vue create mode 100644 src/components/FlawLabels/FlawLabelsContributor.vue create mode 100644 src/components/FlawLabels/FlawLabelsTable.vue create mode 100644 src/components/FlawLabels/__tests__/FlawLabelsContributor.spec.ts create mode 100644 src/components/FlawLabels/__tests__/FlawLabelsTable.spec.ts create mode 100644 src/components/FlawLabels/__tests__/FlawLabelsTableEditingRow.spec.ts create mode 100644 src/components/FlawLabels/__tests__/__snapshots__/FlawLabelsContributor.spec.ts.snap create mode 100644 src/components/FlawLabels/__tests__/__snapshots__/FlawLabelsTable.spec.ts.snap create mode 100644 src/components/FlawLabels/__tests__/__snapshots__/FlawLabelsTableEditingRow.spec.ts.snap create mode 100644 src/composables/__tests__/useFlawLabels.spec.ts create mode 100644 src/composables/useFlawLabels.ts create mode 100644 src/services/LabelsService.ts create mode 100644 src/services/__tests__/LabelsService.spec.ts diff --git a/src/components/FlawLabels/FlawLabelTableEditingRow.vue b/src/components/FlawLabels/FlawLabelTableEditingRow.vue new file mode 100644 index 000000000..bd176ccc2 --- /dev/null +++ b/src/components/FlawLabels/FlawLabelTableEditingRow.vue @@ -0,0 +1,112 @@ + + + + + diff --git a/src/components/FlawLabels/FlawLabelsContributor.vue b/src/components/FlawLabels/FlawLabelsContributor.vue new file mode 100644 index 000000000..4232f7426 --- /dev/null +++ b/src/components/FlawLabels/FlawLabelsContributor.vue @@ -0,0 +1,100 @@ + + + + diff --git a/src/components/FlawLabels/FlawLabelsTable.vue b/src/components/FlawLabels/FlawLabelsTable.vue new file mode 100644 index 000000000..8cca02bae --- /dev/null +++ b/src/components/FlawLabels/FlawLabelsTable.vue @@ -0,0 +1,222 @@ + + + + diff --git a/src/components/FlawLabels/__tests__/FlawLabelsContributor.spec.ts b/src/components/FlawLabels/__tests__/FlawLabelsContributor.spec.ts new file mode 100644 index 000000000..ce679a33f --- /dev/null +++ b/src/components/FlawLabels/__tests__/FlawLabelsContributor.spec.ts @@ -0,0 +1,70 @@ +import { ref } from 'vue'; + +import { flushPromises } from '@vue/test-utils'; + +import { mountWithConfig } from '@/__tests__/helpers'; + +import FlawLabelsContributor from '../FlawLabelsContributor.vue'; + +vi.mock('@/stores/UserStore', () => ({ + useUserStore: () => ({ + jiraUsername: 'skynet', + updateJiraUsername: vi.fn(), + }), +})); + +vi.mock('@/services/JiraService', () => ({ + searchJiraUsers: () => { + return Promise.resolve({ data: { users: [{ displayName: 'SkyNet', name: 'skynet' }] } }); + }, +})); + +vi.mock('@/composables/useFlaw', () => ({ + useFlaw: () => ({ + flaw: ref({ + task_key: '123', + }), + }), +})); + +describe('flawLabelsContributor', () => { + beforeEach(() => { + vi.useFakeTimers(); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('should render', () => { + const wrapper = mountWithConfig(FlawLabelsContributor); + + expect(wrapper.html()).toMatchSnapshot(); + }); + + it('should assign the contributor on self assign', async () => { + const wrapper = mountWithConfig(FlawLabelsContributor, { + props: { + modelValue: 'skynet', + }, + }); + + await wrapper.find('button').trigger('click'); + + expect(wrapper.props('modelValue')).toBe('skynet'); + }); + + it('should assign the contributor on click', async () => { + const wrapper = mountWithConfig(FlawLabelsContributor, { + props: { 'onUpdate:modelValue': (e: string) => wrapper.setProps({ modelValue: e }) }, + }); + + await wrapper.find('input').setValue('skynet'); + + vi.runAllTimers(); + await flushPromises(); + await wrapper.find('div.item').trigger('click'); + + expect(wrapper.props('modelValue')).toBe('skynet'); + }); +}); diff --git a/src/components/FlawLabels/__tests__/FlawLabelsTable.spec.ts b/src/components/FlawLabels/__tests__/FlawLabelsTable.spec.ts new file mode 100644 index 000000000..66603cea8 --- /dev/null +++ b/src/components/FlawLabels/__tests__/FlawLabelsTable.spec.ts @@ -0,0 +1,100 @@ +import { flushPromises } from '@vue/test-utils'; + +import { useFlawLabels } from '@/composables/useFlawLabels'; + +import { mountWithConfig } from '@/__tests__/helpers'; +import { FlawLabelTypeEnum } from '@/types/zodFlaw'; +import { StateEnum } from '@/generated-client'; + +import FlawLabelsTable from '../FlawLabelsTable.vue'; +import FlawLabelTableEditingRow from '../FlawLabelTableEditingRow.vue'; + +vi.mock('@/services/LabelsService', () => ({ + fetchLabels: vi.fn().mockResolvedValue([ + { type: 'context_based', name: 'test' }, + { type: 'context_based', name: 'test1' }, + ]), +})); + +const mountFlawLabelsTable = (props = {}) => mountWithConfig(FlawLabelsTable, { + props: { + modelValue: [ + { type: FlawLabelTypeEnum.CONTEXT_BASED, label: 'test', contributor: 'skynet', state: StateEnum.New }, + ], + ...props, + }, +}); + +describe('flawLabelsTable', () => { + it('should render flaw labels table', async () => { + const wrapper = mountFlawLabelsTable(); + await flushPromises(); + + expect(wrapper.html()).toMatchSnapshot(); + }); + + it('should handle add label', async () => { + const wrapper = mountFlawLabelsTable(); + const { areLabelsUpdated } = useFlawLabels(); + await flushPromises(); + + await wrapper.find('.table-new-row td').trigger('click'); + const editRow = wrapper.findComponent(FlawLabelTableEditingRow); + editRow.vm.$emit('save', { + type: FlawLabelTypeEnum.CONTEXT_BASED, + label: 'test1', + contributor: 'skynet', + state: StateEnum.New, + }); + await flushPromises(); + + expect(areLabelsUpdated.value).toBe(true); + expect(wrapper.html()).toMatchSnapshot(); + }); + + it('should handle edit label', async () => { + const wrapper = mountFlawLabelsTable(); + const { areLabelsUpdated } = useFlawLabels(); + await flushPromises(); + + await wrapper.find('button[title="Edit label"]').trigger('click'); + const editRow = wrapper.findComponent(FlawLabelTableEditingRow); + editRow.vm.$emit('save', { + type: FlawLabelTypeEnum.CONTEXT_BASED, + label: 'test1', + contributor: 'skynet', + state: StateEnum.New, + }); + await flushPromises(); + + expect(areLabelsUpdated.value).toBe(true); + expect(wrapper.html()).toMatchSnapshot(); + }); + + it('should handle delete label', async () => { + const wrapper = mountFlawLabelsTable(); + const { areLabelsUpdated } = useFlawLabels(); + await flushPromises(); + + await wrapper.find('button[title="Delete label"]').trigger('click'); + await flushPromises(); + + expect(areLabelsUpdated.value).toBe(true); + expect(wrapper.html()).toMatchSnapshot(); + }); + + it('should handle undo delete label', async () => { + const wrapper = mountFlawLabelsTable(); + const { areLabelsUpdated } = useFlawLabels(); + await flushPromises(); + + await wrapper.find('button[title="Delete label"]').trigger('click'); + await flushPromises(); + + await wrapper.find('button[title="Undo delete"]').trigger('click'); + await flushPromises(); + + expect(areLabelsUpdated.value).toBe(false); + expect(wrapper.html()).toMatchSnapshot(); + }); +}); diff --git a/src/components/FlawLabels/__tests__/FlawLabelsTableEditingRow.spec.ts b/src/components/FlawLabels/__tests__/FlawLabelsTableEditingRow.spec.ts new file mode 100644 index 000000000..ba5bb982d --- /dev/null +++ b/src/components/FlawLabels/__tests__/FlawLabelsTableEditingRow.spec.ts @@ -0,0 +1,34 @@ +import { mountWithConfig } from '@/__tests__/helpers'; + +import FlawLabelTableEditingRow from '../FlawLabelTableEditingRow.vue'; + +describe('flawLabelsTableEditingRow', () => { + it('should render', () => { + const wrapper = mountWithConfig(FlawLabelTableEditingRow, { shallow: true }); + + expect(wrapper.html()).toMatchSnapshot(); + }); + + it('should not emit save event if nothing changed', async () => { + const wrapper = mountWithConfig(FlawLabelTableEditingRow); + + await wrapper.find('button[title="Save"]').trigger('click'); + + expect(wrapper.emitted()).not.toHaveProperty('save'); + expect(wrapper.emitted()).toHaveProperty('cancel'); + }); + + it('should emit save event if value changed', async () => { + const wrapper = mountWithConfig(FlawLabelTableEditingRow, { + props: { + contextLabels: ['test'], + }, + }); + + await wrapper.findAll('select')[1].setValue('test'); + await wrapper.find('button[title="Save"]').trigger('click'); + + expect(wrapper.emitted()).toHaveProperty('save'); + expect(wrapper.emitted('save')).toEqual([[expect.objectContaining({ label: 'test' })]]); + }); +}); diff --git a/src/components/FlawLabels/__tests__/__snapshots__/FlawLabelsContributor.spec.ts.snap b/src/components/FlawLabels/__tests__/__snapshots__/FlawLabelsContributor.spec.ts.snap new file mode 100644 index 000000000..c800027da --- /dev/null +++ b/src/components/FlawLabels/__tests__/__snapshots__/FlawLabelsContributor.spec.ts.snap @@ -0,0 +1,7 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`flawLabelsContributor > should render 1`] = ` +"
+ +
" +`; diff --git a/src/components/FlawLabels/__tests__/__snapshots__/FlawLabelsTable.spec.ts.snap b/src/components/FlawLabels/__tests__/__snapshots__/FlawLabelsTable.spec.ts.snap new file mode 100644 index 000000000..47ef99cd6 --- /dev/null +++ b/src/components/FlawLabels/__tests__/__snapshots__/FlawLabelsTable.spec.ts.snap @@ -0,0 +1,193 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`flawLabelsTable > should handle add label 1`] = ` +"
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
StateLabelContributorActions
NEWtestskynet +
+ +
+
NEWtest1skynet +
+ +
+
+
+
+
" +`; + +exports[`flawLabelsTable > should handle delete label 1`] = ` +"
+
+
+ + + + + + + + + + + + + + + + + + + + + +
StateLabelContributorActions
NEWtestskynet +
+ + +
+
+
+
+
" +`; + +exports[`flawLabelsTable > should handle edit label 1`] = ` +"
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
StateLabelContributorActions
NEWtestskynet +
+ +
+
NEWtest1skynet +
+ +
+
+
+
+
" +`; + +exports[`flawLabelsTable > should handle undo delete label 1`] = ` +"
+
+
+ + + + + + + + + + + + + + + + + + + + + +
StateLabelContributorActions
NEWtestskynet +
+ +
+
+
+
+
" +`; + +exports[`flawLabelsTable > should render flaw labels table 1`] = ` +"
+
+
+ + + + + + + + + + + + + + + + + + + + + +
StateLabelContributorActions
NEWtestskynet +
+ +
+
+
+
+
" +`; diff --git a/src/components/FlawLabels/__tests__/__snapshots__/FlawLabelsTableEditingRow.spec.ts.snap b/src/components/FlawLabels/__tests__/__snapshots__/FlawLabelsTableEditingRow.spec.ts.snap new file mode 100644 index 000000000..f748bf2eb --- /dev/null +++ b/src/components/FlawLabels/__tests__/__snapshots__/FlawLabelsTableEditingRow.spec.ts.snap @@ -0,0 +1,17 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`flawLabelsTableEditingRow > should render 1`] = ` +" + + + + + +
+" +`; diff --git a/src/components/__tests__/__snapshots__/FlawForm.spec.ts.snap b/src/components/__tests__/__snapshots__/FlawForm.spec.ts.snap index ae889350a..2c0bea69d 100644 --- a/src/components/__tests__/__snapshots__/FlawForm.spec.ts.snap +++ b/src/components/__tests__/__snapshots__/FlawForm.spec.ts.snap @@ -314,6 +314,26 @@ exports[`flawForm > mounts and renders 1`] = ` +
+
+
+ + + + + + + + + + + + + +
StateLabelContributorActions
+
+
+
diff --git a/src/composables/__tests__/useFlawLabels.spec.ts b/src/composables/__tests__/useFlawLabels.spec.ts new file mode 100644 index 000000000..ed5ad3681 --- /dev/null +++ b/src/composables/__tests__/useFlawLabels.spec.ts @@ -0,0 +1,94 @@ +import { ref } from 'vue'; + +import { createLabel, deleteLabel, fetchLabels, updateLabel } from '@/services/LabelsService'; +import { FlawLabelTypeEnum, type ZodFlawLabelType, type ZodFlawType } from '@/types/zodFlaw'; +import sampleFlawRequired from '@/__tests__/__fixtures__/sampleFlawRequired.json'; +import { StateEnum } from '@/generated-client'; + +import { useFlawLabels } from '../useFlawLabels'; +import { useFlaw } from '../useFlaw'; + +vi.mock('@/services/LabelsService'); +vi.mock('../useFlaw'); + +describe('useFlawLabels', () => { + const baseLabel: ZodFlawLabelType = { + label: 'test-label', + type: FlawLabelTypeEnum.CONTEXT_BASED, + state: StateEnum.New, + relevant: true, + }; + + beforeEach(() => { + vi.clearAllMocks(); + vi.mocked(useFlaw, { partial: true }).mockReturnValue({ + flaw: ref({ ...sampleFlawRequired, uuid: 'test-uuid' } as ZodFlawType), + }); + }); + + it('initializes labels correctly', () => { + const initialLabels = [baseLabel]; + const { areLabelsUpdated, deletedLabels, labels, newLabels, updatedLabels } = useFlawLabels(initialLabels); + + expect(labels.value).toEqual({ [baseLabel.label]: baseLabel }); + expect(newLabels.value).toEqual(new Set()); + expect(updatedLabels.value).toEqual(new Set()); + expect(deletedLabels.value).toEqual(new Set()); + expect(areLabelsUpdated.value).toBe(false); + }); + + it.each<[keyof Pick, 'deletedLabels' | 'newLabels' | 'updatedLabels'>, string]>([ + ['newLabels', 'new-label'], + ['updatedLabels', 'updated-label'], + ['deletedLabels', 'deleted-label'], + ])('computes %s correctly', (key, label) => { + const { areLabelsUpdated, [key]: labels } = useFlawLabels(); + + labels.value.add(label); + expect(areLabelsUpdated.value).toBe(true); + }); + + it('identifies new, updated, and deleted labels correctly', () => { + const { deletedLabels, isDeletedLabel, isNewLabel, isUpdatedLabel, newLabels, updatedLabels } = useFlawLabels(); + + newLabels.value.add('new-label'); + updatedLabels.value.add('updated-label'); + deletedLabels.value.add('deleted-label'); + + expect(isNewLabel({ ...baseLabel, label: 'new-label' })).toBe(true); + expect(isUpdatedLabel({ ...baseLabel, label: 'updated-label' })).toBe(true); + expect(isDeletedLabel({ ...baseLabel, label: 'deleted-label' })).toBe(true); + }); + + it('updates labels correctly', async () => { + const { deletedLabels, newLabels, updatedLabels, updateLabels } = useFlawLabels([ + { ...baseLabel, label: 'new-label' }, + { ...baseLabel, label: 'updated-label' }, + { ...baseLabel, label: 'deleted-label' }, + ]); + + newLabels.value.add('new-label'); + updatedLabels.value.add('updated-label'); + deletedLabels.value.add('deleted-label'); + + await updateLabels(); + + expect(createLabel).toHaveBeenCalledWith('test-uuid', { ...baseLabel, label: 'new-label' }); + expect(updateLabel).toHaveBeenCalledWith('test-uuid', { ...baseLabel, label: 'updated-label' }); + expect(deleteLabel).toHaveBeenCalledWith('test-uuid', { ...baseLabel, label: 'deleted-label' }); + }); + + it('loads context labels correctly', async () => { + const { loadContextLabels } = useFlawLabels(); + const mockLabels = [ + { ...baseLabel, name: 'context-label' }, + { ...baseLabel, name: 'other-label', type: FlawLabelTypeEnum.PRODUCT_FAMILY }, + ]; + + vi.mocked(fetchLabels).mockResolvedValue(mockLabels); + + const labels = await loadContextLabels(); + + expect(labels).toEqual(['context-label']); + }); +}); diff --git a/src/composables/useFlawLabels.ts b/src/composables/useFlawLabels.ts new file mode 100644 index 000000000..8a62cc833 --- /dev/null +++ b/src/composables/useFlawLabels.ts @@ -0,0 +1,82 @@ +import { computed, ref, toValue, type MaybeRef } from 'vue'; + +import { createLabel, deleteLabel, fetchLabels, updateLabel } from '@/services/LabelsService'; +import { FlawLabelTypeEnum, type ZodFlawLabelType } from '@/types/zodFlaw'; + +import { useFlaw } from './useFlaw'; + +const labels = ref>({}); +const newLabels = ref>(new Set()); +const updatedLabels = ref>(new Set()); +const deletedLabels = ref>(new Set()); + +export function useFlawLabels(initialLabels?: MaybeRef) { + if (initialLabels) { + labels.value = toValue(initialLabels).reduce((acc: Record, label) => { + acc[label.label] = label; + return acc; + }, {}); + newLabels.value = new Set(); + updatedLabels.value = new Set(); + deletedLabels.value = new Set(); + } + + const { flaw } = useFlaw(); + const areLabelsUpdated = computed(() => + newLabels.value.size > 0 || updatedLabels.value.size > 0 || deletedLabels.value.size > 0, + ); + + const isNewLabel = (label: ZodFlawLabelType) => newLabels.value.has(label.label); + const isUpdatedLabel = (label: ZodFlawLabelType) => updatedLabels.value.has(label.label); + const isDeletedLabel = (label: ZodFlawLabelType) => deletedLabels.value.has(label.label); + + const updateLabels = async () => { + if (!flaw.value) { + return; + } + + const requests = []; + for (const newLabel of newLabels.value) { + requests.push(createLabel(flaw.value.uuid, labels.value[newLabel])); + } + + for (const updatedLabel of updatedLabels.value) { + requests.push(updateLabel(flaw.value.uuid, labels.value[updatedLabel])); + } + + for (const deletedLabel of deletedLabels.value) { + requests.push(deleteLabel(flaw.value.uuid, labels.value[deletedLabel])); + } + + await Promise.allSettled(requests); + }; + + return { + labels, + newLabels, + updatedLabels, + deletedLabels, + areLabelsUpdated, + loadContextLabels, + isNewLabel, + isUpdatedLabel, + isDeletedLabel, + updateLabels, + }; +} + +async function loadContextLabels(): Promise { + const storageKey = 'osim-context-labels'; + const storedLabels = sessionStorage.getItem(storageKey); + if (storedLabels) { + return JSON.parse(storedLabels); + } + + const labels = (await fetchLabels()) + .filter(({ type }) => type === FlawLabelTypeEnum.CONTEXT_BASED) + .map(({ name }) => name); + + sessionStorage.setItem(storageKey, JSON.stringify(labels)); + + return labels; +} diff --git a/src/services/LabelsService.ts b/src/services/LabelsService.ts new file mode 100644 index 000000000..5dcaef03f --- /dev/null +++ b/src/services/LabelsService.ts @@ -0,0 +1,49 @@ +import { createCatchHandler, createSuccessHandler } from '@/composables/service-helpers'; + +import type { PaginatedFlawLabelList } from '@/generated-client'; +import type { ZodFlawLabelType } from '@/types/zodFlaw'; + +import { osidbFetch } from './OsidbAuthService'; + +export async function fetchLabels() { + try { + const { data }: { data: PaginatedFlawLabelList } = await osidbFetch({ + method: 'get', + url: '/osidb/api/v1/labels', + }); + + return data.results; + } catch (error) { + console.error('LabelService::fetchLabels() Error fetching labels', error); + return []; + } +} + +export async function createLabel(flawUUID: string, label: ZodFlawLabelType) { + return osidbFetch({ + method: 'post', + url: `/osidb/api/v1/flaws/${flawUUID}/labels`, + data: label, + }) + .then(createSuccessHandler({ title: 'Success!', body: `Label ${label.label} created.` })) + .catch(createCatchHandler(`Error creating label ${label.label}`)); +} + +export async function deleteLabel(flawUUID: string, label: ZodFlawLabelType) { + return osidbFetch({ + method: 'delete', + url: `/osidb/api/v1/flaws/${flawUUID}/labels/${label.uuid}`, + }) + .then(createSuccessHandler({ title: 'Success!', body: `Label ${label.label} deleted.` })) + .catch(createCatchHandler(`Error deleting label ${label.label}`)); +} + +export async function updateLabel(flawUUID: string, label: ZodFlawLabelType) { + return osidbFetch({ + method: 'put', + url: `/osidb/api/v1/flaws/${flawUUID}/labels/${label.uuid}`, + data: label, + }) + .then(createSuccessHandler({ title: 'Success!', body: `Label ${label.label} updated.` })) + .catch(createCatchHandler(`Error updating label ${label.label}`)); +} diff --git a/src/services/__tests__/LabelsService.spec.ts b/src/services/__tests__/LabelsService.spec.ts new file mode 100644 index 000000000..854f1794e --- /dev/null +++ b/src/services/__tests__/LabelsService.spec.ts @@ -0,0 +1,135 @@ +import { createTestingPinia } from '@pinia/testing'; +import { http, HttpResponse } from 'msw'; + +import { server } from '@/__tests__/setup'; +import { FlawLabelTypeEnum, type ZodFlawLabelType } from '@/types/zodFlaw'; +import { StateEnum } from '@/generated-client'; +import { osimRuntime } from '@/stores/osimRuntime'; +import { handlers } from '@/mock-server/handlers'; + +import { createLabel, deleteLabel, fetchLabels, updateLabel } from '../LabelsService'; + +vi.mock('@/composables/service-helpers'); + +describe('labelsService', () => { + beforeAll(() => { + createTestingPinia(); + server.use(...handlers); + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it('should retrieve labels', async () => { + const labels = await fetchLabels(); + + expect(labels).toBeDefined(); + expect(Array.isArray(labels)).toBeTruthy(); + }); + + it('can create a label', async () => { + const flawUUID = '123'; + const label: ZodFlawLabelType = { + label: 'test create', + state: StateEnum.New, + type: FlawLabelTypeEnum.CONTEXT_BASED, + relevant: true, + contributor: '', + }; + + await expect(createLabel(flawUUID, label)).resolves.not.toBeUndefined(); + }); + + it('can update a label', async () => { + const flawUUID = '123'; + const label: ZodFlawLabelType = { + label: 'test update', + state: StateEnum.New, + type: FlawLabelTypeEnum.CONTEXT_BASED, + relevant: true, + contributor: '', + }; + + await expect(updateLabel(flawUUID, label)).resolves.not.toBeUndefined(); + }); + + it('can delete a label', async () => { + const flawUUID = '123'; + const label: ZodFlawLabelType = { + label: 'test delete', + state: StateEnum.New, + type: FlawLabelTypeEnum.CONTEXT_BASED, + relevant: true, + contributor: '', + }; + + await expect(deleteLabel(flawUUID, label)).resolves.not.toBeUndefined(); + }); + + it('handles errors when creating a label', async () => { + const flawUUID = '123'; + const label: ZodFlawLabelType = { + label: 'test error', + state: StateEnum.New, + type: FlawLabelTypeEnum.CONTEXT_BASED, + relevant: true, + contributor: '', + }; + + server.use( + http.post( + `${osimRuntime.value.backends.osidb}/osidb/api/v1/flaws/${flawUUID}/labels`, () => { + return HttpResponse.text('Error', { status: 500 }); + }, + { once: true }, + ), + ); + + await expect(() => createLabel(flawUUID, label)).rejects.toThrow(); + }); + + it('handles errors when updating a label', async () => { + const flawUUID = '123'; + const label: ZodFlawLabelType = { + label: 'test error', + state: StateEnum.New, + type: FlawLabelTypeEnum.CONTEXT_BASED, + relevant: true, + contributor: '', + }; + + server.use( + http.put( + `${osimRuntime.value.backends.osidb}/osidb/api/v1/flaws/${flawUUID}/labels/${label.uuid}`, () => { + return HttpResponse.text('Error', { status: 500 }); + }, + { once: true }, + ), + ); + + await expect(() => updateLabel(flawUUID, label)).rejects.toThrow(); + }); + + it('handles errors when deleting a label', async () => { + const flawUUID = '123'; + const label: ZodFlawLabelType = { + label: 'test error', + state: StateEnum.New, + type: FlawLabelTypeEnum.CONTEXT_BASED, + relevant: true, + contributor: '', + }; + + server.use( + http.delete( + `${osimRuntime.value.backends.osidb}/osidb/api/v1/flaws/${flawUUID}/labels/${label.uuid}`, () => { + return HttpResponse.text('Error', { status: 500 }); + }, + { once: true }, + ), + ); + + await expect(() => deleteLabel(flawUUID, label)).rejects.toThrow(); + }); +}); diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts index 2530208b0..d0d9e69b2 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -1,19 +1,13 @@ -import { toRaw, isRef, isReactive, isProxy, ref, toRef, watch, unref } from 'vue'; +import { toRaw, isRef, isReactive, isProxy, ref, unref, type Ref, type UnwrapRef } from 'vue'; import { DateTime } from 'luxon'; import * as R from 'ramda'; +import { watchOnce } from '@vueuse/core'; import { IssuerEnum } from '@/generated-client'; import { CVSS_V3 } from '@/constants'; import type { ZodAffectType, ZodAffectCVSSType } from '@/types'; -export function watchedPropRef(prop: Record, property: string, defaultValue: any) { - const reffedProp = toRef(prop, property); - const flexRef = reffedProp.value === undefined ? ref(defaultValue) : reffedProp; - watch(reffedProp, value => flexRef.value = value); - return flexRef; -} - export function unwrap(value: any): any { const unwrapped = toRaw(unref(value)); return isUnwrappable(unwrapped) ? unwrap(unwrapped) : unwrapped; @@ -116,3 +110,13 @@ export function isAffectIn(affect: ZodAffectType, affects: ZodAffectType[]) { export function matcherForAffect(affect: ZodAffectType) { return (affectToMatch: ZodAffectType) => doAffectsMatch(affect, affectToMatch); } + +export function watchedRef(): [Ref, Ref]; +export function watchedRef(initialValue: T): [Ref, Ref]; +export function watchedRef(initialValue?: T): [Ref<(T | undefined) | (undefined | UnwrapRef)>, Ref] { + const refValue = ref(initialValue); + const hasChanged = ref(false); + + watchOnce(refValue, () => hasChanged.value = true); + return [refValue, hasChanged] as const; +} diff --git a/src/views/__tests__/__snapshots__/FlawCreateView.spec.ts.snap b/src/views/__tests__/__snapshots__/FlawCreateView.spec.ts.snap index e08227cb3..44096aa87 100644 --- a/src/views/__tests__/__snapshots__/FlawCreateView.spec.ts.snap +++ b/src/views/__tests__/__snapshots__/FlawCreateView.spec.ts.snap @@ -252,6 +252,7 @@ exports[`flawCreateView > should render 1`] = `
+
diff --git a/src/components/__tests__/FlawForm.spec.ts b/src/components/__tests__/FlawForm.spec.ts index 1307d298b..28175f4d7 100644 --- a/src/components/__tests__/FlawForm.spec.ts +++ b/src/components/__tests__/FlawForm.spec.ts @@ -43,6 +43,8 @@ vi.mock('@/composables/useTrackers', () => { }; }); +vi.mock('@/services/LabelsService'); + const flawProps = (flaw: ZodFlawType, mode: 'create' | 'edit') => ({ flaw, mode, relatedFlaws: [flaw] }); const flawEditModeProps = (flaw: ZodFlawType) => flawProps(flaw, 'edit'); const flawCreateModeProps = (flaw: ZodFlawType) => flawProps(flaw, 'create'); diff --git a/src/components/__tests__/__snapshots__/FlawForm.spec.ts.snap b/src/components/__tests__/__snapshots__/FlawForm.spec.ts.snap index 2c0bea69d..cfa4f55af 100644 --- a/src/components/__tests__/__snapshots__/FlawForm.spec.ts.snap +++ b/src/components/__tests__/__snapshots__/FlawForm.spec.ts.snap @@ -317,20 +317,9 @@ exports[`flawForm > mounts and renders 1`] = `
- - - - - - - - - - - - - -
StateLabelContributorActions
+
+

No available labels

+
diff --git a/src/composables/useFlawModel.ts b/src/composables/useFlawModel.ts index 37b688627..b39c447d3 100644 --- a/src/composables/useFlawModel.ts +++ b/src/composables/useFlawModel.ts @@ -30,6 +30,7 @@ import { } from '@/types'; import { createSuccessHandler, createCatchHandler } from './service-helpers'; +import { useFlawLabels } from './useFlawLabels'; export function useFlawModel(forFlaw: ZodFlawType, onSaveSuccess: () => void) { const { flaw } = useFlaw(); @@ -49,6 +50,8 @@ export function useFlawModel(forFlaw: ZodFlawType, onSaveSuccess: () => void) { wereAffectsEditedOrAdded, } = useFlawAffectsModel(); + const { areLabelsUpdated, updateLabels } = useFlawLabels(); + const router = useRouter(); const committedFlaw = ref(null); const { saveDraftFlaw } = useDraftFlawStore(); @@ -168,6 +171,10 @@ export function useFlawModel(forFlaw: ZodFlawType, onSaveSuccess: () => void) { queue.push(saveAffects); } + if (areLabelsUpdated.value) { + queue.push(updateLabels); + } + try { return await execute(...queue); } catch (error) { From 60c6ed5e1e0aedce2e5eeed54690787ffada1cc1 Mon Sep 17 00:00:00 2001 From: Alvaro Tinoco Date: Fri, 24 Jan 2025 16:37:12 +0100 Subject: [PATCH 7/8] =?UTF-8?q?=F0=9F=9A=A8=20Ignore=20error=20on=20type-c?= =?UTF-8?q?heck?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/helpers.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts index d0d9e69b2..714c461c1 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -118,5 +118,8 @@ export function watchedRef(initialValue?: T): [Ref<(T | undefined) | (undefin const hasChanged = ref(false); watchOnce(refValue, () => hasChanged.value = true); + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore - TODO: this throws an error on `yarn type-check` but IDE is happy with it return [refValue, hasChanged] as const; } From a16332cdaff1a7668c6f46cf69456b866016acbd Mon Sep 17 00:00:00 2001 From: Alvaro Tinoco Date: Fri, 24 Jan 2025 16:37:30 +0100 Subject: [PATCH 8/8] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Update=20old=20referen?= =?UTF-8?q?ces=20of=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/IssueQueue/IssueQueueItem.vue | 4 ++-- src/components/__tests__/IssueQueue.spec.ts | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/IssueQueue/IssueQueueItem.vue b/src/components/IssueQueue/IssueQueueItem.vue index 8c1ded3a9..5524c732e 100644 --- a/src/components/IssueQueue/IssueQueueItem.vue +++ b/src/components/IssueQueue/IssueQueueItem.vue @@ -22,13 +22,13 @@ const [showLabels, toggleShowLabels] = useToggle(); const sortedLabels = computed(() => issue.labels?.toSorted((a, b) => { if ( (a.state === 'REQ' && b.state !== 'REQ') - || (a.collaborator && !b.collaborator) + || (a.contributor && !b.contributor) ) { return -1; } if ( (a.state !== 'REQ' && b.state === 'REQ') - || (!a.collaborator && b.collaborator) + || (!a.contributor && b.contributor) ) { return 1; } diff --git a/src/components/__tests__/IssueQueue.spec.ts b/src/components/__tests__/IssueQueue.spec.ts index c6fed65ec..63d85a1b3 100644 --- a/src/components/__tests__/IssueQueue.spec.ts +++ b/src/components/__tests__/IssueQueue.spec.ts @@ -199,9 +199,9 @@ describe('issueQueue', () => { issues: [{ ...mockData[0], labels: [ - { label: 'test', state: 'NEW', collaborator: '' }, - { label: 'test-2', state: 'NEW', collaborator: '' }, - { label: 'test-3', state: 'REQ', collaborator: '' }, + { label: 'test', state: 'NEW', contributor: '' }, + { label: 'test-2', state: 'NEW', contributor: '' }, + { label: 'test-3', state: 'REQ', contributor: '' }, ], } as ZodFlawType], }); @@ -217,7 +217,7 @@ describe('issueQueue', () => { const wrapper = mountIssueQueue({ issues: [{ ...mockData[0], - labels: Array.from({ length: 10 }).map((_, i) => ({ label: `test-${i}`, state: 'NEW', collaborator: '' })), + labels: Array.from({ length: 10 }).map((_, i) => ({ label: `test-${i}`, state: 'NEW', contributor: '' })), } as ZodFlawType], }); @@ -231,7 +231,7 @@ describe('issueQueue', () => { const wrapper = mountIssueQueue({ issues: [{ ...mockData[0], - labels: Array.from({ length: 10 }).map((_, i) => ({ label: `test-${i}`, state: 'NEW', collaborator: '' })), + labels: Array.from({ length: 10 }).map((_, i) => ({ label: `test-${i}`, state: 'NEW', contributor: '' })), } as ZodFlawType], });