From 79fc503a0184a48fb7bb5e2efc10cea07ffa6cb7 Mon Sep 17 00:00:00 2001 From: jorgep Date: Mon, 18 Sep 2023 14:35:21 +0200 Subject: [PATCH 01/17] ref #6104 recreate vnlog --- package.json | 2 +- src/components/FetchData.vue | 2 +- src/components/common/VnJsonValue.vue | 88 ++ src/components/common/VnLog.vue | 1133 +++++++++++++++++-- src/composables/useColor.js | 35 + src/composables/useFirstUpper.js | 3 + src/composables/useIso8601.js | 18 + src/css/app.scss | 6 + src/filters/index.js | 2 + src/filters/toRelativeDate.js | 32 + src/i18n/en/index.js | 9 + src/i18n/es/index.js | 9 + src/stores/useValidationsStore.js | 19 + test/cypress/integration/ClaimNotes.spec.js | 1 + test/cypress/integration/vnLog.spec.js | 31 + test/cypress/integration/workerList.spec.js | 10 +- 16 files changed, 1279 insertions(+), 121 deletions(-) create mode 100644 src/components/common/VnJsonValue.vue create mode 100644 src/composables/useColor.js create mode 100644 src/composables/useFirstUpper.js create mode 100644 src/composables/useIso8601.js create mode 100644 src/filters/toRelativeDate.js create mode 100644 src/stores/useValidationsStore.js create mode 100644 test/cypress/integration/vnLog.spec.js diff --git a/package.json b/package.json index b713c906a..8568d507d 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "lint": "eslint --ext .js,.vue ./", "format": "prettier --write \"**/*.{js,vue,scss,html,md,json}\" --ignore-path .gitignore", "test:e2e": "cypress open", - "test:e2e:ci": "cypress run --browser chromium", + "test:e2e:ci": "cypress run --browser chrome", "test": "echo \"See package.json => scripts for available tests.\" && exit 0", "test:unit": "vitest", "test:unit:ci": "vitest run" diff --git a/src/components/FetchData.vue b/src/components/FetchData.vue index 251d7502a..f0d908972 100644 --- a/src/components/FetchData.vue +++ b/src/components/FetchData.vue @@ -46,7 +46,7 @@ async function fetch() { if ($props.limit) filter.limit = $props.limit; const { data } = await axios.get($props.url, { - params: { filter }, + params: { filter: JSON.stringify(filter) }, }); emit('onFetch', data); diff --git a/src/components/common/VnJsonValue.vue b/src/components/common/VnJsonValue.vue new file mode 100644 index 000000000..a2e858d0d --- /dev/null +++ b/src/components/common/VnJsonValue.vue @@ -0,0 +1,88 @@ + + + + + diff --git a/src/components/common/VnLog.vue b/src/components/common/VnLog.vue index 1213c8bbc..7e3cfc408 100644 --- a/src/components/common/VnLog.vue +++ b/src/components/common/VnLog.vue @@ -1,128 +1,641 @@ - + en: + to: To + pointRecord: View record at this point in time + recordChanges: show all record changes + tooltips: + search: Search by id or concept + changes: Search by changes actions: - insert: Creates - update: Updates - delete: Deletes + Creates: Creates + Edits: Edits + Deletes: Deletes + Accesses: Accesses models: - Claim: Claim - ClaimDms: Document - ClaimBeginning: Claimed Sales - ClaimObservation: Observation + Claim: Reclamación + ClaimDms: Documento + ClaimBeginning: Comienzo + ClaimObservation: Observación + Users: + User: Usuario + All: Todo + System: Sistema properties: id: ID claimFk: Claim ID @@ -172,6 +1071,12 @@ en: responsibility: Responsibility packages: Packages es: + to: Hasta + pointRecord: Ver el registro en este punto + recordChanges: Mostrar todos los cambios realizados en el registro + tooltips: + search: Buscar por identificador o concepto + changes: Buscar por cambios. Los atributos deben buscarse por su nombre interno, para obtenerlo situar el cursor sobre el atributo. Audit logs: Registros de auditoría Property: Propiedad Before: Antes @@ -179,14 +1084,14 @@ es: Yes: Si Nothing: Nada actions: - insert: Crea - update: Actualiza - delete: Elimina - models: - Claim: Reclamación - ClaimDms: Documento - ClaimBeginning: Línea reclamada - ClaimObservation: Observación + Creates: Crea + Edits: Modifica + Deletes: Elimina + Accesses: Accede + Users: + User: Usuario + All: Todo + System: Sistema properties: id: ID claimFk: ID reclamación diff --git a/src/composables/useColor.js b/src/composables/useColor.js new file mode 100644 index 000000000..b325e985f --- /dev/null +++ b/src/composables/useColor.js @@ -0,0 +1,35 @@ +export function djb2a(string) { + let hash = 5381; + for (let i = 0; i < string.length; i++) + hash = ((hash << 5) + hash) ^ string.charCodeAt(i); + return hash >>> 0; +} + +export function useColor(value) { + return '#' + colors[djb2a(value || '') % colors.length]; +} + +const colors = [ + 'b5b941', // Yellow + 'ae9681', // Peach + 'd78767', // Salmon + 'cc7000', // Orange bright + 'e2553d', // Coral + '8B0000', // Red dark + 'de4362', // Red crimson + 'FF1493', // Ping intense + 'be39a2', // Pink light + 'b754cf', // Purple middle + 'a87ba8', // Pink + '8a69cd', // Blue lavender + 'ab20ab', // Purple dark + '00b5b8', // Turquoise + '1fa8a1', // Green ocean + '5681cf', // Blue steel + '3399fe', // Blue sky + '6d9c3e', // Green chartreuse + '51bb51', // Green lime + '518b8b', // Gray board + '7e7e7e', // Gray + '5d5d5d', // Gray dark +]; diff --git a/src/composables/useFirstUpper.js b/src/composables/useFirstUpper.js new file mode 100644 index 000000000..36378c05f --- /dev/null +++ b/src/composables/useFirstUpper.js @@ -0,0 +1,3 @@ +export function useFirstUpper(str) { + return str && str.charAt(0).toUpperCase() + str.substr(1); +} diff --git a/src/composables/useIso8601.js b/src/composables/useIso8601.js new file mode 100644 index 000000000..848e60de8 --- /dev/null +++ b/src/composables/useIso8601.js @@ -0,0 +1,18 @@ +export function useIso8601(dateString) { + // Crear un objeto Date a partir de la cadena de texto + const date = new Date(dateString); + + // Obtener los componentes de fecha y hora + const year = date.getUTCFullYear(); + const month = String(date.getUTCMonth() + 1).padStart(2, '0'); + const day = String(date.getUTCDate()).padStart(2, '0'); + const hours = String(date.getUTCHours()).padStart(2, '0'); + const minutes = String(date.getUTCMinutes()).padStart(2, '0'); + const seconds = String(date.getUTCSeconds()).padStart(2, '0'); + const milliseconds = String(date.getUTCMilliseconds()).padStart(3, '0'); + + // Formatear la cadena en el formato ISO 8601 + const formattedDate = `${year}-${month}-${day}T${hours}:${minutes}:${seconds}.${milliseconds}Z`; + + return formattedDate; +} diff --git a/src/css/app.scss b/src/css/app.scss index 3c8cc50b6..9ed51c1c7 100644 --- a/src/css/app.scss +++ b/src/css/app.scss @@ -14,6 +14,10 @@ a { color: $orange-4; } +.rounded--full { + border-radius: 50%; +} + // Removes chrome autofill background input:-webkit-autofill, select:-webkit-autofill { @@ -32,10 +36,12 @@ body.body--light { --vn-text: #000000; --vn-gray: #f5f5f5; --vn-label: #5f5f5f; + --vn-header: #e9e9e9; } body.body--dark { --vn-text: #ffffff; --vn-gray: #313131; --vn-label: #a8a8a8; + --vn-header: #212121; } diff --git a/src/filters/index.js b/src/filters/index.js index 158ce1009..b0c441641 100644 --- a/src/filters/index.js +++ b/src/filters/index.js @@ -2,6 +2,7 @@ import toLowerCase from './toLowerCase'; import toDate from './toDate'; import toDateString from './toDateString'; import toDateHour from './toDateHour'; +import toRelativeDate from './toRelativeDate'; import toCurrency from './toCurrency'; import toPercentage from './toPercentage'; import toLowerCamel from './toLowerCamel'; @@ -13,6 +14,7 @@ export { toDate, toDateString, toDateHour, + toRelativeDate, toCurrency, toPercentage, dashIfEmpty, diff --git a/src/filters/toRelativeDate.js b/src/filters/toRelativeDate.js new file mode 100644 index 000000000..76e67dbea --- /dev/null +++ b/src/filters/toRelativeDate.js @@ -0,0 +1,32 @@ +import { useI18n } from 'vue-i18n'; + +export default function formatDate(dateVal) { + const { t } = useI18n(); + const today = new Date(); + if (dateVal == null) return ''; + + const date = new Date(dateVal); + const dateZeroTime = new Date(dateVal); + dateZeroTime.setHours(0, 0, 0, 0); + const diff = Math.trunc( + (today.getTime() - dateZeroTime.getTime()) / (1000 * 3600 * 24) + ); + let format; + if (diff === 0) format = t('globals.today'); + else if (diff === 1) format = t('globals.yesterday'); + else if (diff > 1 && diff < 7) { + const options = { weekday: 'short' }; + format = date.toLocaleDateString(t('globals.dateFormat'), options); + } else if (today.getFullYear() === date.getFullYear()) { + const options = { day: 'numeric', month: 'short' }; + format = date.toLocaleDateString(t('globals.dateFormat'), options); + } else { + const options = { year: 'numeric', month: '2-digit', day: '2-digit' }; + format = date.toLocaleDateString(t('globals.dateFormat'), options); + } + + // Formatear la hora en HH:mm + const hours = date.getHours().toString().padStart(2, '0'); + const minutes = date.getMinutes().toString().padStart(2, '0'); + return `${format} ${hours}:${minutes}`; +} diff --git a/src/i18n/en/index.js b/src/i18n/en/index.js index d7519ba53..285fb778f 100644 --- a/src/i18n/en/index.js +++ b/src/i18n/en/index.js @@ -5,6 +5,9 @@ export default { en: 'English', }, language: 'Language', + entity: 'Entity', + user: 'User', + details: 'Details', collapseMenu: 'Collapse left menu', backToDashboard: 'Return to dashboard', notifications: 'Notifications', @@ -13,8 +16,11 @@ export default { pinnedModules: 'Pinned modules', darkMode: 'Dark mode', logOut: 'Log out', + date: 'Date', dataSaved: 'Data saved', dataDeleted: 'Data deleted', + search: 'Search', + changes: 'Changes', add: 'Add', create: 'Create', save: 'Save', @@ -36,6 +42,9 @@ export default { summary: { basicData: 'Basic data', }, + today: 'Today', + yesterday: 'Yesterday', + dateFormat: 'en-GB', }, errors: { statusUnauthorized: 'Access denied', diff --git a/src/i18n/es/index.js b/src/i18n/es/index.js index fc2c80f55..97001eab8 100644 --- a/src/i18n/es/index.js +++ b/src/i18n/es/index.js @@ -5,6 +5,9 @@ export default { en: 'Inglés', }, language: 'Idioma', + entity: 'Entidad', + user: 'Usuario', + details: 'Detalles', collapseMenu: 'Contraer menú lateral', backToDashboard: 'Volver al tablón', notifications: 'Notificaciones', @@ -13,8 +16,11 @@ export default { pinnedModules: 'Módulos fijados', darkMode: 'Modo oscuro', logOut: 'Cerrar sesión', + date: 'Fecha', dataSaved: 'Datos guardados', dataDeleted: 'Datos eliminados', + search: 'Buscar', + changes: 'Cambios', add: 'Añadir', create: 'Crear', save: 'Guardar', @@ -36,6 +42,9 @@ export default { summary: { basicData: 'Datos básicos', }, + today: 'Hoy', + yesterday: 'Ayer', + dateFormat: 'es-ES', }, errors: { statusUnauthorized: 'Acceso denegado', diff --git a/src/stores/useValidationsStore.js b/src/stores/useValidationsStore.js new file mode 100644 index 000000000..c658a90af --- /dev/null +++ b/src/stores/useValidationsStore.js @@ -0,0 +1,19 @@ +import axios from 'axios'; +import { defineStore } from 'pinia'; + +export const useValidationsStore = defineStore('validationsStore', { + state: () => ({ + validations: null, + }), + actions: { + async fetchModels() { + if (this.validations) return; + try { + const { data } = await axios.get('Schemas/modelinfo'); + this.validations = data; + } catch (error) { + console.error('Error al obtener las validaciones:', error); + } + }, + }, +}); diff --git a/test/cypress/integration/ClaimNotes.spec.js b/test/cypress/integration/ClaimNotes.spec.js index 5b52dd339..9f52c29e4 100644 --- a/test/cypress/integration/ClaimNotes.spec.js +++ b/test/cypress/integration/ClaimNotes.spec.js @@ -8,6 +8,7 @@ describe('ClaimNotes', () => { it('should add a new note', () => { const message = 'This is a new message.'; cy.get('.q-page-sticky button').click(); + cy.get('.q-page-sticky > div > button').click(); cy.get('.q-dialog .q-card__section:nth-child(2)').type(message); cy.get('.q-card__actions button:nth-child(2)').click(); cy.get('.q-card .q-card__section:nth-child(2)') diff --git a/test/cypress/integration/vnLog.spec.js b/test/cypress/integration/vnLog.spec.js new file mode 100644 index 000000000..3d6d9b95d --- /dev/null +++ b/test/cypress/integration/vnLog.spec.js @@ -0,0 +1,31 @@ +/// +describe('ClaimNotes', () => { + beforeEach(() => { + cy.login('developer'); + cy.visit(`/#/claim/${1}/log`); + }); + + it('should have just one record', () => { + cy.get('.model-info .q-btn').eq(1).click(); + cy.get('.user-log .model-log').its('length').should('eq', 1); + cy.get('.q-page-sticky .q-btn').click(); + cy.get('.user-log .model-log').its('length').should('eq', 4); + }); + + it('should filter by insert actions', () => { + cy.get('.q-gutter-x-sm > .q-btn > .q-btn__content > .q-icon').click(); + cy.get('.q-checkbox__inner').eq(0).click(); + cy.get('.q-page > .q-drawer-container > .fullscreen').click(); + cy.get('.model-info .q-chip__content').eq(0).should('have.text', 'Document'); + cy.get('.model-info .q-chip__content').eq(1).should('have.text', 'Beginning'); + }); + + it('should show the point record', () => { + cy.get('.pit').eq(0).click(); + cy.get('.q-menu .q-card .header').should('have.text', 'Observation #1'); + cy.get('.q-menu .q-card .json-string').should( + 'have.text', + 'Waiting for customer' + ); + }); +}); diff --git a/test/cypress/integration/workerList.spec.js b/test/cypress/integration/workerList.spec.js index d76958367..8d4dd770d 100644 --- a/test/cypress/integration/workerList.spec.js +++ b/test/cypress/integration/workerList.spec.js @@ -8,17 +8,17 @@ describe('WorkerList', () => { it('should load workers', () => { cy.get('.card-list-body > .list-items > :nth-child(2) > .value > span') .eq(0) - .should('have.text', 'victorvd'); - cy.get('.card-list-body > .list-items > :nth-child(2) > .value > span') - .eq(1) .should('have.text', 'JessicaJones'); cy.get('.card-list-body > .list-items > :nth-child(2) > .value > span') - .eq(2) + .eq(1) .should('have.text', 'BruceBanner'); + cy.get('.card-list-body > .list-items > :nth-child(2) > .value > span') + .eq(2) + .should('have.text', 'CharlesXavier'); }); it('should open the worker summary', () => { - cy.get('.card-list-body .actions .q-btn:nth-child(2)').eq(1).click(); + cy.get('.card-list-body .actions .q-btn:nth-child(2)').eq(0).click(); cy.get('.summaryHeader div').should('have.text', '1110 - Jessica Jones'); cy.get('.summary .header').eq(0).invoke('text').should('include', 'Basic data'); cy.get('.summary .header').eq(1).should('have.text', 'User data'); From bdf288241b043b6192c6e96fa02aa10179b6ebf6 Mon Sep 17 00:00:00 2001 From: jorgep Date: Mon, 18 Sep 2023 14:38:35 +0200 Subject: [PATCH 02/17] ref #6104 fix package --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8568d507d..b713c906a 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "lint": "eslint --ext .js,.vue ./", "format": "prettier --write \"**/*.{js,vue,scss,html,md,json}\" --ignore-path .gitignore", "test:e2e": "cypress open", - "test:e2e:ci": "cypress run --browser chrome", + "test:e2e:ci": "cypress run --browser chromium", "test": "echo \"See package.json => scripts for available tests.\" && exit 0", "test:unit": "vitest", "test:unit:ci": "vitest run" From ca28ecdd8ecbfd2f1633b3ae7c8ba3f44033fc19 Mon Sep 17 00:00:00 2001 From: jorgep Date: Tue, 26 Sep 2023 11:49:57 +0200 Subject: [PATCH 03/17] ref #6104 tests created and searchFn fixed --- package.json | 2 +- src/components/common/VnLog.vue | 51 +++--- src/css/app.scss | 4 - src/pages/Claim/Card/ClaimDescriptor.vue | 2 +- src/stores/useValidationsStore.js | 37 +++-- .../__tests__/components/common/VnLog.spec.js | 150 ++++++++++++------ test/vitest/helper.js | 2 +- 7 files changed, 154 insertions(+), 94 deletions(-) diff --git a/package.json b/package.json index b713c906a..8568d507d 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "lint": "eslint --ext .js,.vue ./", "format": "prettier --write \"**/*.{js,vue,scss,html,md,json}\" --ignore-path .gitignore", "test:e2e": "cypress open", - "test:e2e:ci": "cypress run --browser chromium", + "test:e2e:ci": "cypress run --browser chrome", "test": "echo \"See package.json => scripts for available tests.\" && exit 0", "test:unit": "vitest", "test:unit:ci": "vitest run" diff --git a/src/components/common/VnLog.vue b/src/components/common/VnLog.vue index 7e3cfc408..6ee09610a 100644 --- a/src/components/common/VnLog.vue +++ b/src/components/common/VnLog.vue @@ -15,15 +15,16 @@ import FetchData from '../FetchData.vue'; import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue'; const stateStore = useStateStore(); + const route = useRoute(); const { t } = useI18n(); -const validationsStore = useValidationsStore(); const props = defineProps({ model: { type: String, default: null, }, }); + const filter = { fields: [ 'id', @@ -160,19 +161,21 @@ function getLogs(data) { let originLog = null; let userLog = null; let modelLog = null; - let prevLog, prevUser, prevModel; + let prevLog; let nLogs; data.forEach((log) => { - const locale = validationsStore.validations[log.changedModel]?.locale || {}; + const locale = validations[log.changedModel]?.locale || {}; // Origin - if (!prevLog || prevLog.originFk != log.originFk) { + const originChanged = !prevLog || log.originFk != prevLog.originFk; + if (originChanged) { logs.push((originLog = { originFk: log.originFk, logs: [] })); prevLog = log; } // User - if (prevUser != log.userFk) { + const userChanged = originChanged || log.userFk != prevLog.userFk; + if (userChanged) { originLog.logs.push( (userLog = { user: log.user, @@ -180,15 +183,14 @@ function getLogs(data) { logs: [], }) ); - prevUser = log.userFk; } // Model - if ( - !prevModel || - prevModel.changedModelId != log.changedModelId || - prevModel.changedModel != log.changedModel || - nLogs >= 6 - ) { + const modelChanged = + userChanged || + log.changedModel != prevLog.changedModel || + log.changedModelId != prevLog.changedModelId || + nLogs >= 6; + if (modelChanged) { userLog.logs.push( (modelLog = { model: log.changedModel, @@ -198,10 +200,6 @@ function getLogs(data) { logs: [], }) ); - prevModel = { - changedModelId: log.changedModelId, - changedModel: log.changedModel, - }; nLogs = 0; } nLogs++; @@ -224,12 +222,12 @@ async function openPointRecord(id, modelLog) { pointRecord.value = null; const { data } = await axios.get(`${props.model}Logs/${id}/pitInstance`); const propNames = Object.keys(data); - const locale = validationsStore.validations[modelLog.model]?.locale || {}; + const locale = validations[modelLog.model]?.locale || {}; pointRecord.value = parseProps(propNames, locale, data); } async function setLogTree() { if (!validations) { - validations = await validationsStore.fetchModels(); + validations = await useValidationsStore(); } filter.where = { and: [{ originFk: route.params.id }] }; const { data } = await axios.get(`${props.model}Logs`, { @@ -377,7 +375,7 @@ function filterFn(val, update, abortFn, type) { const needle = val.toLowerCase(); if (type === 'actions') filteredActions.value = actions.value.filter((item) => - item.toLowerCase().includes(needle) + t(`models.${item}`).toLowerCase().includes(needle) ); if (type === 'workers') { if (isNaN(needle)) @@ -417,11 +415,11 @@ setLogTree(); :key="originLogIndex" > -
- {{ originLog.modelI18n }} +
+ {{ useFirstUpper(validations[props.model].locale.name) }} #{{ originLog.originFk }}
-
+
@@ -875,6 +873,9 @@ setLogTree(); margin-top: 0; } & > .origin-info { + width: 100%; + max-width: 42em; + margin-top: 28px; gap: 6px; & > .origin-id { @@ -885,13 +886,13 @@ setLogTree(); } & > .line { flex-grow: 1; - background-color: $primary; height: 2px; } } } .user-log { - width: 40em; + width: 100%; + max-width: 40em; & > .timeline { position: relative; diff --git a/src/css/app.scss b/src/css/app.scss index 9ed51c1c7..b9ad4742c 100644 --- a/src/css/app.scss +++ b/src/css/app.scss @@ -14,10 +14,6 @@ a { color: $orange-4; } -.rounded--full { - border-radius: 50%; -} - // Removes chrome autofill background input:-webkit-autofill, select:-webkit-autofill { diff --git a/src/pages/Claim/Card/ClaimDescriptor.vue b/src/pages/Claim/Card/ClaimDescriptor.vue index 914de2eb2..f03a1d8a2 100644 --- a/src/pages/Claim/Card/ClaimDescriptor.vue +++ b/src/pages/Claim/Card/ClaimDescriptor.vue @@ -109,7 +109,7 @@ const setData = (entity) => { diff --git a/src/stores/useValidationsStore.js b/src/stores/useValidationsStore.js index c658a90af..5b196fa4c 100644 --- a/src/stores/useValidationsStore.js +++ b/src/stores/useValidationsStore.js @@ -1,19 +1,26 @@ import axios from 'axios'; import { defineStore } from 'pinia'; -export const useValidationsStore = defineStore('validationsStore', { - state: () => ({ - validations: null, - }), - actions: { - async fetchModels() { - if (this.validations) return; - try { - const { data } = await axios.get('Schemas/modelinfo'); - this.validations = data; - } catch (error) { - console.error('Error al obtener las validaciones:', error); - } +export const useValidationsStore = async () => { + const validationsStore = defineStore('validationsStore', { + state: () => ({ + validations: null, + }), + actions: { + async fetchModels() { + if (this.validations) return; + try { + const { data } = await axios.get('Schemas/modelinfo'); + this.validations = data; + } catch (error) { + console.error('Error al obtener las validaciones:', error); + } + }, }, - }, -}); + }); + const v = validationsStore(); + if (!v.validations) { + await v.fetchModels(); + } + return v.validations; +}; diff --git a/test/vitest/__tests__/components/common/VnLog.spec.js b/test/vitest/__tests__/components/common/VnLog.spec.js index 6787b6d51..79db1b9a5 100644 --- a/test/vitest/__tests__/components/common/VnLog.spec.js +++ b/test/vitest/__tests__/components/common/VnLog.spec.js @@ -2,18 +2,40 @@ import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest'; import { createWrapper } from 'app/test/vitest/helper'; import VnLog from 'src/components/common/VnLog.vue'; +const mockValidations = { + Claim: { + locale: { + name: 'reclamación', + }, + }, + ClaimObservation: { + locale: { + name: 'observación', + }, + }, + ClaimDms: { + locale: { + name: 'documento', + }, + }, + ClaimBeginning: { + locale: { + name: 'comienzo', + }, + }, +}; + describe('VnLog', () => { let vm; + beforeAll(() => { vm = createWrapper(VnLog, { global: { - stubs: ['FetchData', 'VnPaginate'], - mocks: { - fetch: vi.fn(), - }, + stubs: [], + mocks: {}, }, propsData: { - model: "Claim", + model: 'Claim', }, }).vm; }); @@ -22,54 +44,88 @@ describe('VnLog', () => { vi.clearAllMocks(); }); - describe('formatValue()', () => { - it('should return Yes if has a true boolean', async () => { - const result = vm.formatValue(true); - - expect(result).toEqual('Yes'); - }); - it('should return No if has a true boolean', async () => { - const result = vm.formatValue(false); - - expect(result).toEqual('No'); - }); - it('should return Nothing if has no params', async () => { - const result = vm.formatValue(); - - expect(result).toEqual('Nothing'); - }); - it('should return a string from a string value', async () => { - const result = vm.formatValue('Something'); - - expect(result).toEqual(`"Something"`); - }); - it('should call to format a date', async () => { - vi.mock('src/filters', () => ({ - toDate: ()=>{ - return "Date formatted" + it('should correctly set logTree', async () => { + const fakeLogTreeData = [ + { + id: 2, + originFk: 1, + userFk: 18, + action: 'update', + changedModel: 'ClaimObservation', + oldInstance: {}, + newInstance: { + claimFk: 1, + text: 'Waiting for customer', }, - })); - - const result = vm.formatValue('01-01-1970'); - expect(result).toEqual("Date formatted"); - }); + creationDate: '2023-09-18T12:25:34.000Z', + changedModelId: '1', + changedModelValue: null, + description: null, + user: { + id: 18, + name: 'salesPerson', + nickname: 'salesPersonNick', + image: '4fa3ada0-3ac4-11eb-9ab8-27f6fc3b85fd', + worker: { + id: 18, + userFk: 18, + }, + }, + }, + { + id: 1, + originFk: 1, + userFk: 18, + action: 'update', + changedModel: 'Claim', + oldInstance: { + hasToPickUp: false, + }, + newInstance: { + hasToPickUp: true, + }, + creationDate: '2023-09-18T12:25:34.000Z', + changedModelId: '1', + changedModelValue: null, + description: null, + user: { + id: 18, + name: 'salesPerson', + nickname: 'salesPersonNick', + image: '4fa3ada0-3ac4-11eb-9ab8-27f6fc3b85fd', + worker: { + id: 18, + userFk: 18, + }, + }, + }, + ]; + vm.validations = mockValidations; + vm.logTree = vm.getLogs(fakeLogTreeData); + expect(vm.logTree[0].originFk).toEqual(1); + expect(vm.logTree[0].logs[0].user.name).toEqual('salesPerson'); }); - describe('actionColor()', () => { - it('should return positive if insert', async () => { - const result = vm.actionColor('insert'); + it('should correctly set the selectedFilters when filtering', async () => { + await vm.$nextTick(); + vm.searchInput = '1'; + vm.userSelect = '21'; + vm.checkboxOptions.insert.selected = true; + vm.checkboxOptions.update.selected = true; - expect(result).toEqual('positive'); - }); - it('should return positive if update', async () => { - const result = vm.actionColor('update'); + vm.selectFilter('search'); + vm.selectFilter('userSelect'); - expect(result).toEqual('positive'); - }); - it('should return negative if delete', async () => { - const result = vm.actionColor('delete'); + expect(vm.selectedFilters.changedModelId).toEqual('1'); + expect(vm.selectedFilters.userFk).toEqual('21'); + expect(vm.selectedFilters.action).toEqual({ inq: ['insert', 'update'] }); + }); - expect(result).toEqual('negative'); + it('should correctly set the date from', () => { + vm.date = '18-09-2023'; + vm.selectFilter('date', 'from'); + expect(vm.selectedFilters.creationDate).toEqual({ + between: ['2023-09-18T00:00:00.000Z', '2023-09-18T19:59:59.999Z'], }); }); }); diff --git a/test/vitest/helper.js b/test/vitest/helper.js index 8a6fb1415..67e4f960a 100644 --- a/test/vitest/helper.js +++ b/test/vitest/helper.js @@ -12,7 +12,7 @@ installQuasarPlugin({ Dialog, }, }); - +axios.defaults.baseURL = 'http://localhost:9000/api/'; const pinia = createTestingPinia({ createSpy: vi.fn, stubActions: false }); const mockPush = vi.fn(); From 41eccd2885ecdc7dd1a5e00815fccc1eea2c96f3 Mon Sep 17 00:00:00 2001 From: jorgep Date: Wed, 27 Sep 2023 14:07:49 +0200 Subject: [PATCH 04/17] ref #6104 fix claimNotes test --- test/cypress/integration/ClaimNotes.spec.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/cypress/integration/ClaimNotes.spec.js b/test/cypress/integration/ClaimNotes.spec.js index 9f52c29e4..5b52dd339 100644 --- a/test/cypress/integration/ClaimNotes.spec.js +++ b/test/cypress/integration/ClaimNotes.spec.js @@ -8,7 +8,6 @@ describe('ClaimNotes', () => { it('should add a new note', () => { const message = 'This is a new message.'; cy.get('.q-page-sticky button').click(); - cy.get('.q-page-sticky > div > button').click(); cy.get('.q-dialog .q-card__section:nth-child(2)').type(message); cy.get('.q-card__actions button:nth-child(2)').click(); cy.get('.q-card .q-card__section:nth-child(2)') From 4995c993bd9b64dc1352aa74d7b87d6e20cd3a01 Mon Sep 17 00:00:00 2001 From: jorgep Date: Thu, 28 Sep 2023 13:45:14 +0200 Subject: [PATCH 05/17] ref #6104 validations store created --- quasar.config.js | 2 +- src/boot/validations.js | 6 ++++ src/components/common/VnLog.vue | 10 +++--- src/composables/useValidator.js | 33 +++++++---------- src/stores/useValidationsStore.js | 36 ++++++++----------- .../__tests__/components/common/VnLog.spec.js | 4 +-- 6 files changed, 39 insertions(+), 52 deletions(-) create mode 100644 src/boot/validations.js diff --git a/quasar.config.js b/quasar.config.js index cbcbae4dc..2d3c71417 100644 --- a/quasar.config.js +++ b/quasar.config.js @@ -29,7 +29,7 @@ module.exports = configure(function (/* ctx */) { // app boot file (/src/boot) // --> boot files are part of "main.js" // https://v2.quasar.dev/quasar-cli/boot-files - boot: ['i18n', 'axios', 'vnDate'], + boot: ['i18n', 'axios', 'vnDate', 'validations'], // https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css css: ['app.scss'], diff --git a/src/boot/validations.js b/src/boot/validations.js new file mode 100644 index 000000000..31e232f86 --- /dev/null +++ b/src/boot/validations.js @@ -0,0 +1,6 @@ +import { boot } from 'quasar/wrappers'; +import { useValidationsStore } from 'src/stores/useValidationsStore'; + +export default boot(async ({ store }) => { + await useValidationsStore(store).fetchModels(); +}); diff --git a/src/components/common/VnLog.vue b/src/components/common/VnLog.vue index 6ee09610a..2c9451641 100644 --- a/src/components/common/VnLog.vue +++ b/src/components/common/VnLog.vue @@ -4,18 +4,19 @@ import { useI18n } from 'vue-i18n'; import { useRoute } from 'vue-router'; import axios from 'axios'; import { useStateStore } from 'stores/useStateStore'; -import { useValidationsStore } from 'src/stores/useValidationsStore'; import { toRelativeDate, toDateString, toDateHour } from 'src/filters'; import { useColor } from 'src/composables/useColor'; import { useFirstUpper } from 'src/composables/useFirstUpper'; import { useIso8601 } from 'src/composables/useIso8601'; +import { useValidator } from 'src/composables/useValidator'; import VnAvatar from '../ui/VnAvatar.vue'; import VnJsonValue from '../common/VnJsonValue.vue'; import FetchData from '../FetchData.vue'; import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue'; const stateStore = useStateStore(); - +const validationsStore = useValidator(); +const { models } = validationsStore; const route = useRoute(); const { t } = useI18n(); const props = defineProps({ @@ -91,7 +92,7 @@ const checkboxOptions = ref({ }, }); -let validations; +let validations = models; let pointRecord = ref(null); let byRecord = ref(false); const logTree = ref([]); @@ -226,9 +227,6 @@ async function openPointRecord(id, modelLog) { pointRecord.value = parseProps(propNames, locale, data); } async function setLogTree() { - if (!validations) { - validations = await useValidationsStore(); - } filter.where = { and: [{ originFk: route.params.id }] }; const { data } = await axios.get(`${props.model}Logs`, { params: { filter: JSON.stringify(filter) }, diff --git a/src/composables/useValidator.js b/src/composables/useValidator.js index ef2dcbd90..3f9f00367 100644 --- a/src/composables/useValidator.js +++ b/src/composables/useValidator.js @@ -1,21 +1,12 @@ -import { ref } from 'vue'; import { useI18n } from 'vue-i18n'; -import axios from 'axios'; import validator from 'validator'; - - -const models = ref(null); +import { useValidationsStore } from 'src/stores/useValidationsStore'; export function useValidator() { - if (!models.value) fetch(); - - function fetch() { - axios.get('Schemas/ModelInfo') - .then(response => models.value = response.data) - } + const models = useValidationsStore().validations; function validate(propertyRule) { - const modelInfo = models.value; + const modelInfo = models; if (!modelInfo || !propertyRule) return; const rule = propertyRule.split('.'); @@ -38,19 +29,18 @@ export function useValidator() { const { t } = useI18n(); const validations = function (validation) { - return { presence: (value) => { let message = `Value can't be empty`; if (validation.message) - message = t(validation.message) || validation.message + message = t(validation.message) || validation.message; - return !validator.isEmpty(value ? String(value) : '') || message + return !validator.isEmpty(value ? String(value) : '') || message; }, length: (value) => { const options = { min: validation.min || validation.is, - max: validation.max || validation.is + max: validation.max || validation.is, }; value = String(value); @@ -69,14 +59,15 @@ export function useValidator() { }, numericality: (value) => { if (validation.int) - return validator.isInt(value) || 'Value should be integer' - return validator.isNumeric(value) || 'Value should be a number' + return validator.isInt(value) || 'Value should be integer'; + return validator.isNumeric(value) || 'Value should be a number'; }, - custom: (value) => validation.bindedFunction(value) || 'Invalid value' + custom: (value) => validation.bindedFunction(value) || 'Invalid value', }; }; return { - validate + validate, + models, }; -} \ No newline at end of file +} diff --git a/src/stores/useValidationsStore.js b/src/stores/useValidationsStore.js index 5b196fa4c..e00812379 100644 --- a/src/stores/useValidationsStore.js +++ b/src/stores/useValidationsStore.js @@ -1,26 +1,18 @@ import axios from 'axios'; import { defineStore } from 'pinia'; -export const useValidationsStore = async () => { - const validationsStore = defineStore('validationsStore', { - state: () => ({ - validations: null, - }), - actions: { - async fetchModels() { - if (this.validations) return; - try { - const { data } = await axios.get('Schemas/modelinfo'); - this.validations = data; - } catch (error) { - console.error('Error al obtener las validaciones:', error); - } - }, +export const useValidationsStore = defineStore('validationsStore', { + state: () => ({ + validations: null, + }), + actions: { + async fetchModels() { + try { + const { data } = await axios.get('Schemas/modelinfo'); + this.validations = data; + } catch (error) { + console.error('Error al obtener las validaciones:', error); + } }, - }); - const v = validationsStore(); - if (!v.validations) { - await v.fetchModels(); - } - return v.validations; -}; + }, +}); diff --git a/test/vitest/__tests__/components/common/VnLog.spec.js b/test/vitest/__tests__/components/common/VnLog.spec.js index 79db1b9a5..c818b3398 100644 --- a/test/vitest/__tests__/components/common/VnLog.spec.js +++ b/test/vitest/__tests__/components/common/VnLog.spec.js @@ -121,11 +121,11 @@ describe('VnLog', () => { expect(vm.selectedFilters.action).toEqual({ inq: ['insert', 'update'] }); }); - it('should correctly set the date from', () => { + /*it('should correctly set the date from', () => { vm.date = '18-09-2023'; vm.selectFilter('date', 'from'); expect(vm.selectedFilters.creationDate).toEqual({ between: ['2023-09-18T00:00:00.000Z', '2023-09-18T19:59:59.999Z'], }); - }); + }); */ }); From 30791acba648d3b63cb97fcea99ae761d6550cc0 Mon Sep 17 00:00:00 2001 From: jorgep Date: Thu, 28 Sep 2023 13:56:21 +0200 Subject: [PATCH 06/17] ref #6104 replace color vars --- src/components/common/VnLog.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/common/VnLog.vue b/src/components/common/VnLog.vue index 2c9451641..27b7c3622 100644 --- a/src/components/common/VnLog.vue +++ b/src/components/common/VnLog.vue @@ -955,7 +955,7 @@ setLogTree(); } .change-info { overflow: hidden; - background-color: var(--vn-header); + background-color: var(--vn-dark); & > .date { overflow: hidden; white-space: nowrap; From 9a06a1296c7d499cf863293b4ffc38d39aeaa75e Mon Sep 17 00:00:00 2001 From: jorgep Date: Thu, 28 Sep 2023 13:57:39 +0200 Subject: [PATCH 07/17] ref #6104 conflicts fixed --- src/composables/useValidator.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/composables/useValidator.js b/src/composables/useValidator.js index 577cfa0bf..3f9f00367 100644 --- a/src/composables/useValidator.js +++ b/src/composables/useValidator.js @@ -68,9 +68,6 @@ export function useValidator() { return { validate, -<<<<<<< HEAD models, -======= ->>>>>>> 11305fc4c8efea147bc6ee9ff25e59e805b62e41 }; } From 288a50b296b33910d53b81ab8865ad617bf28ba9 Mon Sep 17 00:00:00 2001 From: jorgep Date: Fri, 29 Sep 2023 15:03:02 +0200 Subject: [PATCH 08/17] ref #6104 fix validations,css and tests --- src/components/common/VnLog.vue | 48 ++++++++++------- test/cypress/integration/vnLog.spec.js | 24 +++------ .../__tests__/components/common/VnLog.spec.js | 51 +++++++++---------- 3 files changed, 62 insertions(+), 61 deletions(-) diff --git a/src/components/common/VnLog.vue b/src/components/common/VnLog.vue index 27b7c3622..09e580f29 100644 --- a/src/components/common/VnLog.vue +++ b/src/components/common/VnLog.vue @@ -373,7 +373,7 @@ function filterFn(val, update, abortFn, type) { const needle = val.toLowerCase(); if (type === 'actions') filteredActions.value = actions.value.filter((item) => - t(`models.${item}`).toLowerCase().includes(needle) + validations[item].locale.name.includes(needle) ); if (type === 'workers') { if (isNaN(needle)) @@ -507,7 +507,7 @@ setLogTree(); flat round :title="t('pointRecord')" - padding="xs" + padding="none" v-if="log.action != 'insert'" @click.stop=" openPointRecord(log.id, modelLog) @@ -571,8 +571,8 @@ setLogTree(); size="sm" @click="log.expand = !log.expand" /> - - + + - - , + - - + @@ -611,8 +611,8 @@ setLogTree();
- - + + {{ log.description }} @@ -678,11 +678,11 @@ setLogTree(); > @@ -943,11 +943,21 @@ setLogTree(); font-size: 0.9rem; } .q-btn { + visibility: hidden; float: right; } } + + &:hover { + .model-info { + .q-btn { + visibility: visible; + } + } + } } .changes-log { + width: 100%; overflow: hidden; &:last-child { @@ -983,11 +993,18 @@ setLogTree(); } } } + .q-btn.pit { + visibility: hidden; + } + &:hover .q-btn.pit { + visibility: visible; + } } & > .change-detail { - background-color: var(--vn-gray); + position: relative; overflow: hidden; text-overflow: ellipsis; + background-color: var(--vn-gray); white-space: nowrap; box-sizing: border-box; & > .q-icon { @@ -1042,11 +1059,6 @@ en: Edits: Edits Deletes: Deletes Accesses: Accesses - models: - Claim: Reclamación - ClaimDms: Documento - ClaimBeginning: Comienzo - ClaimObservation: Observación Users: User: Usuario All: Todo diff --git a/test/cypress/integration/vnLog.spec.js b/test/cypress/integration/vnLog.spec.js index 3d6d9b95d..bf9edb66c 100644 --- a/test/cypress/integration/vnLog.spec.js +++ b/test/cypress/integration/vnLog.spec.js @@ -1,31 +1,21 @@ /// -describe('ClaimNotes', () => { +describe('VnLog', () => { beforeEach(() => { cy.login('developer'); cy.visit(`/#/claim/${1}/log`); - }); - - it('should have just one record', () => { - cy.get('.model-info .q-btn').eq(1).click(); - cy.get('.user-log .model-log').its('length').should('eq', 1); - cy.get('.q-page-sticky .q-btn').click(); - cy.get('.user-log .model-log').its('length').should('eq', 4); + cy.get('.q-gutter-x-sm > .q-btn > .q-btn__content > .q-icon').click(); }); it('should filter by insert actions', () => { - cy.get('.q-gutter-x-sm > .q-btn > .q-btn__content > .q-icon').click(); cy.get('.q-checkbox__inner').eq(0).click(); - cy.get('.q-page > .q-drawer-container > .fullscreen').click(); + cy.get('.q-page').click(); cy.get('.model-info .q-chip__content').eq(0).should('have.text', 'Document'); cy.get('.model-info .q-chip__content').eq(1).should('have.text', 'Beginning'); }); - it('should show the point record', () => { - cy.get('.pit').eq(0).click(); - cy.get('.q-menu .q-card .header').should('have.text', 'Observation #1'); - cy.get('.q-menu .q-card .json-string').should( - 'have.text', - 'Waiting for customer' - ); + it('should filter by entity', () => { + cy.get('.q-scrollarea .q-list .q-item .q-select').eq(0).click(); + cy.get('.q-scrollarea .q-list .q-item .q-select').eq(0).type('mien'); + cy.get('[role="listbox"] .q-item').should('have.text', 'Comienzo'); }); }); diff --git a/test/vitest/__tests__/components/common/VnLog.spec.js b/test/vitest/__tests__/components/common/VnLog.spec.js index c818b3398..1fd5b3990 100644 --- a/test/vitest/__tests__/components/common/VnLog.spec.js +++ b/test/vitest/__tests__/components/common/VnLog.spec.js @@ -2,31 +2,30 @@ import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest'; import { createWrapper } from 'app/test/vitest/helper'; import VnLog from 'src/components/common/VnLog.vue'; -const mockValidations = { - Claim: { - locale: { - name: 'reclamación', - }, - }, - ClaimObservation: { - locale: { - name: 'observación', - }, - }, - ClaimDms: { - locale: { - name: 'documento', - }, - }, - ClaimBeginning: { - locale: { - name: 'comienzo', - }, - }, -}; - describe('VnLog', () => { let vm; + const mockValidations = { + Claim: { + locale: { + name: 'reclamación', + }, + }, + ClaimObservation: { + locale: { + name: 'observación', + }, + }, + ClaimDms: { + locale: { + name: 'documento', + }, + }, + ClaimBeginning: { + locale: { + name: 'comienzo', + }, + }, + }; beforeAll(() => { vm = createWrapper(VnLog, { @@ -38,6 +37,7 @@ describe('VnLog', () => { model: 'Claim', }, }).vm; + vm.validations = mockValidations; }); afterEach(() => { @@ -100,7 +100,6 @@ describe('VnLog', () => { }, }, ]; - vm.validations = mockValidations; vm.logTree = vm.getLogs(fakeLogTreeData); expect(vm.logTree[0].originFk).toEqual(1); expect(vm.logTree[0].logs[0].user.name).toEqual('salesPerson'); @@ -121,11 +120,11 @@ describe('VnLog', () => { expect(vm.selectedFilters.action).toEqual({ inq: ['insert', 'update'] }); }); - /*it('should correctly set the date from', () => { + it('should correctly set the date from', () => { vm.date = '18-09-2023'; vm.selectFilter('date', 'from'); expect(vm.selectedFilters.creationDate).toEqual({ between: ['2023-09-18T00:00:00.000Z', '2023-09-18T19:59:59.999Z'], }); - }); */ + }); }); From 970226f012da3f24810b5513160cb268ee831df2 Mon Sep 17 00:00:00 2001 From: jorgep Date: Fri, 29 Sep 2023 15:13:19 +0200 Subject: [PATCH 09/17] ref #6104 test e2e fixed --- test/cypress/integration/vnLog.spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/cypress/integration/vnLog.spec.js b/test/cypress/integration/vnLog.spec.js index bf9edb66c..97c9e7a33 100644 --- a/test/cypress/integration/vnLog.spec.js +++ b/test/cypress/integration/vnLog.spec.js @@ -15,7 +15,7 @@ describe('VnLog', () => { it('should filter by entity', () => { cy.get('.q-scrollarea .q-list .q-item .q-select').eq(0).click(); - cy.get('.q-scrollarea .q-list .q-item .q-select').eq(0).type('mien'); - cy.get('[role="listbox"] .q-item').should('have.text', 'Comienzo'); + cy.get('.q-scrollarea .q-list .q-item .q-select').eq(0).type('cla'); + cy.get('[role="listbox"] .q-item').should('have.text', 'Claim'); }); }); From 801ed8ab50081917f5043fb889ce5f16ae84cefa Mon Sep 17 00:00:00 2001 From: jorgep Date: Mon, 2 Oct 2023 09:19:08 +0200 Subject: [PATCH 10/17] ref #6104 remove css --- src/components/common/VnLog.vue | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/components/common/VnLog.vue b/src/components/common/VnLog.vue index 09e580f29..ba9d74ec5 100644 --- a/src/components/common/VnLog.vue +++ b/src/components/common/VnLog.vue @@ -842,22 +842,6 @@ setLogTree(); /> -