Repository: chaoss/grimoirelab-sortinghat (language: python)

Issue:
Different date format in the Jobs
I executed it several times and the date formats were different

- `16/7/2024, 18:45:06`

![image](https://github.com/user-attachments/assets/7a7c0531-6c8b-4771-8696-4d33e31b2fcc)

- `7/17/2024, 6:56:09 PM`

![image](https://github.com/user-attachments/assets/9cd80dcb-bfc1-4030-b557-0424c90b0691)

It would be great if the date format were the same for every execution.


Discussion / hints:

Fix patch:
diff --git a/releases/unreleased/consistent-date-format.yml b/releases/unreleased/consistent-date-format.yml
new file mode 100644
index 00000000..8173e781
--- /dev/null
+++ b/releases/unreleased/consistent-date-format.yml
@@ -0,0 +1,8 @@
+---
+title: Consistent date format
+category: fixed
+author: Eva Millán <evamillan@bitergia.com>
+issue: 901
+notes: >
+  All the job execution dates are now in YYYY-MM-DD hh:mm format
+  on the user interface.
diff --git a/ui/.storybook/preview.js b/ui/.storybook/preview.js
index 4683901b..6cdab53e 100644
--- a/ui/.storybook/preview.js
+++ b/ui/.storybook/preview.js
@@ -1,6 +1,7 @@
 import vuetify from '../src/plugins/vuetify'
 import logger from '../src/plugins/logger';
 import errorMessages from "../src/plugins/errors";
+import dateFormatter from '../src/plugins/dateFormatter';
 import router from "../src/router";
 import { store } from '../src/store';
 import { setup } from '@storybook/vue3';
@@ -9,6 +10,7 @@ setup((app) => {
   app.use(vuetify)
   app.use(logger)
   app.use(errorMessages)
+  app.use(dateFormatter)
   app.use(store)
   app.use(router)
 });
diff --git a/ui/package.json b/ui/package.json
index 16606e8f..e5ab77e5 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -16,6 +16,7 @@
     "@apollo/client": "^3.9.5",
     "@vue/apollo-option": "^4.0.0",
     "core-js": "^3.27.1",
+    "dayjs": "^1.10.4",
     "graphql": "^16.8.1",
     "graphql-tag": "^2.12.6",
     "js-cookie": "^2.2.1",
diff --git a/ui/src/components/JobsTable.vue b/ui/src/components/JobsTable.vue
index 12c554cc..7ab55a24 100644
--- a/ui/src/components/JobsTable.vue
+++ b/ui/src/components/JobsTable.vue
@@ -43,7 +43,7 @@
               </v-tooltip>
             </td>
             <td class="capitalize">{{ job.jobType.replace("_", " ") }}</td>
-            <td>{{ formatDate(job.enqueuedAt) }}</td>
+            <td>{{ $formatDate(job.enqueuedAt) }}</td>
             <td class="text-center">
               <v-chip
                 class="ma-2"
@@ -114,9 +114,6 @@ export default {
         return "rgba(0, 0, 0, 0.42)";
       }
     },
-    formatDate(dateTime) {
-      return dateTime ? new Date(dateTime).toLocaleString("en-US") : "-";
-    },
   },
 };
 </script>
diff --git a/ui/src/components/TasksTable.vue b/ui/src/components/TasksTable.vue
index 49e2181c..63553e49 100644
--- a/ui/src/components/TasksTable.vue
+++ b/ui/src/components/TasksTable.vue
@@ -58,7 +58,7 @@
                 >
                   {{ task.failed ? "mdi-alert" : "mdi-check" }}
                 </v-icon>
-                Last sync {{ formatDate(task.lastExecution) }}
+                Last sync {{ $formatDate(task.lastExecution) }}
               </p>
             </td>
             <td class="text-right pr-8">
