diff options
Diffstat (limited to 'app/assets/javascripts/alert_management')
11 files changed, 130 insertions, 78 deletions
diff --git a/app/assets/javascripts/alert_management/components/alert_details.vue b/app/assets/javascripts/alert_management/components/alert_details.vue index 5d260fcc200..c6605452616 100644 --- a/app/assets/javascripts/alert_management/components/alert_details.vue +++ b/app/assets/javascripts/alert_management/components/alert_details.vue @@ -1,4 +1,5 @@ <script> +/* eslint-disable vue/no-v-html */ import * as Sentry from '@sentry/browser'; import { GlAlert, @@ -9,7 +10,6 @@ import { GlTabs, GlTab, GlButton, - GlTable, } from '@gitlab/ui'; import { s__ } from '~/locale'; import alertQuery from '../graphql/queries/details.query.graphql'; @@ -27,6 +27,7 @@ import { toggleContainerClasses } from '~/lib/utils/dom_utils'; import SystemNote from './system_notes/system_note.vue'; import AlertSidebar from './alert_sidebar.vue'; import AlertMetrics from './alert_metrics.vue'; +import AlertDetailsTable from '~/vue_shared/components/alert_details_table.vue'; const containerEl = document.querySelector('.page-with-contextual-sidebar'); @@ -42,18 +43,19 @@ export default { tabsConfig: [ { id: 'overview', - title: s__('AlertManagement|Overview'), - }, - { - id: 'fullDetails', title: s__('AlertManagement|Alert details'), }, { id: 'metrics', title: s__('AlertManagement|Metrics'), }, + { + id: 'activity', + title: s__('AlertManagement|Activity feed'), + }, ], components: { + AlertDetailsTable, GlBadge, GlAlert, GlIcon, @@ -62,7 +64,6 @@ export default { GlTab, GlTabs, GlButton, - GlTable, TimeAgoTooltip, AlertSidebar, SystemNote, @@ -330,32 +331,17 @@ export default { </div> <div class="gl-pl-2" data-testid="runbook">{{ alert.runbook }}</div> </div> - <template> - <div v-if="alert.notes.nodes" class="issuable-discussion py-5"> - <ul class="notes main-notes-list timeline"> - <system-note v-for="note in alert.notes.nodes" :key="note.id" :note="note" /> - </ul> - </div> - </template> + <alert-details-table :alert="alert" :loading="loading" /> </gl-tab> <gl-tab :data-testid="$options.tabsConfig[1].id" :title="$options.tabsConfig[1].title"> - <gl-table - class="alert-management-details-table" - :items="[{ key: 'Value', ...alert }]" - :show-empty="true" - :busy="loading" - stacked - > - <template #empty> - {{ s__('AlertManagement|No alert data to display.') }} - </template> - <template #table-busy> - <gl-loading-icon size="lg" color="dark" class="mt-3" /> - </template> - </gl-table> + <alert-metrics :dashboard-url="alert.metricsDashboardUrl" /> </gl-tab> <gl-tab :data-testid="$options.tabsConfig[2].id" :title="$options.tabsConfig[2].title"> - <alert-metrics :dashboard-url="alert.metricsDashboardUrl" /> + <div v-if="alert.notes.nodes.length > 0" class="issuable-discussion"> + <ul class="notes main-notes-list timeline"> + <system-note v-for="note in alert.notes.nodes" :key="note.id" :note="note" /> + </ul> + </div> </gl-tab> </gl-tabs> <alert-sidebar diff --git a/app/assets/javascripts/alert_management/components/alert_management_table.vue b/app/assets/javascripts/alert_management/components/alert_management_table.vue index 92fd85c6217..0fd00fe90eb 100644 --- a/app/assets/javascripts/alert_management/components/alert_management_table.vue +++ b/app/assets/javascripts/alert_management/components/alert_management_table.vue @@ -1,8 +1,12 @@ <script> +/* eslint-disable vue/no-v-html */ import { GlLoadingIcon, GlTable, GlAlert, + GlAvatarsInline, + GlAvatarLink, + GlAvatar, GlIcon, GlLink, GlTabs, @@ -11,6 +15,7 @@ import { GlPagination, GlSearchBoxByType, GlSprintf, + GlTooltipDirective, } from '@gitlab/ui'; import { debounce, trim } from 'lodash'; import { __, s__ } from '~/locale'; @@ -35,6 +40,7 @@ const tdClass = const thClass = 'gl-hover-bg-blue-50'; const bodyTrClass = 'gl-border-1 gl-border-t-solid gl-border-gray-100 gl-hover-bg-blue-50 gl-hover-cursor-pointer gl-hover-border-b-solid gl-hover-border-blue-200'; +const TH_TEST_ID = { 'data-testid': 'alert-management-severity-sort' }; const initialPaginationState = { currentPage: 1, @@ -55,12 +61,14 @@ export default { "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear.", ), searchPlaceholder: __('Search or filter results...'), + unassigned: __('Unassigned'), }, fields: [ { key: 'severity', label: s__('AlertManagement|Severity'), thClass: `${thClass} gl-w-eighth`, + thAttr: TH_TEST_ID, tdClass: `${tdClass} rounded-top text-capitalize sortable-cell`, sortable: true, }, @@ -72,7 +80,7 @@ export default { sortable: true, }, { - key: 'title', + key: 'alertLabel', label: s__('AlertManagement|Alert'), thClass: `gl-pointer-events-none`, tdClass, @@ -110,6 +118,9 @@ export default { GlLoadingIcon, GlTable, GlAlert, + GlAvatarsInline, + GlAvatarLink, + GlAvatar, TimeAgo, GlIcon, GlLink, @@ -121,6 +132,9 @@ export default { GlSprintf, AlertStatus, }, + directives: { + GlTooltip: GlTooltipDirective, + }, props: { projectPath: { type: String, @@ -264,11 +278,8 @@ export default { const { category, action, label } = trackAlertStatusUpdateOptions; Tracking.event(category, action, { label, property: status }); }, - getAssignees(assignees) { - // TODO: Update to show list of assignee(s) after https://gitlab.com/gitlab-org/gitlab/-/issues/218405 - return assignees.nodes?.length > 0 - ? assignees.nodes[0]?.username - : s__('AlertManagement|Unassigned'); + hasAssignees(assignees) { + return Boolean(assignees.nodes?.length); }, getIssueLink(item) { return joinPaths('/', this.projectPath, '-', 'issues', item.issueIid); @@ -397,8 +408,14 @@ export default { {{ item.eventCount }} </template> - <template #cell(title)="{ item }"> - <div class="gl-max-w-full text-truncate" :title="item.title">{{ item.title }}</div> + <template #cell(alertLabel)="{ item }"> + <div + class="gl-max-w-full text-truncate" + :title="`${item.iid} - ${item.title}`" + data-testid="idField" + > + #{{ item.iid }} {{ item.title }} + </div> </template> <template #cell(issue)="{ item }"> @@ -409,8 +426,32 @@ export default { </template> <template #cell(assignees)="{ item }"> - <div class="gl-max-w-full text-truncate" data-testid="assigneesField"> - {{ getAssignees(item.assignees) }} + <div data-testid="assigneesField"> + <template v-if="hasAssignees(item.assignees)"> + <gl-avatars-inline + :avatars="item.assignees.nodes" + :collapsed="true" + :max-visible="4" + :avatar-size="24" + badge-tooltip-prop="name" + :badge-tooltip-max-chars="100" + > + <template #avatar="{ avatar }"> + <gl-avatar-link + :key="avatar.username" + v-gl-tooltip + target="_blank" + :href="avatar.webUrl" + :title="avatar.name" + > + <gl-avatar :src="avatar.avatarUrl" :label="avatar.name" :size="24" /> + </gl-avatar-link> + </template> + </gl-avatars-inline> + </template> + <template v-else> + {{ $options.i18n.unassigned }} + </template> </div> </template> diff --git a/app/assets/javascripts/alert_management/components/alert_status.vue b/app/assets/javascripts/alert_management/components/alert_status.vue index 8531ca1374e..ff71b348cc9 100644 --- a/app/assets/javascripts/alert_management/components/alert_status.vue +++ b/app/assets/javascripts/alert_management/components/alert_status.vue @@ -101,12 +101,12 @@ export default { @keydown.esc.native="$emit('hide-dropdown')" @hide="$emit('hide-dropdown')" > - <div v-if="isSidebar" class="dropdown-title text-center"> - <span class="alert-title">{{ s__('AlertManagement|Assign status') }}</span> + <div v-if="isSidebar" class="dropdown-title gl-display-flex"> + <span class="alert-title gl-ml-auto">{{ s__('AlertManagement|Assign status') }}</span> <gl-button :aria-label="__('Close')" variant="link" - class="dropdown-title-button dropdown-menu-close" + class="dropdown-title-button dropdown-menu-close gl-ml-auto gl-text-black-normal!" icon="close" @click="$emit('hide-dropdown')" /> diff --git a/app/assets/javascripts/alert_management/components/sidebar/sidebar_assignees.vue b/app/assets/javascripts/alert_management/components/sidebar/sidebar_assignees.vue index 4af5c83b30c..0f354e85e96 100644 --- a/app/assets/javascripts/alert_management/components/sidebar/sidebar_assignees.vue +++ b/app/assets/javascripts/alert_management/components/sidebar/sidebar_assignees.vue @@ -12,7 +12,7 @@ import { } from '@gitlab/ui'; import { debounce } from 'lodash'; import axios from '~/lib/utils/axios_utils'; -import { s__, __ } from '~/locale'; +import { s__ } from '~/locale'; import alertSetAssignees from '../../graphql/mutations/alert_set_assignees.mutation.graphql'; import SidebarAssignee from './sidebar_assignee.vue'; @@ -82,8 +82,11 @@ export default { userName() { return this.alert?.assignees?.nodes[0]?.username; }, - assignedUser() { - return this.userName || __('None'); + userFullName() { + return this.alert?.assignees?.nodes[0]?.name; + }, + userImg() { + return this.alert?.assignees?.nodes[0]?.avatarUrl; }, sortedUsers() { return this.users @@ -184,15 +187,15 @@ export default { </script> <template> - <div class="block alert-status"> - <div ref="status" class="sidebar-collapsed-icon" @click="$emit('toggle-sidebar')"> + <div class="block alert-assignees "> + <div ref="assignees" class="sidebar-collapsed-icon" @click="$emit('toggle-sidebar')"> <gl-icon name="user" :size="14" /> <gl-loading-icon v-if="isUpdating" /> </div> - <gl-tooltip :target="() => $refs.status" boundary="viewport" placement="left"> + <gl-tooltip :target="() => $refs.assignees" boundary="viewport" placement="left"> <gl-sprintf :message="$options.i18n.ASSIGNEES_BLOCK"> <template #assignees> - {{ assignedUser }} + {{ userName }} </template> </gl-sprintf> </gl-tooltip> @@ -215,19 +218,19 @@ export default { <div class="dropdown dropdown-menu-selectable" :class="dropdownClass"> <gl-deprecated-dropdown ref="dropdown" - :text="assignedUser" + :text="userName" class="w-100" toggle-class="dropdown-menu-toggle" variant="outline-default" @keydown.esc.native="hideDropdown" @hide="hideDropdown" > - <div class="dropdown-title"> - <span class="alert-title">{{ __('Assign To') }}</span> + <div class="dropdown-title gl-display-flex"> + <span class="alert-title gl-ml-auto">{{ __('Assign To') }}</span> <gl-button :aria-label="__('Close')" variant="link" - class="dropdown-title-button dropdown-menu-close" + class="dropdown-title-button dropdown-menu-close gl-ml-auto gl-text-black-normal!" icon="close" @click="hideDropdown" /> @@ -272,14 +275,28 @@ export default { </div> <gl-loading-icon v-if="isUpdating" :inline="true" /> - <p v-else-if="!isDropdownShowing" class="value gl-m-0" :class="{ 'no-value': !userName }"> - <span v-if="userName" class="gl-text-gray-500" data-testid="assigned-users">{{ - assignedUser - }}</span> - <span v-else class="gl-display-flex gl-align-items-center"> + <div v-else-if="!isDropdownShowing" class="value gl-m-0" :class="{ 'no-value': !userName }"> + <div v-if="userName" class="gl-display-inline-flex gl-mt-2" data-testid="assigned-users"> + <span class="gl-relative mr-2"> + <img + :alt="userName" + :src="userImg" + :width="32" + class="avatar avatar-inline gl-m-0 s32" + data-qa-selector="avatar_image" + /> + </span> + <span class="gl-display-flex gl-flex-direction-column gl-overflow-hidden"> + <strong class="dropdown-menu-user-full-name"> + {{ userFullName }} + </strong> + <span class="dropdown-menu-user-username">{{ userName }}</span> + </span> + </div> + <span v-else class="gl-display-flex gl-align-items-center gl-line-height-normal"> {{ __('None') }} - <gl-button - class="gl-pl-2" + class="gl-ml-2" href="#" variant="link" data-testid="unassigned-users" @@ -288,7 +305,7 @@ export default { {{ __('assign yourself') }} </gl-button> </span> - </p> + </div> </div> </div> </template> diff --git a/app/assets/javascripts/alert_management/components/sidebar/sidebar_todo.vue b/app/assets/javascripts/alert_management/components/sidebar/sidebar_todo.vue index 5bd69a1f0ec..84d54466a10 100644 --- a/app/assets/javascripts/alert_management/components/sidebar/sidebar_todo.vue +++ b/app/assets/javascripts/alert_management/components/sidebar/sidebar_todo.vue @@ -1,8 +1,9 @@ <script> +import produce from 'immer'; import { s__ } from '~/locale'; import Todo from '~/sidebar/components/todo_toggle/todo.vue'; -import createAlertTodo from '../../graphql/mutations/alert_todo_create.mutation.graphql'; -import todoMarkDone from '../../graphql/mutations/alert_todo_mark_done.mutation.graphql'; +import createAlertTodoMutation from '../../graphql/mutations/alert_todo_create.mutation.graphql'; +import todoMarkDoneMutation from '~/graphql_shared/mutations/todo_mark_done.mutation.graphql'; import alertQuery from '../../graphql/queries/details.query.graphql'; export default { @@ -52,7 +53,7 @@ export default { }, methods: { updateToDoCount(add) { - const oldCount = parseInt(document.querySelector('.todos-count').innerText, 10); + const oldCount = parseInt(document.querySelector('.js-todos-count').innerText, 10); const count = add ? oldCount + 1 : oldCount - 1; const headerTodoEvent = new CustomEvent('todo:toggle', { detail: { @@ -66,7 +67,7 @@ export default { this.isUpdating = true; return this.$apollo .mutate({ - mutation: createAlertTodo, + mutation: createAlertTodoMutation, variables: { iid: this.alert.iid, projectPath: this.projectPath, @@ -89,7 +90,7 @@ export default { this.isUpdating = true; return this.$apollo .mutate({ - mutation: todoMarkDone, + mutation: todoMarkDoneMutation, variables: { id: this.firstToDoId, }, @@ -109,12 +110,15 @@ export default { }); }, updateCache(store) { - const data = store.readQuery({ + const sourceData = store.readQuery({ query: alertQuery, variables: this.getAlertQueryVariables, }); - data.project.alertManagementAlerts.nodes[0].todos.nodes.shift(); + const data = produce(sourceData, draftData => { + // eslint-disable-next-line no-param-reassign + draftData.project.alertManagementAlerts.nodes[0].todos.nodes = []; + }); store.writeQuery({ query: alertQuery, diff --git a/app/assets/javascripts/alert_management/components/system_notes/system_note.vue b/app/assets/javascripts/alert_management/components/system_notes/system_note.vue index 39717ab609f..0b206ce42f4 100644 --- a/app/assets/javascripts/alert_management/components/system_notes/system_note.vue +++ b/app/assets/javascripts/alert_management/components/system_notes/system_note.vue @@ -1,4 +1,5 @@ <script> +/* eslint-disable vue/no-v-html */ import NoteHeader from '~/notes/components/note_header.vue'; import { spriteIcon } from '~/lib/utils/common_utils'; @@ -31,7 +32,7 @@ export default { </script> <template> - <li :id="noteAnchorId" class="timeline-entry note system-note note-wrapper"> + <li :id="noteAnchorId" class="timeline-entry note system-note note-wrapper gl-px-0!"> <div class="timeline-entry-inner"> <div class="timeline-icon" v-html="iconHtml"></div> <div class="timeline-content"> diff --git a/app/assets/javascripts/alert_management/constants.js b/app/assets/javascripts/alert_management/constants.js index b9670466c0f..73cb5ecdf98 100644 --- a/app/assets/javascripts/alert_management/constants.js +++ b/app/assets/javascripts/alert_management/constants.js @@ -64,4 +64,4 @@ export const trackAlertStatusUpdateOptions = { label: 'Status', }; -export const DEFAULT_PAGE_SIZE = 10; +export const DEFAULT_PAGE_SIZE = 20; diff --git a/app/assets/javascripts/alert_management/details.js b/app/assets/javascripts/alert_management/details.js index dccf990f0b4..c2020dfcbe3 100644 --- a/app/assets/javascripts/alert_management/details.js +++ b/app/assets/javascripts/alert_management/details.js @@ -1,5 +1,6 @@ import Vue from 'vue'; import VueApollo from 'vue-apollo'; +import produce from 'immer'; import { defaultDataIdFromObject } from 'apollo-cache-inmemory'; import createDefaultClient from '~/lib/graphql'; import createRouter from './router'; @@ -16,8 +17,11 @@ export default selector => { const resolvers = { Mutation: { toggleSidebarStatus: (_, __, { cache }) => { - const data = cache.readQuery({ query: sidebarStatusQuery }); - data.sidebarStatus = !data.sidebarStatus; + const sourceData = cache.readQuery({ query: sidebarStatusQuery }); + const data = produce(sourceData, draftData => { + // eslint-disable-next-line no-param-reassign + draftData.sidebarStatus = !draftData.sidebarStatus; + }); cache.writeQuery({ query: sidebarStatusQuery, data }); }, }, @@ -34,6 +38,7 @@ export default selector => { return defaultDataIdFromObject(object); }, }, + assumeImmutableResults: true, }), }); diff --git a/app/assets/javascripts/alert_management/graphql/fragments/list_item.fragment.graphql b/app/assets/javascripts/alert_management/graphql/fragments/list_item.fragment.graphql index c37f29c74fc..62119177887 100644 --- a/app/assets/javascripts/alert_management/graphql/fragments/list_item.fragment.graphql +++ b/app/assets/javascripts/alert_management/graphql/fragments/list_item.fragment.graphql @@ -8,7 +8,10 @@ fragment AlertListItem on AlertManagementAlert { issueIid assignees { nodes { + name username + avatarUrl + webUrl } } } diff --git a/app/assets/javascripts/alert_management/graphql/mutations/alert_set_assignees.mutation.graphql b/app/assets/javascripts/alert_management/graphql/mutations/alert_set_assignees.mutation.graphql index 40b4b6ae854..5008bfa5e1b 100644 --- a/app/assets/javascripts/alert_management/graphql/mutations/alert_set_assignees.mutation.graphql +++ b/app/assets/javascripts/alert_management/graphql/mutations/alert_set_assignees.mutation.graphql @@ -10,6 +10,9 @@ mutation alertSetAssignees($projectPath: ID!, $assigneeUsernames: [String!]!, $i assignees { nodes { username + name + avatarUrl + webUrl } } notes { diff --git a/app/assets/javascripts/alert_management/graphql/mutations/alert_todo_mark_done.mutation.graphql b/app/assets/javascripts/alert_management/graphql/mutations/alert_todo_mark_done.mutation.graphql deleted file mode 100644 index 4d59b4d94cd..00000000000 --- a/app/assets/javascripts/alert_management/graphql/mutations/alert_todo_mark_done.mutation.graphql +++ /dev/null @@ -1,8 +0,0 @@ -mutation todoMarkDone($id: ID!) { - todoMarkDone(input: { id: $id }) { - errors - todo { - id - } - } -} |