diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 288905fbd39..5797c04c518 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-1be74fe6af19847eec28665da39ef03865329acb
+0cea2923073bcd867dd8e718a0a7b4f7de5b6094
diff --git a/app/assets/javascripts/graphql_shared/fragments/issuable_timelogs.fragment.graphql b/app/assets/javascripts/graphql_shared/fragments/issuable_timelogs.fragment.graphql
new file mode 100644
index 00000000000..6ed3be84cd8
--- /dev/null
+++ b/app/assets/javascripts/graphql_shared/fragments/issuable_timelogs.fragment.graphql
@@ -0,0 +1,10 @@
+fragment TimelogFragment on Timelog {
+ timeSpent
+ user {
+ name
+ }
+ spentAt
+ note {
+ body
+ }
+}
diff --git a/app/assets/javascripts/jobs/components/table/cells/job_cell.vue b/app/assets/javascripts/jobs/components/table/cells/job_cell.vue
index 1797992641d..46108572a88 100644
--- a/app/assets/javascripts/jobs/components/table/cells/job_cell.vue
+++ b/app/assets/javascripts/jobs/components/table/cells/job_cell.vue
@@ -1,11 +1,18 @@
@@ -73,6 +83,14 @@ export default {
{{ jobId }}
+
+
+import { GlLoadingIcon, GlTable } from '@gitlab/ui';
+import createFlash from '~/flash';
+import { convertToGraphQLId } from '~/graphql_shared/utils';
+import { formatDate, parseSeconds, stringifyTime } from '~/lib/utils/datetime_utility';
+import { __ } from '~/locale';
+import { timelogQueries } from '~/sidebar/constants';
+
+const TIME_DATE_FORMAT = 'mmmm d, yyyy, HH:MM ("UTC:" o)';
+
+export default {
+ components: {
+ GlLoadingIcon,
+ GlTable,
+ },
+ inject: ['issuableId', 'issuableType'],
+ data() {
+ return { report: [], isLoading: true };
+ },
+ apollo: {
+ report: {
+ query() {
+ return timelogQueries[this.issuableType].query;
+ },
+ variables() {
+ return {
+ id: convertToGraphQLId(this.getGraphQLEntityType(), this.issuableId),
+ };
+ },
+ update(data) {
+ this.isLoading = false;
+ return this.extractTimelogs(data);
+ },
+ error() {
+ createFlash({ message: __('Something went wrong. Please try again.') });
+ },
+ },
+ },
+ methods: {
+ isIssue() {
+ return this.issuableType === 'issue';
+ },
+ getGraphQLEntityType() {
+ // eslint-disable-next-line @gitlab/require-i18n-strings
+ return this.isIssue() ? 'Issue' : 'MergeRequest';
+ },
+ extractTimelogs(data) {
+ const timelogs = data?.issuable?.timelogs?.nodes || [];
+ return timelogs.slice().sort((a, b) => new Date(a.spentAt) - new Date(b.spentAt));
+ },
+ formatDate(date) {
+ return formatDate(date, TIME_DATE_FORMAT);
+ },
+ getNote(note) {
+ return note?.body;
+ },
+ getTotalTimeSpent() {
+ const seconds = this.report.reduce((acc, item) => acc + item.timeSpent, 0);
+ return this.formatTimeSpent(seconds);
+ },
+ formatTimeSpent(seconds) {
+ const negative = seconds < 0;
+ return (negative ? '- ' : '') + stringifyTime(parseSeconds(seconds));
+ },
+ },
+ fields: [
+ { key: 'spentAt', label: __('Spent At'), sortable: true },
+ { key: 'user', label: __('User'), sortable: true },
+ { key: 'timeSpent', label: __('Time Spent'), sortable: true },
+ { key: 'note', label: __('Note'), sortable: true },
+ ],
+};
+
+
+
+
+
+
+
+ {{ formatDate(spentAt) }}
+
+
+
+
+ {{ user.name }}
+
+
+
+
+ {{ formatTimeSpent(timeSpent) }}
+
+
+ {{ getTotalTimeSpent() }}
+
+
+
+ {{ getNote(note) }}
+
+
+
+
+
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
index 4c095006dd7..ac3d278d840 100644
--- a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
+++ b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
@@ -1,10 +1,11 @@