@@ -141,12 +141,6 @@ export default {
     };
   },
   methods: {
-    formatDate(dateTime) {
-      if (!dateTime) {
-        return "-";
-      }
-      return new Date(dateTime).toLocaleString("en-US");
-    },
     openModal(backend, task = {}) {
       if (typeof backend === "string") {
         backend = this.importers.find((importer) => importer.name === backend);
diff --git a/ui/src/main.js b/ui/src/main.js
index 320be3b7..d3923976 100644
--- a/ui/src/main.js
+++ b/ui/src/main.js
@@ -14,6 +14,7 @@ import {
 import vuetify from "./plugins/vuetify";
 import logger from "./plugins/logger";
 import errorMessages from "./plugins/errors";
+import dateFormatter from "./plugins/dateFormatter";
 
 const API_URL = process.env.VUE_APP_API_URL || `${process.env.BASE_URL}api/`;
 
@@ -66,6 +67,7 @@ const apolloProvider = new createApolloProvider({
 
 createApp(App)
   .use(apolloProvider)
+  .use(dateFormatter)
   .use(errorMessages)
   .use(logger)
   .use(store)
diff --git a/ui/src/plugins/dateFormatter.js b/ui/src/plugins/dateFormatter.js
new file mode 100644
index 00000000..4df59c0f
--- /dev/null
+++ b/ui/src/plugins/dateFormatter.js
@@ -0,0 +1,16 @@
+import dayjs from "dayjs";
+
+const dateFormatter = {
+  install: (app) => {
+    app.config.globalProperties.$formatDate = (
+      dateTime,
+      dateFormat = "YYYY-MM-DD hh:mm A"
+    ) => {
+      if (!dateTime) return;
+
+      return dayjs(dateTime).format(dateFormat);
+    };
+  },
+};
+
+export default dateFormatter;
diff --git a/ui/src/views/SettingsGeneral.vue b/ui/src/views/SettingsGeneral.vue
index 8983da3a..54f89dcf 100644
--- a/ui/src/views/SettingsGeneral.vue
+++ b/ui/src/views/SettingsGeneral.vue
@@ -20,7 +20,7 @@
                   mdi-alert
                 </v-icon>
                 <v-icon v-else color="green" left small> mdi-check </v-icon>
-                Last run {{ formatDate(tasks.affiliate.lastExecution) }}
+                Last run {{ $formatDate(tasks.affiliate.lastExecution) }}
               </span>
             </v-col>
             <v-col class="d-flex justify-end">
@@ -102,7 +102,7 @@
                 </v-icon>
                 <v-icon v-else color="green" left small> mdi-check </v-icon>
                 <span class="v-list-item__subtitle text-medium-emphasis">
-                  Last run {{ formatDate(tasks.unify.lastExecution) }}
+                  Last run {{ $formatDate(tasks.unify.lastExecution) }}
                 </span>
               </span>
             </v-col>
@@ -309,9 +309,6 @@ export default {
         params: task.args,
       });
     },
-    formatDate(dateTime) {
-      return new Date(dateTime).toLocaleString();
-    },
     toggleTask(job) {
       if (this.tasks[job].isActive) {
         this.createTask(job);
diff --git a/ui/yarn.lock b/ui/yarn.lock
index d7c5de27..8d0f7f84 100644
--- a/ui/yarn.lock
+++ b/ui/yarn.lock
@@ -11016,7 +11016,7 @@ string-length@^5.0.1:
     char-regex "^2.0.0"
     strip-ansi "^7.0.1"
 
-"string-width-cjs@npm:string-width@^4.2.0":
+"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
   version "4.2.3"
   resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
   integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -11033,15 +11033,6 @@ string-width@^2.1.1:
     is-fullwidth-code-point "^2.0.0"
     strip-ansi "^4.0.0"
 
-string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
-  version "4.2.3"
-  resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
-  integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
-  dependencies:
-    emoji-regex "^8.0.0"
-    is-fullwidth-code-point "^3.0.0"
-    strip-ansi "^6.0.1"
-
 string-width@^5.0.1, string-width@^5.1.2:
   version "5.1.2"
   resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794"
@@ -11065,7 +11056,7 @@ string_decoder@~1.1.1:
   dependencies:
     safe-buffer "~5.1.0"
 
-"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
+"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
   version "6.0.1"
   resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
   integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -11079,13 +11070,6 @@ strip-ansi@^4.0.0:
   dependencies:
     ansi-regex "^3.0.0"
 
-strip-ansi@^6.0.0, strip-ansi@^6.0.1:
-  version "6.0.1"
-  resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
-  integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
-  dependencies:
-    ansi-regex "^5.0.1"
-
 strip-ansi@^7.0.1:
   version "7.1.0"
   resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
@@ -12309,7 +12293,7 @@ wordwrap@^1.0.0:
   resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
   integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==
 
-"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
+"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
   version "7.0.0"
   resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
   integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@@ -12335,15 +12319,6 @@ wrap-ansi@^6.2.0:
     string-width "^4.1.0"
     strip-ansi "^6.0.0"
 
-wrap-ansi@^7.0.0:
-  version "7.0.0"
-  resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
-  integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
-  dependencies:
-    ansi-styles "^4.0.0"
-    string-width "^4.1.0"
-    strip-ansi "^6.0.0"
-
 wrap-ansi@^8.1.0:
   version "8.1.0"
   resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"


Test patch:
diff --git a/ui/tests/unit/__snapshots__/storybook.spec.js.snap b/ui/tests/unit/__snapshots__/storybook.spec.js.snap
index 0f81aacf..e3e66b2b 100644
--- a/ui/tests/unit/__snapshots__/storybook.spec.js.snap
+++ b/ui/tests/unit/__snapshots__/storybook.spec.js.snap
@@ -25517,7 +25517,7 @@ exports[`Storybook Tests JobsTable Default 1`] = `
                 recommend affiliations
               </td>
               <td>
-                2/19/2021, 12:09:21 PM
+                2021-02-19 12:09 PM
               </td>
               <td
                 class="text-center"
@@ -25590,7 +25590,7 @@ exports[`Storybook Tests JobsTable Default 1`] = `
                 recommend affiliations
               </td>
               <td>
-                2/19/2021, 2:12:00 PM
+                2021-02-19 02:12 PM
               </td>
               <td
                 class="text-center"
@@ -25663,7 +25663,7 @@ exports[`Storybook Tests JobsTable Default 1`] = `
                 affiliate
               </td>
               <td>
-                2/19/2021, 2:22:10 PM
+                2021-02-19 02:22 PM
               </td>
               <td
                 class="text-center"
@@ -25736,7 +25736,7 @@ exports[`Storybook Tests JobsTable Default 1`] = `
                 recommend affiliations
               </td>
               <td>
-                2/20/2021, 8:01:56 AM
+                2021-02-20 08:01 AM
               </td>
               <td
                 class="text-center"
@@ -25809,7 +25809,7 @@ exports[`Storybook Tests JobsTable Default 1`] = `
                 affiliate
               </td>
               <td>
-                2/20/2021, 10:45:38 AM
+                2021-02-20 10:45 AM
               </td>
               <td
                 class="text-center"
@@ -31599,7 +31599,7 @@ exports[`Storybook Tests TasksTable Default 1`] = `
                     class="mdi-alert mdi v-icon notranslate v-theme--light v-icon--size-default text-failed mb-1 mr-1"
                     small=""
                   />
-                   Last sync 3/14/2023, 2:21:10 PM
+                   Last sync 2023-03-14 02:21 PM
                 </p>
               </td>
               <td
@@ -31699,7 +31699,7 @@ exports[`Storybook Tests TasksTable Default 1`] = `
                     class="mdi-check mdi v-icon notranslate v-theme--light v-icon--size-default text-finished mb-1 mr-1"
                     small=""
                   />
-                   Last sync 3/12/2023, 11:01:40 AM
+                   Last sync 2023-03-12 11:01 AM
                 </p>
               </td>
               <td
diff --git a/ui/tests/unit/storybook.spec.js b/ui/tests/unit/storybook.spec.js
index fef17a3f..eb67162a 100644
--- a/ui/tests/unit/storybook.spec.js
+++ b/ui/tests/unit/storybook.spec.js
@@ -2,6 +2,7 @@ import path from 'path';
 import * as glob from 'glob';
 import errorMessages from "@/plugins/errors";
 import vuetify from "@/plugins/vuetify";
+import dateFormatter from "@/plugins/dateFormatter";
 import { describe, test, expect } from '@jest/globals';
 import { render } from '@testing-library/vue';
 import { composeStories } from '@storybook/vue3';
@@ -66,7 +67,7 @@ describe(options.suite, () => {
         testFn(name, async () => {
           const mounted = render(story(), {
             global: {
-              plugins: [errorMessages, vuetify, store],
+              plugins: [errorMessages, vuetify, store, dateFormatter],
               stubs: ['router-link']
             },
             container: null