diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-10-21 10:08:36 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-10-21 10:08:36 +0300 |
commit | 48aff82709769b098321c738f3444b9bdaa694c6 (patch) | |
tree | e00c7c43e2d9b603a5a6af576b1685e400410dee /app/assets/javascripts/alert_management | |
parent | 879f5329ee916a948223f8f43d77fba4da6cd028 (diff) |
Add latest changes from gitlab-org/gitlab@13-5-stable-eev13.5.0-rc42
Diffstat (limited to 'app/assets/javascripts/alert_management')
17 files changed, 384 insertions, 513 deletions
diff --git a/app/assets/javascripts/alert_management/components/alert_details.vue b/app/assets/javascripts/alert_management/components/alert_details.vue index c6605452616..f7a5d31b835 100644 --- a/app/assets/javascripts/alert_management/components/alert_details.vue +++ b/app/assets/javascripts/alert_management/components/alert_details.vue @@ -1,16 +1,17 @@ <script> -/* eslint-disable vue/no-v-html */ -import * as Sentry from '@sentry/browser'; import { GlAlert, GlBadge, GlIcon, + GlLink, GlLoadingIcon, GlSprintf, GlTabs, GlTab, GlButton, + GlSafeHtmlDirective, } from '@gitlab/ui'; +import * as Sentry from '~/sentry/wrapper'; import { s__ } from '~/locale'; import alertQuery from '../graphql/queries/details.query.graphql'; import sidebarStatusQuery from '../graphql/queries/sidebar_status.query.graphql'; @@ -28,6 +29,8 @@ 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'; +import AlertSummaryRow from './alert_summary_row.vue'; +import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; const containerEl = document.querySelector('.page-with-contextual-sidebar'); @@ -39,6 +42,9 @@ export default { reportedAt: s__('AlertManagement|Reported %{when}'), reportedAtWithTool: s__('AlertManagement|Reported %{when} by %{tool}'), }, + directives: { + SafeHtml: GlSafeHtmlDirective, + }, severityLabels: ALERTS_SEVERITY_LABELS, tabsConfig: [ { @@ -56,9 +62,11 @@ export default { ], components: { AlertDetailsTable, + AlertSummaryRow, GlBadge, GlAlert, GlIcon, + GlLink, GlLoadingIcon, GlSprintf, GlTab, @@ -69,20 +77,18 @@ export default { SystemNote, AlertMetrics, }, + mixins: [glFeatureFlagsMixin()], inject: { projectPath: { default: '', }, alertId: { - type: String, default: '', }, projectId: { - type: String, default: '', }, projectIssuesPath: { - type: String, default: '', }, }, @@ -143,6 +149,15 @@ export default { this.$router.replace({ name: 'tab', params: { tabId } }); }, }, + environmentName() { + return this.shouldDisplayEnvironment && this.alert?.environment?.name; + }, + environmentPath() { + return this.shouldDisplayEnvironment && this.alert?.environment?.path; + }, + shouldDisplayEnvironment() { + return this.glFeatures.exposeEnvironmentPathInAlertDetails; + }, }, mounted() { this.trackPageViews(); @@ -211,7 +226,7 @@ export default { <template> <div> <gl-alert v-if="showErrorMsg" variant="danger" @dismiss="dismissError"> - <p v-html="sidebarErrorMessage || $options.i18n.errorMsg"></p> + <p v-safe-html="sidebarErrorMessage || $options.i18n.errorMsg"></p> </gl-alert> <gl-alert v-if="createIncidentError" @@ -270,10 +285,9 @@ export default { variant="default" class="d-sm-none gl-absolute toggle-sidebar-mobile-button" type="button" + icon="chevron-double-lg-left" @click="toggleSidebar" - > - <i class="fa fa-angle-double-left"></i> - </gl-button> + /> </div> <div v-if="alert" @@ -283,54 +297,65 @@ export default { </div> <gl-tabs v-if="alert" v-model="currentTabIndex" data-testid="alertDetailsTabs"> <gl-tab :data-testid="$options.tabsConfig[0].id" :title="$options.tabsConfig[0].title"> - <div v-if="alert.severity" class="gl-mt-3 gl-mb-5 gl-display-flex"> - <div class="gl-font-weight-bold gl-w-13 gl-text-right gl-pr-3"> - {{ s__('AlertManagement|Severity') }}: - </div> - <div class="gl-pl-2" data-testid="severity"> - <span> - <gl-icon - class="gl-vertical-align-middle" - :size="12" - :name="`severity-${alert.severity.toLowerCase()}`" - :class="`icon-${alert.severity.toLowerCase()}`" - /> - </span> + <alert-summary-row v-if="alert.severity" :label="`${s__('AlertManagement|Severity')}:`"> + <span data-testid="severity"> + <gl-icon + class="gl-vertical-align-middle" + :size="12" + :name="`severity-${alert.severity.toLowerCase()}`" + :class="`icon-${alert.severity.toLowerCase()}`" + /> {{ $options.severityLabels[alert.severity] }} - </div> - </div> - <div v-if="alert.startedAt" class="gl-my-5 gl-display-flex"> - <div class="gl-font-weight-bold gl-w-13 gl-text-right gl-pr-3"> - {{ s__('AlertManagement|Start time') }}: - </div> - <div class="gl-pl-2"> - <time-ago-tooltip data-testid="startTimeItem" :time="alert.startedAt" /> - </div> - </div> - <div v-if="alert.eventCount" class="gl-my-5 gl-display-flex"> - <div class="gl-font-weight-bold gl-w-13 gl-text-right gl-pr-3"> - {{ s__('AlertManagement|Events') }}: - </div> - <div class="gl-pl-2" data-testid="eventCount">{{ alert.eventCount }}</div> - </div> - <div v-if="alert.monitoringTool" class="gl-my-5 gl-display-flex"> - <div class="gl-font-weight-bold gl-w-13 gl-text-right gl-pr-3"> - {{ s__('AlertManagement|Tool') }}: - </div> - <div class="gl-pl-2" data-testid="monitoringTool">{{ alert.monitoringTool }}</div> - </div> - <div v-if="alert.service" class="gl-my-5 gl-display-flex"> - <div class="bold gl-w-13 gl-text-right gl-pr-3"> - {{ s__('AlertManagement|Service') }}: - </div> - <div class="gl-pl-2" data-testid="service">{{ alert.service }}</div> - </div> - <div v-if="alert.runbook" class="gl-my-5 gl-display-flex"> - <div class="bold gl-w-13 gl-text-right gl-pr-3"> - {{ s__('AlertManagement|Runbook') }}: - </div> - <div class="gl-pl-2" data-testid="runbook">{{ alert.runbook }}</div> - </div> + </span> + </alert-summary-row> + <alert-summary-row + v-if="environmentName" + :label="`${s__('AlertManagement|Environment')}:`" + > + <gl-link + v-if="environmentPath" + class="gl-display-inline-block" + data-testid="environmentPath" + :href="environmentPath" + > + {{ environmentName }} + </gl-link> + <span v-else data-testid="environmentName">{{ environmentName }}</span> + </alert-summary-row> + <alert-summary-row + v-if="alert.startedAt" + :label="`${s__('AlertManagement|Start time')}:`" + > + <time-ago-tooltip data-testid="startTimeItem" :time="alert.startedAt" /> + </alert-summary-row> + <alert-summary-row + v-if="alert.eventCount" + :label="`${s__('AlertManagement|Events')}:`" + data-testid="eventCount" + > + {{ alert.eventCount }} + </alert-summary-row> + <alert-summary-row + v-if="alert.monitoringTool" + :label="`${s__('AlertManagement|Tool')}:`" + data-testid="monitoringTool" + > + {{ alert.monitoringTool }} + </alert-summary-row> + <alert-summary-row + v-if="alert.service" + :label="`${s__('AlertManagement|Service')}:`" + data-testid="service" + > + {{ alert.service }} + </alert-summary-row> + <alert-summary-row + v-if="alert.runbook" + :label="`${s__('AlertManagement|Runbook')}:`" + data-testid="runbook" + > + {{ alert.runbook }} + </alert-summary-row> <alert-details-table :alert="alert" :loading="loading" /> </gl-tab> <gl-tab :data-testid="$options.tabsConfig[1].id" :title="$options.tabsConfig[1].title"> diff --git a/app/assets/javascripts/alert_management/components/alert_management_empty_state.vue b/app/assets/javascripts/alert_management/components/alert_management_empty_state.vue index 68443166f40..c5ff2dc0d11 100644 --- a/app/assets/javascripts/alert_management/components/alert_management_empty_state.vue +++ b/app/assets/javascripts/alert_management/components/alert_management_empty_state.vue @@ -33,30 +33,13 @@ export default { query: alertsHelpUrlQuery, }, }, - props: { - enableAlertManagementPath: { - type: String, - required: true, - }, - userCanEnableAlertManagement: { - type: Boolean, - required: true, - }, - emptyAlertSvgPath: { - type: String, - required: true, - }, - opsgenieMvcEnabled: { - type: Boolean, - required: false, - default: false, - }, - opsgenieMvcTargetUrl: { - type: String, - required: false, - default: '', - }, - }, + inject: [ + 'enableAlertManagementPath', + 'userCanEnableAlertManagement', + 'emptyAlertSvgPath', + 'opsgenieMvcEnabled', + 'opsgenieMvcTargetUrl', + ], data() { return { alertsHelpUrl: '', diff --git a/app/assets/javascripts/alert_management/components/alert_management_list_wrapper.vue b/app/assets/javascripts/alert_management/components/alert_management_list_wrapper.vue index 094f33fed3b..5e9cdfb3fed 100644 --- a/app/assets/javascripts/alert_management/components/alert_management_list_wrapper.vue +++ b/app/assets/javascripts/alert_management/components/alert_management_list_wrapper.vue @@ -1,6 +1,4 @@ <script> -import Tracking from '~/tracking'; -import { trackAlertListViewsOptions } from '../constants'; import AlertManagementEmptyState from './alert_management_empty_state.vue'; import AlertManagementTable from './alert_management_table.vue'; @@ -9,67 +7,12 @@ export default { AlertManagementEmptyState, AlertManagementTable, }, - props: { - projectPath: { - type: String, - required: true, - }, - alertManagementEnabled: { - type: Boolean, - required: true, - }, - enableAlertManagementPath: { - type: String, - required: true, - }, - populatingAlertsHelpUrl: { - type: String, - required: true, - }, - userCanEnableAlertManagement: { - type: Boolean, - required: true, - }, - emptyAlertSvgPath: { - type: String, - required: true, - }, - opsgenieMvcEnabled: { - type: Boolean, - required: false, - default: false, - }, - opsgenieMvcTargetUrl: { - type: String, - required: false, - default: '', - }, - }, - mounted() { - this.trackPageViews(); - }, - methods: { - trackPageViews() { - const { category, action } = trackAlertListViewsOptions; - Tracking.event(category, action); - }, - }, + inject: ['alertManagementEnabled'], }; </script> <template> <div> - <alert-management-table - v-if="alertManagementEnabled" - :populating-alerts-help-url="populatingAlertsHelpUrl" - :project-path="projectPath" - /> - <alert-management-empty-state - v-else - :empty-alert-svg-path="emptyAlertSvgPath" - :enable-alert-management-path="enableAlertManagementPath" - :user-can-enable-alert-management="userCanEnableAlertManagement" - :opsgenie-mvc-enabled="opsgenieMvcEnabled" - :opsgenie-mvc-target-url="opsgenieMvcTargetUrl" - /> + <alert-management-table v-if="alertManagementEnabled" /> + <alert-management-empty-state v-else /> </div> </template> 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 0fd00fe90eb..f287b425826 100644 --- a/app/assets/javascripts/alert_management/components/alert_management_table.vue +++ b/app/assets/javascripts/alert_management/components/alert_management_table.vue @@ -1,58 +1,43 @@ <script> -/* eslint-disable vue/no-v-html */ import { + GlAlert, GlLoadingIcon, GlTable, - GlAlert, GlAvatarsInline, GlAvatarLink, GlAvatar, GlIcon, GlLink, - GlTabs, - GlTab, - GlBadge, - GlPagination, - GlSearchBoxByType, GlSprintf, GlTooltipDirective, } from '@gitlab/ui'; -import { debounce, trim } from 'lodash'; -import { __, s__ } from '~/locale'; -import { joinPaths, visitUrl } from '~/lib/utils/url_utility'; +import { s__, __ } from '~/locale'; import { fetchPolicies } from '~/lib/graphql'; +import { joinPaths, visitUrl } from '~/lib/utils/url_utility'; +import PaginatedTableWithSearchAndTabs from '~/vue_shared/components/paginated_table_with_search_and_tabs/paginated_table_with_search_and_tabs.vue'; +import { + tdClass, + thClass, + bodyTrClass, + initialPaginationState, +} from '~/vue_shared/components/paginated_table_with_search_and_tabs/constants'; import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; import { convertToSnakeCase } from '~/lib/utils/text_utility'; -import Tracking from '~/tracking'; import getAlerts from '../graphql/queries/get_alerts.query.graphql'; import getAlertsCountByStatus from '../graphql/queries/get_count_by_status.query.graphql'; import { ALERTS_STATUS_TABS, ALERTS_SEVERITY_LABELS, - DEFAULT_PAGE_SIZE, trackAlertListViewsOptions, - trackAlertStatusUpdateOptions, } from '../constants'; import AlertStatus from './alert_status.vue'; -const tdClass = - 'table-col gl-display-flex d-md-table-cell gl-align-items-center gl-white-space-nowrap'; -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, - prevPageCursor: '', - nextPageCursor: '', - firstPageSize: DEFAULT_PAGE_SIZE, - lastPageSize: null, -}; - const TWELVE_HOURS_IN_MS = 12 * 60 * 60 * 1000; export default { + trackAlertListViewsOptions, i18n: { noAlertsMsg: s__( 'AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list.', @@ -60,7 +45,6 @@ export default { errorMsg: s__( "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: [ @@ -94,7 +78,7 @@ export default { }, { key: 'issue', - label: s__('AlertManagement|Issue'), + label: s__('AlertManagement|Incident'), thClass: 'gl-w-12 gl-pointer-events-none', tdClass, }, @@ -115,36 +99,23 @@ export default { severityLabels: ALERTS_SEVERITY_LABELS, statusTabs: ALERTS_STATUS_TABS, components: { + GlAlert, GlLoadingIcon, GlTable, - GlAlert, GlAvatarsInline, GlAvatarLink, GlAvatar, TimeAgo, GlIcon, GlLink, - GlTabs, - GlTab, - GlBadge, - GlPagination, - GlSearchBoxByType, GlSprintf, AlertStatus, + PaginatedTableWithSearchAndTabs, }, directives: { GlTooltip: GlTooltipDirective, }, - props: { - projectPath: { - type: String, - required: true, - }, - populatingAlertsHelpUrl: { - type: String, - required: true, - }, - }, + inject: ['projectPath', 'textQuery', 'assigneeUsernameQuery', 'populatingAlertsHelpUrl'], apollo: { alerts: { fetchPolicy: fetchPolicies.CACHE_AND_NETWORK, @@ -152,6 +123,7 @@ export default { variables() { return { searchTerm: this.searchTerm, + assigneeUsername: this.assigneeUsername, projectPath: this.projectPath, statuses: this.statusFilter, sort: this.sort, @@ -182,14 +154,16 @@ export default { }; }, error() { - this.hasError = true; + this.errored = true; }, }, alertsCount: { + fetchPolicy: fetchPolicies.CACHE_AND_NETWORK, query: getAlertsCountByStatus, variables() { return { searchTerm: this.searchTerm, + assigneeUsername: this.assigneeUsername, projectPath: this.projectPath, }; }, @@ -200,83 +174,53 @@ export default { }, data() { return { - searchTerm: '', - hasError: false, - errorMessage: '', - isAlertDismissed: false, + errored: false, + serverErrorMessage: '', + isErrorAlertDismissed: false, sort: 'STARTED_AT_DESC', statusFilter: [], filteredByStatus: '', - pagination: initialPaginationState, + alerts: {}, + alertsCount: {}, sortBy: 'startedAt', sortDesc: true, sortDirection: 'desc', + searchTerm: this.textQuery, + assigneeUsername: this.assigneeUsernameQuery, + pagination: initialPaginationState, }; }, computed: { + showErrorMsg() { + return this.errored && !this.isErrorAlertDismissed; + }, showNoAlertsMsg() { return ( - !this.hasError && + !this.errored && !this.loading && this.alertsCount?.all === 0 && !this.searchTerm && - !this.isAlertDismissed + !this.assigneeUsername && + !this.isErrorAlertDismissed ); }, loading() { return this.$apollo.queries.alerts.loading; }, - hasAlerts() { - return this.alerts?.list?.length; - }, - showPaginationControls() { - return Boolean(this.prevPage || this.nextPage); - }, - alertsForCurrentTab() { - return this.alertsCount ? this.alertsCount[this.filteredByStatus.toLowerCase()] : 0; - }, - prevPage() { - return Math.max(this.pagination.currentPage - 1, 0); - }, - nextPage() { - const nextPage = this.pagination.currentPage + 1; - return nextPage > Math.ceil(this.alertsForCurrentTab / DEFAULT_PAGE_SIZE) ? null : nextPage; + isEmpty() { + return !this.alerts?.list?.length; }, }, - mounted() { - this.trackPageViews(); - }, methods: { - filterAlertsByStatus(tabIndex) { - this.resetPagination(); - const { filters, status } = this.$options.statusTabs[tabIndex]; - this.statusFilter = filters; - this.filteredByStatus = status; - }, fetchSortedData({ sortBy, sortDesc }) { const sortingDirection = sortDesc ? 'DESC' : 'ASC'; const sortingColumn = convertToSnakeCase(sortBy).toUpperCase(); - this.resetPagination(); + this.pagination = initialPaginationState; this.sort = `${sortingColumn}_${sortingDirection}`; }, - onInputChange: debounce(function debounceSearch(input) { - const trimmedInput = trim(input); - if (trimmedInput !== this.searchTerm) { - this.resetPagination(); - this.searchTerm = trimmedInput; - } - }, 500), - navigateToAlertDetails({ iid }) { - return visitUrl(joinPaths(window.location.pathname, iid, 'details')); - }, - trackPageViews() { - const { category, action } = trackAlertListViewsOptions; - Tracking.event(category, action); - }, - trackStatusUpdate(status) { - const { category, action, label } = trackAlertStatusUpdateOptions; - Tracking.event(category, action, { label, property: status }); + navigateToAlertDetails({ iid }, index, { metaKey }) { + return visitUrl(joinPaths(window.location.pathname, iid, 'details'), metaKey); }, hasAssignees(assignees) { return Boolean(assignees.nodes?.length); @@ -284,204 +228,180 @@ export default { getIssueLink(item) { return joinPaths('/', this.projectPath, '-', 'issues', item.issueIid); }, - handlePageChange(page) { - const { startCursor, endCursor } = this.alerts.pageInfo; - - if (page > this.pagination.currentPage) { - this.pagination = { - ...initialPaginationState, - nextPageCursor: endCursor, - currentPage: page, - }; - } else { - this.pagination = { - lastPageSize: DEFAULT_PAGE_SIZE, - firstPageSize: null, - prevPageCursor: startCursor, - nextPageCursor: '', - currentPage: page, - }; - } - }, - resetPagination() { - this.pagination = initialPaginationState; - }, tbodyTrClass(item) { return { - [bodyTrClass]: !this.loading && this.hasAlerts, + [bodyTrClass]: !this.loading && !this.isEmpty, 'new-alert': item?.isNew, }; }, handleAlertError(errorMessage) { - this.hasError = true; - this.errorMessage = errorMessage; + this.errored = true; + this.serverErrorMessage = errorMessage; }, - dismissError() { - this.hasError = false; - this.errorMessage = ''; + handleStatusUpdate() { + this.$apollo.queries.alerts.refetch(); + this.$apollo.queries.alertsCount.refetch(); + }, + pageChanged(pagination) { + this.pagination = pagination; + }, + statusChanged({ filters, status }) { + this.statusFilter = filters; + this.filteredByStatus = status; + }, + filtersChanged({ searchTerm, assigneeUsername }) { + this.searchTerm = searchTerm; + this.assigneeUsername = assigneeUsername; + }, + errorAlertDismissed() { + this.errored = false; + this.serverErrorMessage = ''; + this.isErrorAlertDismissed = true; }, }, }; </script> <template> <div> - <div class="incident-management-list"> - <gl-alert v-if="showNoAlertsMsg" @dismiss="isAlertDismissed = true"> - <gl-sprintf :message="$options.i18n.noAlertsMsg"> - <template #link="{ content }"> - <gl-link - class="gl-display-inline-block" - :href="populatingAlertsHelpUrl" - target="_blank" - > - {{ content }} - </gl-link> - </template> - </gl-sprintf> - </gl-alert> - <gl-alert v-if="hasError" variant="danger" data-testid="alert-error" @dismiss="dismissError"> - <p v-html="errorMessage || $options.i18n.errorMsg"></p> - </gl-alert> - - <gl-tabs - content-class="gl-p-0 gl-border-b-solid gl-border-b-1 gl-border-gray-100" - @input="filterAlertsByStatus" - > - <gl-tab v-for="tab in $options.statusTabs" :key="tab.status"> - <template slot="title"> - <span>{{ tab.title }}</span> - <gl-badge v-if="alertsCount" pill size="sm" class="gl-tab-counter-badge"> - {{ alertsCount[tab.status.toLowerCase()] }} - </gl-badge> - </template> - </gl-tab> - </gl-tabs> + <gl-alert v-if="showNoAlertsMsg" @dismiss="errorAlertDismissed"> + <gl-sprintf :message="$options.i18n.noAlertsMsg"> + <template #link="{ content }"> + <gl-link class="gl-display-inline-block" :href="populatingAlertsHelpUrl" target="_blank"> + {{ content }} + </gl-link> + </template> + </gl-sprintf> + </gl-alert> - <div class="gl-bg-gray-10 gl-p-5 gl-border-b-solid gl-border-b-1 gl-border-gray-100"> - <gl-search-box-by-type - class="gl-bg-white" - :placeholder="$options.i18n.searchPlaceholder" - @input="onInputChange" - /> - </div> + <paginated-table-with-search-and-tabs + :show-error-msg="showErrorMsg" + :i18n="$options.i18n" + :items="alerts.list || []" + :page-info="alerts.pageInfo" + :items-count="alertsCount" + :status-tabs="$options.statusTabs" + :track-views-options="$options.trackAlertListViewsOptions" + :server-error-message="serverErrorMessage" + :filter-search-tokens="['assignee_username']" + filter-search-key="alerts" + @page-changed="pageChanged" + @tabs-changed="statusChanged" + @filters-changed="filtersChanged" + @error-alert-dismissed="errorAlertDismissed" + > + <template #header-actions></template> - <h4 class="d-block d-md-none my-3"> + <template #title> {{ s__('AlertManagement|Alerts') }} - </h4> - <gl-table - class="alert-management-table" - :items="alerts ? alerts.list : []" - :fields="$options.fields" - :show-empty="true" - :busy="loading" - stacked="md" - :tbody-tr-class="tbodyTrClass" - :no-local-sorting="true" - :sort-direction="sortDirection" - :sort-desc.sync="sortDesc" - :sort-by.sync="sortBy" - sort-icon-left - fixed - @row-clicked="navigateToAlertDetails" - @sort-changed="fetchSortedData" - > - <template #cell(severity)="{ item }"> - <div - class="d-inline-flex align-items-center justify-content-between" - data-testid="severityField" - > - <gl-icon - class="mr-2" - :size="12" - :name="`severity-${item.severity.toLowerCase()}`" - :class="`icon-${item.severity.toLowerCase()}`" - /> - {{ $options.severityLabels[item.severity] }} - </div> - </template> + </template> - <template #cell(startedAt)="{ item }"> - <time-ago v-if="item.startedAt" :time="item.startedAt" /> - </template> + <template #table> + <gl-table + class="alert-management-table" + :items="alerts ? alerts.list : []" + :fields="$options.fields" + :show-empty="true" + :busy="loading" + stacked="md" + :tbody-tr-class="tbodyTrClass" + :no-local-sorting="true" + :sort-direction="sortDirection" + :sort-desc.sync="sortDesc" + :sort-by.sync="sortBy" + sort-icon-left + fixed + @row-clicked="navigateToAlertDetails" + @sort-changed="fetchSortedData" + > + <template #cell(severity)="{ item }"> + <div + class="d-inline-flex align-items-center justify-content-between" + data-testid="severityField" + > + <gl-icon + class="mr-2" + :size="12" + :name="`severity-${item.severity.toLowerCase()}`" + :class="`icon-${item.severity.toLowerCase()}`" + /> + {{ $options.severityLabels[item.severity] }} + </div> + </template> - <template #cell(eventCount)="{ item }"> - {{ item.eventCount }} - </template> + <template #cell(startedAt)="{ item }"> + <time-ago v-if="item.startedAt" :time="item.startedAt" /> + </template> - <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(eventCount)="{ item }"> + {{ item.eventCount }} + </template> - <template #cell(issue)="{ item }"> - <gl-link v-if="item.issueIid" data-testid="issueField" :href="getIssueLink(item)"> - #{{ item.issueIid }} - </gl-link> - <div v-else data-testid="issueField">{{ s__('AlertManagement|None') }}</div> - </template> + <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(assignees)="{ item }"> - <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> + <template #cell(issue)="{ item }"> + <gl-link v-if="item.issueIid" data-testid="issueField" :href="getIssueLink(item)"> + #{{ item.issueIid }} + </gl-link> + <div v-else data-testid="issueField">{{ s__('AlertManagement|None') }}</div> + </template> - <template #cell(status)="{ item }"> - <alert-status - :alert="item" - :project-path="projectPath" - :is-sidebar="false" - @alert-error="handleAlertError" - /> - </template> + <template #cell(assignees)="{ item }"> + <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> - <template #empty> - {{ s__('AlertManagement|No alerts to display.') }} - </template> + <template #cell(status)="{ item }"> + <alert-status + :alert="item" + :project-path="projectPath" + :is-sidebar="false" + @alert-error="handleAlertError" + @hide-dropdown="handleStatusUpdate" + /> + </template> - <template #table-busy> - <gl-loading-icon size="lg" color="dark" class="mt-3" /> - </template> - </gl-table> + <template #empty> + {{ s__('AlertManagement|No alerts to display.') }} + </template> - <gl-pagination - v-if="showPaginationControls" - :value="pagination.currentPage" - :prev-page="prevPage" - :next-page="nextPage" - align="center" - class="gl-pagination gl-mt-3" - @input="handlePageChange" - /> - </div> + <template #table-busy> + <gl-loading-icon size="lg" color="dark" class="mt-3" /> + </template> + </gl-table> + </template> + </paginated-table-with-search-and-tabs> </div> </template> diff --git a/app/assets/javascripts/alert_management/components/alert_metrics.vue b/app/assets/javascripts/alert_management/components/alert_metrics.vue index c5b40edc672..8a6490ecd5c 100644 --- a/app/assets/javascripts/alert_management/components/alert_metrics.vue +++ b/app/assets/javascripts/alert_management/components/alert_metrics.vue @@ -1,7 +1,7 @@ <script> import Vue from 'vue'; import Vuex from 'vuex'; -import * as Sentry from '@sentry/browser'; +import * as Sentry from '~/sentry/wrapper'; Vue.use(Vuex); diff --git a/app/assets/javascripts/alert_management/components/alert_sidebar.vue b/app/assets/javascripts/alert_management/components/alert_sidebar.vue index 64e4089c85a..41d77716592 100644 --- a/app/assets/javascripts/alert_management/components/alert_sidebar.vue +++ b/app/assets/javascripts/alert_management/components/alert_sidebar.vue @@ -18,7 +18,6 @@ export default { default: '', }, projectId: { - type: String, default: '', }, }, diff --git a/app/assets/javascripts/alert_management/components/alert_status.vue b/app/assets/javascripts/alert_management/components/alert_status.vue index ff71b348cc9..3083a85cbd9 100644 --- a/app/assets/javascripts/alert_management/components/alert_status.vue +++ b/app/assets/javascripts/alert_management/components/alert_status.vue @@ -1,9 +1,9 @@ <script> -import { GlDeprecatedDropdown, GlDeprecatedDropdownItem, GlButton } from '@gitlab/ui'; +import { GlDropdown, GlDropdownItem } from '@gitlab/ui'; import { s__ } from '~/locale'; import Tracking from '~/tracking'; import { trackAlertStatusUpdateOptions } from '../constants'; -import updateAlertStatus from '../graphql/mutations/update_alert_status.mutation.graphql'; +import updateAlertStatusMutation from '../graphql/mutations/update_alert_status.mutation.graphql'; export default { i18n: { @@ -18,9 +18,8 @@ export default { RESOLVED: s__('AlertManagement|Resolved'), }, components: { - GlDeprecatedDropdown, - GlDeprecatedDropdownItem, - GlButton, + GlDropdown, + GlDropdownItem, }, props: { projectPath: { @@ -51,7 +50,7 @@ export default { this.$emit('handle-updating', true); this.$apollo .mutate({ - mutation: updateAlertStatus, + mutation: updateAlertStatusMutation, variables: { iid: this.alert.iid, status: status.toUpperCase(), @@ -60,8 +59,6 @@ export default { }) .then(resp => { this.trackStatusUpdate(status); - this.$emit('hide-dropdown'); - const errors = resp.data?.updateAlertStatus?.errors || []; if (errors[0]) { @@ -70,6 +67,8 @@ export default { `${this.$options.i18n.UPDATE_ALERT_STATUS_ERROR} ${errors[0]}`, ); } + + this.$emit('hide-dropdown'); }) .catch(() => { this.$emit( @@ -91,39 +90,30 @@ export default { <template> <div class="dropdown dropdown-menu-selectable" :class="dropdownClass"> - <gl-deprecated-dropdown + <gl-dropdown ref="dropdown" right :text="$options.statuses[alert.status]" class="w-100" toggle-class="dropdown-menu-toggle" - variant="outline-default" @keydown.esc.native="$emit('hide-dropdown')" @hide="$emit('hide-dropdown')" > - <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 gl-ml-auto gl-text-black-normal!" - icon="close" - @click="$emit('hide-dropdown')" - /> - </div> + <p v-if="isSidebar" class="gl-new-dropdown-header-top" data-testid="dropdown-header"> + {{ s__('AlertManagement|Assign status') }} + </p> <div class="dropdown-content dropdown-body"> - <gl-deprecated-dropdown-item + <gl-dropdown-item v-for="(label, field) in $options.statuses" :key="field" data-testid="statusDropdownItem" - class="gl-vertical-align-middle" :active="label.toUpperCase() === alert.status" :active-class="'is-active'" @click="updateAlertStatus(label)" > {{ label }} - </gl-deprecated-dropdown-item> + </gl-dropdown-item> </div> - </gl-deprecated-dropdown> + </gl-dropdown> </div> </template> diff --git a/app/assets/javascripts/alert_management/components/alert_summary_row.vue b/app/assets/javascripts/alert_management/components/alert_summary_row.vue new file mode 100644 index 00000000000..13835b7e2fa --- /dev/null +++ b/app/assets/javascripts/alert_management/components/alert_summary_row.vue @@ -0,0 +1,18 @@ +<script> +export default { + props: { + label: { + type: String, + required: true, + }, + }, +}; +</script> +<template> + <div class="gl-my-5 gl-display-flex"> + <div class="gl-font-weight-bold gl-w-13 gl-text-right gl-pr-3">{{ label }}</div> + <div class="gl-pl-2"> + <slot></slot> + </div> + </div> +</template> diff --git a/app/assets/javascripts/alert_management/components/sidebar/sidebar_assignee.vue b/app/assets/javascripts/alert_management/components/sidebar/sidebar_assignee.vue index 0a1478ef5fe..df07038151e 100644 --- a/app/assets/javascripts/alert_management/components/sidebar/sidebar_assignee.vue +++ b/app/assets/javascripts/alert_management/components/sidebar/sidebar_assignee.vue @@ -1,9 +1,9 @@ <script> -import { GlDeprecatedDropdownItem } from '@gitlab/ui'; +import { GlDropdownItem } from '@gitlab/ui'; export default { components: { - GlDeprecatedDropdownItem, + GlDropdownItem, }, props: { user: { @@ -24,7 +24,7 @@ export default { </script> <template> - <gl-deprecated-dropdown-item + <gl-dropdown-item :key="user.username" data-testid="assigneeDropdownItem" class="assignee-dropdown-item gl-vertical-align-middle" @@ -47,5 +47,5 @@ export default { </strong> <span class="dropdown-menu-user-username"> {{ user.username }}</span> </span> - </gl-deprecated-dropdown-item> + </gl-dropdown-item> </template> 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 0f354e85e96..5e4fd56738b 100644 --- a/app/assets/javascripts/alert_management/components/sidebar/sidebar_assignees.vue +++ b/app/assets/javascripts/alert_management/components/sidebar/sidebar_assignees.vue @@ -1,10 +1,11 @@ <script> import { GlIcon, - GlDeprecatedDropdown, - GlDeprecatedDropdownDivider, - GlDeprecatedDropdownHeader, - GlDeprecatedDropdownItem, + GlDropdown, + GlDropdownDivider, + GlDropdownSectionHeader, + GlDropdownItem, + GlSearchBoxByType, GlLoadingIcon, GlTooltip, GlButton, @@ -33,10 +34,11 @@ export default { }, components: { GlIcon, - GlDeprecatedDropdown, - GlDeprecatedDropdownItem, - GlDeprecatedDropdownDivider, - GlDeprecatedDropdownHeader, + GlDropdown, + GlDropdownItem, + GlDropdownDivider, + GlDropdownSectionHeader, + GlSearchBoxByType, GlLoadingIcon, GlTooltip, GlButton, @@ -216,48 +218,32 @@ export default { </p> <div class="dropdown dropdown-menu-selectable" :class="dropdownClass"> - <gl-deprecated-dropdown + <gl-dropdown ref="dropdown" :text="userName" class="w-100" toggle-class="dropdown-menu-toggle" - variant="outline-default" @keydown.esc.native="hideDropdown" @hide="hideDropdown" > - <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 gl-ml-auto gl-text-black-normal!" - icon="close" - @click="hideDropdown" - /> - </div> - <div class="dropdown-input"> - <input - v-model.trim="search" - class="dropdown-input-field" - type="search" - :placeholder="__('Search users')" - /> - <gl-icon name="search" class="dropdown-input-search ic-search" data-hidden="true" /> - </div> + <p class="gl-new-dropdown-header-top"> + {{ __('Assign To') }} + </p> + <gl-search-box-by-type v-model.trim="search" :placeholder="__('Search users')" /> <div class="dropdown-content dropdown-body"> <template v-if="userListValid"> - <gl-deprecated-dropdown-item + <gl-dropdown-item :active="!userName" active-class="is-active" @click="updateAlertAssignees('')" > {{ __('Unassigned') }} - </gl-deprecated-dropdown-item> - <gl-deprecated-dropdown-divider /> + </gl-dropdown-item> + <gl-dropdown-divider /> - <gl-deprecated-dropdown-header class="mt-0"> + <gl-dropdown-section-header> {{ __('Assignee') }} - </gl-deprecated-dropdown-header> + </gl-dropdown-section-header> <sidebar-assignee v-for="user in sortedUsers" :key="user.username" @@ -266,12 +252,12 @@ export default { @update-alert-assignees="updateAlertAssignees" /> </template> - <gl-deprecated-dropdown-item v-else-if="userListEmpty"> + <p v-else-if="userListEmpty" class="mx-3 my-2"> {{ __('No Matching Results') }} - </gl-deprecated-dropdown-item> + </p> <gl-loading-icon v-else /> </div> - </gl-deprecated-dropdown> + </gl-dropdown> </div> <gl-loading-icon v-if="isUpdating" :inline="true" /> 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 0b206ce42f4..3705e36a579 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,11 +1,12 @@ <script> /* eslint-disable vue/no-v-html */ +import { GlIcon } from '@gitlab/ui'; import NoteHeader from '~/notes/components/note_header.vue'; -import { spriteIcon } from '~/lib/utils/common_utils'; export default { components: { NoteHeader, + GlIcon, }, props: { note: { @@ -24,23 +25,23 @@ export default { } = this.note; return { ...author, id: id?.split('/').pop() }; }, - iconHtml() { - return spriteIcon(this.note?.systemNoteIconName); - }, }, }; </script> <template> - <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"> - <div class="note-header"> - <note-header :author="noteAuthor" :created-at="note.createdAt" :note-id="note.id"> - <span v-html="note.bodyHtml"></span> - </note-header> - </div> + <li :id="noteAnchorId" class="timeline-entry note system-note note-wrapper gl-p-0!"> + <div class="gl-display-inline-flex gl-align-items-center"> + <div + class="gl-display-inline gl-bg-white gl-text-gray-200 gl-border-gray-100 gl-border-1 gl-border-solid gl-rounded-full gl-box-sizing-content-box gl-p-3 gl-mt-n2 gl-mr-6" + > + <gl-icon :name="note.systemNoteIconName" /> + </div> + + <div class="note-header"> + <note-header :author="noteAuthor" :created-at="note.createdAt" :note-id="note.id"> + <span v-html="note.bodyHtml"></span> + </note-header> </div> </div> </li> diff --git a/app/assets/javascripts/alert_management/constants.js b/app/assets/javascripts/alert_management/constants.js index 73cb5ecdf98..b79a64646eb 100644 --- a/app/assets/javascripts/alert_management/constants.js +++ b/app/assets/javascripts/alert_management/constants.js @@ -63,5 +63,3 @@ export const trackAlertStatusUpdateOptions = { action: 'update_alert_status', label: 'Status', }; - -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 c2020dfcbe3..cbbdecae390 100644 --- a/app/assets/javascripts/alert_management/details.js +++ b/app/assets/javascripts/alert_management/details.js @@ -1,11 +1,11 @@ +import { defaultDataIdFromObject } from 'apollo-cache-inmemory'; +import produce from 'immer'; 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'; import AlertDetails from './components/alert_details.vue'; import sidebarStatusQuery from './graphql/queries/sidebar_status.query.graphql'; +import createRouter from './router'; Vue.use(VueApollo); diff --git a/app/assets/javascripts/alert_management/graphql/fragments/detail_item.fragment.graphql b/app/assets/javascripts/alert_management/graphql/fragments/detail_item.fragment.graphql index 0712ff12c23..406dfe97ce0 100644 --- a/app/assets/javascripts/alert_management/graphql/fragments/detail_item.fragment.graphql +++ b/app/assets/javascripts/alert_management/graphql/fragments/detail_item.fragment.graphql @@ -10,6 +10,11 @@ fragment AlertDetailItem on AlertManagementAlert { description updatedAt endedAt + hosts + environment { + name + path + } details runbook todos { diff --git a/app/assets/javascripts/alert_management/graphql/queries/get_alerts.query.graphql b/app/assets/javascripts/alert_management/graphql/queries/get_alerts.query.graphql index 8ac00bbc6b5..bc7e51a2e90 100644 --- a/app/assets/javascripts/alert_management/graphql/queries/get_alerts.query.graphql +++ b/app/assets/javascripts/alert_management/graphql/queries/get_alerts.query.graphql @@ -1,7 +1,6 @@ #import "../fragments/list_item.fragment.graphql" query getAlerts( - $searchTerm: String $projectPath: ID! $statuses: [AlertManagementStatus!] $sort: AlertManagementAlertSort @@ -9,10 +8,13 @@ query getAlerts( $lastPageSize: Int $prevPageCursor: String = "" $nextPageCursor: String = "" + $searchTerm: String = "" + $assigneeUsername: String = "" ) { project(fullPath: $projectPath) { alertManagementAlerts( search: $searchTerm + assigneeUsername: $assigneeUsername statuses: $statuses sort: $sort first: $firstPageSize diff --git a/app/assets/javascripts/alert_management/graphql/queries/get_count_by_status.query.graphql b/app/assets/javascripts/alert_management/graphql/queries/get_count_by_status.query.graphql index 5a6faea5cd8..40ec4c56171 100644 --- a/app/assets/javascripts/alert_management/graphql/queries/get_count_by_status.query.graphql +++ b/app/assets/javascripts/alert_management/graphql/queries/get_count_by_status.query.graphql @@ -1,6 +1,6 @@ -query getAlertsCount($searchTerm: String, $projectPath: ID!) { +query getAlertsCount($searchTerm: String, $projectPath: ID!, $assigneeUsername: String = "") { project(fullPath: $projectPath) { - alertManagementAlertStatusCounts(search: $searchTerm) { + alertManagementAlertStatusCounts(search: $searchTerm, assigneeUsername: $assigneeUsername) { all open acknowledged diff --git a/app/assets/javascripts/alert_management/list.js b/app/assets/javascripts/alert_management/list.js index e180ab5f7e3..e34450204fb 100644 --- a/app/assets/javascripts/alert_management/list.js +++ b/app/assets/javascripts/alert_management/list.js @@ -18,12 +18,12 @@ export default () => { populatingAlertsHelpUrl, alertsHelpUrl, opsgenieMvcTargetUrl, + textQuery, + assigneeUsernameQuery, + alertManagementEnabled, + userCanEnableAlertManagement, + opsgenieMvcEnabled, } = domEl.dataset; - let { alertManagementEnabled, userCanEnableAlertManagement, opsgenieMvcEnabled } = domEl.dataset; - - alertManagementEnabled = parseBoolean(alertManagementEnabled); - userCanEnableAlertManagement = parseBoolean(userCanEnableAlertManagement); - opsgenieMvcEnabled = parseBoolean(opsgenieMvcEnabled); const apolloProvider = new VueApollo({ defaultClient: createDefaultClient( @@ -50,23 +50,24 @@ export default () => { return new Vue({ el: selector, + provide: { + projectPath, + textQuery, + assigneeUsernameQuery, + enableAlertManagementPath, + populatingAlertsHelpUrl, + emptyAlertSvgPath, + opsgenieMvcTargetUrl, + alertManagementEnabled: parseBoolean(alertManagementEnabled), + userCanEnableAlertManagement: parseBoolean(userCanEnableAlertManagement), + opsgenieMvcEnabled: parseBoolean(opsgenieMvcEnabled), + }, apolloProvider, components: { AlertManagementList, }, render(createElement) { - return createElement('alert-management-list', { - props: { - projectPath, - enableAlertManagementPath, - populatingAlertsHelpUrl, - emptyAlertSvgPath, - alertManagementEnabled, - userCanEnableAlertManagement, - opsgenieMvcTargetUrl, - opsgenieMvcEnabled, - }, - }); + return createElement('alert-management-list'); }, }); }; |