Commit 7c29ff14 authored by Alexander Turinske's avatar Alexander Turinske

Use assignee sidebar widget in the alert drawer

- update assignee sidebar widget to fit the alert case
  not having a participant query
- update assignee to fit alert mutation not being
  standard
- update alerts to work with widget
- create alert assignees query for use with widget
- alias for response shape to match other
  queries
- alias mutation properties
- update other sidebar component to continue working
- make iid the alert unique identifier
- add tests
- Refactor alert details graphql queries
- removed assignees from the alert.fragment
- created a new alert details query without assignees
  and use it for the threat monitoring alert drawer
- renamed the old alert details query and added
  assignees with the user fragment to it for use just
  in the alert details page, using the user
- added assignees to get alert fragment and use the
  user fragment

Changelog: added
EE: true
parent eefe06ee
...@@ -11,12 +11,4 @@ fragment AlertListItem on AlertManagementAlert { ...@@ -11,12 +11,4 @@ fragment AlertListItem on AlertManagementAlert {
title title
webUrl webUrl
} }
assignees {
nodes {
name
username
avatarUrl
webUrl
}
}
} }
#import "~/graphql_shared/fragments/alert.fragment.graphql" #import "~/graphql_shared/fragments/alert.fragment.graphql"
#import "~/graphql_shared/fragments/user.fragment.graphql"
query getAlerts( query getAlerts(
$projectPath: ID! $projectPath: ID!
...@@ -26,6 +27,11 @@ query getAlerts( ...@@ -26,6 +27,11 @@ query getAlerts(
) { ) {
nodes { nodes {
...AlertListItem ...AlertListItem
assignees {
nodes {
...User
}
}
} }
pageInfo { pageInfo {
hasNextPage hasNextPage
......
...@@ -16,6 +16,7 @@ export const IssuableType = { ...@@ -16,6 +16,7 @@ export const IssuableType = {
Issue: 'issue', Issue: 'issue',
Epic: 'epic', Epic: 'epic',
MergeRequest: 'merge_request', MergeRequest: 'merge_request',
Alert: 'alert',
}; };
export const IssueStateEvent = { export const IssueStateEvent = {
......
...@@ -61,7 +61,7 @@ export default { ...@@ -61,7 +61,7 @@ export default {
required: false, required: false,
default: IssuableType.Issue, default: IssuableType.Issue,
validator(value) { validator(value) {
return [IssuableType.Issue, IssuableType.MergeRequest].includes(value); return [IssuableType.Issue, IssuableType.MergeRequest, IssuableType.Alert].includes(value);
}, },
}, },
issuableId: { issuableId: {
......
...@@ -19,6 +19,8 @@ import updateIssueConfidentialMutation from '~/sidebar/queries/update_issue_conf ...@@ -19,6 +19,8 @@ import updateIssueConfidentialMutation from '~/sidebar/queries/update_issue_conf
import updateIssueDueDateMutation from '~/sidebar/queries/update_issue_due_date.mutation.graphql'; import updateIssueDueDateMutation from '~/sidebar/queries/update_issue_due_date.mutation.graphql';
import updateIssueSubscriptionMutation from '~/sidebar/queries/update_issue_subscription.mutation.graphql'; import updateIssueSubscriptionMutation from '~/sidebar/queries/update_issue_subscription.mutation.graphql';
import updateMergeRequestSubscriptionMutation from '~/sidebar/queries/update_merge_request_subscription.mutation.graphql'; import updateMergeRequestSubscriptionMutation from '~/sidebar/queries/update_merge_request_subscription.mutation.graphql';
import updateAlertAssigneesMutation from '~/vue_shared/alert_details/graphql/mutations/alert_set_assignees.mutation.graphql';
import getAlertAssignees from '~/vue_shared/components/sidebar/queries/get_alert_assignees.query.graphql';
import getIssueAssignees from '~/vue_shared/components/sidebar/queries/get_issue_assignees.query.graphql'; import getIssueAssignees from '~/vue_shared/components/sidebar/queries/get_issue_assignees.query.graphql';
import issueParticipantsQuery from '~/vue_shared/components/sidebar/queries/get_issue_participants.query.graphql'; import issueParticipantsQuery from '~/vue_shared/components/sidebar/queries/get_issue_participants.query.graphql';
import getIssueTimelogsQuery from '~/vue_shared/components/sidebar/queries/get_issue_timelogs.query.graphql'; import getIssueTimelogsQuery from '~/vue_shared/components/sidebar/queries/get_issue_timelogs.query.graphql';
...@@ -40,6 +42,10 @@ export const assigneesQueries = { ...@@ -40,6 +42,10 @@ export const assigneesQueries = {
query: getMergeRequestAssignees, query: getMergeRequestAssignees,
mutation: updateMergeRequestAssigneesMutation, mutation: updateMergeRequestAssigneesMutation,
}, },
[IssuableType.Alert]: {
query: getAlertAssignees,
mutation: updateAlertAssigneesMutation,
},
}; };
export const participantsQueries = { export const participantsQueries = {
...@@ -52,6 +58,10 @@ export const participantsQueries = { ...@@ -52,6 +58,10 @@ export const participantsQueries = {
[IssuableType.Epic]: { [IssuableType.Epic]: {
query: epicParticipantsQuery, query: epicParticipantsQuery,
}, },
[IssuableType.Alert]: {
query: '',
skipQuery: true,
},
}; };
export const confidentialityQueries = { export const confidentialityQueries = {
......
...@@ -24,7 +24,7 @@ import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; ...@@ -24,7 +24,7 @@ import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import { PAGE_CONFIG, SEVERITY_LEVELS } from '../constants'; import { PAGE_CONFIG, SEVERITY_LEVELS } from '../constants';
import createIssueMutation from '../graphql/mutations/alert_issue_create.mutation.graphql'; import createIssueMutation from '../graphql/mutations/alert_issue_create.mutation.graphql';
import toggleSidebarStatusMutation from '../graphql/mutations/alert_sidebar_status.mutation.graphql'; import toggleSidebarStatusMutation from '../graphql/mutations/alert_sidebar_status.mutation.graphql';
import alertQuery from '../graphql/queries/alert_details.query.graphql'; import alertQuery from '../graphql/queries/alert_sidebar_details.query.graphql';
import sidebarStatusQuery from '../graphql/queries/alert_sidebar_status.query.graphql'; import sidebarStatusQuery from '../graphql/queries/alert_sidebar_status.query.graphql';
import AlertMetrics from './alert_metrics.vue'; import AlertMetrics from './alert_metrics.vue';
import AlertSidebar from './alert_sidebar.vue'; import AlertSidebar from './alert_sidebar.vue';
......
...@@ -167,10 +167,10 @@ export default { ...@@ -167,10 +167,10 @@ export default {
variables: { variables: {
iid: this.alert.iid, iid: this.alert.iid,
assigneeUsernames: [this.isActive(assignees) ? '' : assignees], assigneeUsernames: [this.isActive(assignees) ? '' : assignees],
projectPath: this.projectPath, fullPath: this.projectPath,
}, },
}) })
.then(({ data: { alertSetAssignees: { errors } = [] } = {} } = {}) => { .then(({ data: { issuableSetAssignees: { errors } = [] } = {} } = {}) => {
this.hideDropdown(); this.hideDropdown();
if (errors[0]) { if (errors[0]) {
......
...@@ -4,7 +4,7 @@ import todoMarkDoneMutation from '~/graphql_shared/mutations/todo_mark_done.muta ...@@ -4,7 +4,7 @@ import todoMarkDoneMutation from '~/graphql_shared/mutations/todo_mark_done.muta
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import Todo from '~/sidebar/components/todo_toggle/todo.vue'; import Todo from '~/sidebar/components/todo_toggle/todo.vue';
import createAlertTodoMutation from '../../graphql/mutations/alert_todo_create.mutation.graphql'; import createAlertTodoMutation from '../../graphql/mutations/alert_todo_create.mutation.graphql';
import alertQuery from '../../graphql/queries/alert_details.query.graphql'; import alertQuery from '../../graphql/queries/alert_sidebar_details.query.graphql';
export default { export default {
i18n: { i18n: {
......
#import "~/graphql_shared/fragments/alert_note.fragment.graphql" #import "~/graphql_shared/fragments/alert_note.fragment.graphql"
#import "~/graphql_shared/fragments/user.fragment.graphql"
#import "~/graphql_shared/fragments/user_availability.fragment.graphql"
mutation alertSetAssignees($projectPath: ID!, $assigneeUsernames: [String!]!, $iid: String!) { mutation alertSetAssignees($fullPath: ID!, $assigneeUsernames: [String!]!, $iid: String!) {
alertSetAssignees( issuableSetAssignees: alertSetAssignees(
input: { iid: $iid, assigneeUsernames: $assigneeUsernames, projectPath: $projectPath } input: { iid: $iid, assigneeUsernames: $assigneeUsernames, projectPath: $fullPath }
) { ) {
errors errors
alert { issuable: alert {
iid iid
assignees { assignees {
nodes { nodes {
username ...User
name ...UserAvailability
avatarUrl
webUrl
} }
} }
notes { notes {
......
#import "../fragments/alert_detail_item.fragment.graphql" #import "~/graphql_shared/fragments/alert_detail_item.fragment.graphql"
#import "~/graphql_shared/fragments/user.fragment.graphql"
mutation alertTodoCreate($projectPath: ID!, $iid: String!) { mutation alertTodoCreate($projectPath: ID!, $iid: String!) {
alertTodoCreate(input: { iid: $iid, projectPath: $projectPath }) { alertTodoCreate(input: { iid: $iid, projectPath: $projectPath }) {
errors errors
alert { alert {
...AlertDetailItem ...AlertDetailItem
assignees {
nodes {
...User
}
}
} }
} }
} }
#import "~/graphql_shared/fragments/alert_detail_item.fragment.graphql"
#import "~/graphql_shared/fragments/user.fragment.graphql"
query alertDetails($fullPath: ID!, $alertId: String) {
project(fullPath: $fullPath) {
alertManagementAlerts(iid: $alertId) {
nodes {
...AlertDetailItem
assignees {
nodes {
...User
}
}
}
}
}
}
#import "~/graphql_shared/fragments/user.fragment.graphql"
#import "~/graphql_shared/fragments/user_availability.fragment.graphql"
query alertAssignees(
$domain: AlertManagementDomainFilter = threat_monitoring
$fullPath: ID!
$iid: String!
) {
workspace: project(fullPath: $fullPath) {
issuable: alertManagementAlert(domain: $domain, iid: $iid) {
iid
assignees {
nodes {
...User
...UserAvailability
}
}
}
}
}
...@@ -74,6 +74,9 @@ export default { ...@@ -74,6 +74,9 @@ export default {
query() { query() {
return participantsQueries[this.issuableType].query; return participantsQueries[this.issuableType].query;
}, },
skip() {
return Boolean(participantsQueries[this.issuableType].skipQuery);
},
variables() { variables() {
return { return {
iid: this.iid, iid: this.iid,
......
<script> <script>
import { GlAlert, GlButton, GlDrawer, GlLink, GlSkeletonLoader } from '@gitlab/ui'; import { GlAlert, GlButton, GlDrawer, GlLink, GlSkeletonLoader } from '@gitlab/ui';
import * as Sentry from '@sentry/browser'; import * as Sentry from '@sentry/browser';
import getAlertDetailsQuery from '~/graphql_shared/queries/alert_details.query.graphql';
import { capitalizeFirstCharacter, splitCamelCase } from '~/lib/utils/text_utility'; import { capitalizeFirstCharacter, splitCamelCase } from '~/lib/utils/text_utility';
import { visitUrl } from '~/lib/utils/url_utility'; import { visitUrl } from '~/lib/utils/url_utility';
import { __ } from '~/locale'; import { __ } from '~/locale';
import SidebarAssigneesWidget from '~/sidebar/components/assignees/sidebar_assignees_widget.vue';
import SidebarStatus from '~/vue_shared/alert_details/components/sidebar/sidebar_status.vue'; import SidebarStatus from '~/vue_shared/alert_details/components/sidebar/sidebar_status.vue';
import createIssueMutation from '~/vue_shared/alert_details/graphql/mutations/alert_issue_create.mutation.graphql'; import createIssueMutation from '~/vue_shared/alert_details/graphql/mutations/alert_issue_create.mutation.graphql';
import getAlertDetailsQuery from '~/vue_shared/alert_details/graphql/queries/alert_details.query.graphql';
import { getContentWrapperHeight } from '../../utils'; import { getContentWrapperHeight } from '../../utils';
import { ALERT_DETAILS_LOADING_ROWS, DRAWER_ERRORS, HIDDEN_VALUES } from './constants'; import { ALERT_DETAILS_LOADING_ROWS, DRAWER_ERRORS, HIDDEN_VALUES } from './constants';
...@@ -23,7 +24,9 @@ export default { ...@@ -23,7 +24,9 @@ export default {
GlDrawer, GlDrawer,
GlLink, GlLink,
GlSkeletonLoader, GlSkeletonLoader,
SidebarAssigneesWidget,
}, },
provide: { canUpdate: true },
inject: ['projectPath'], inject: ['projectPath'],
apollo: { apollo: {
alertDetails: { alertDetails: {
...@@ -159,6 +162,13 @@ export default { ...@@ -159,6 +162,13 @@ export default {
<gl-alert v-if="errored" variant="danger" :dismissible="false" contained> <gl-alert v-if="errored" variant="danger" :dismissible="false" contained>
{{ errorMessage }} {{ errorMessage }}
</gl-alert> </gl-alert>
<sidebar-assignees-widget
issuable-type="alert"
:iid="selectedAlert.iid"
:full-path="projectPath"
:allow-multiple-assignees="false"
@assignees-updated="handleAlertUpdate"
/>
<sidebar-status <sidebar-status
:alert="selectedAlert" :alert="selectedAlert"
:project-path="projectPath" :project-path="projectPath"
......
...@@ -160,7 +160,7 @@ export default { ...@@ -160,7 +160,7 @@ export default {
handleFilterChange(newFilters) { handleFilterChange(newFilters) {
this.filters = newFilters; this.filters = newFilters;
}, },
handleStatusUpdate() { handleAlertUpdate() {
this.$apollo.queries.alerts.refetch(); this.$apollo.queries.alerts.refetch();
}, },
hasAssignees(assignees) { hasAssignees(assignees) {
...@@ -287,7 +287,7 @@ export default { ...@@ -287,7 +287,7 @@ export default {
:alert="item" :alert="item"
:project-path="projectPath" :project-path="projectPath"
@alert-error="handleAlertError" @alert-error="handleAlertError"
@alert-update="handleStatusUpdate" @alert-update="handleAlertUpdate"
/> />
</template> </template>
...@@ -320,8 +320,8 @@ export default { ...@@ -320,8 +320,8 @@ export default {
v-if="selectedAlert" v-if="selectedAlert"
:is-alert-drawer-open="isAlertDrawerOpen" :is-alert-drawer-open="isAlertDrawerOpen"
:selected-alert="selectedAlert" :selected-alert="selectedAlert"
@alert-update="handleStatusUpdate"
@deselect-alert="handleAlertDeselect" @deselect-alert="handleAlertDeselect"
@alert-update="handleAlertUpdate"
/> />
</div> </div>
</template> </template>
import { defaultDataIdFromObject } from 'apollo-cache-inmemory';
import Vue from 'vue'; import Vue from 'vue';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql'; import createDefaultClient from '~/lib/graphql';
...@@ -7,7 +8,20 @@ import createStore from './store'; ...@@ -7,7 +8,20 @@ import createStore from './store';
Vue.use(VueApollo); Vue.use(VueApollo);
const apolloProvider = new VueApollo({ const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(), defaultClient: createDefaultClient(
{},
{
cacheConfig: {
dataIdFromObject: (object) => {
// eslint-disable-next-line no-underscore-dangle
if (object.__typename === 'AlertManagementAlert') {
return object.iid;
}
return defaultDataIdFromObject(object);
},
},
},
),
}); });
export default () => { export default () => {
......
...@@ -7,9 +7,10 @@ import { DRAWER_ERRORS } from 'ee/threat_monitoring/components/alerts/constants' ...@@ -7,9 +7,10 @@ import { DRAWER_ERRORS } from 'ee/threat_monitoring/components/alerts/constants'
import createMockApollo from 'helpers/mock_apollo_helper'; import createMockApollo from 'helpers/mock_apollo_helper';
import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper'; import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
import getAlertDetailsQuery from '~/graphql_shared/queries/alert_details.query.graphql';
import { visitUrl } from '~/lib/utils/url_utility'; import { visitUrl } from '~/lib/utils/url_utility';
import SidebarAssigneesWidget from '~/sidebar/components/assignees/sidebar_assignees_widget.vue';
import SidebarStatus from '~/vue_shared/alert_details/components/sidebar/sidebar_status.vue'; import SidebarStatus from '~/vue_shared/alert_details/components/sidebar/sidebar_status.vue';
import getAlertDetailsQuery from '~/vue_shared/alert_details/graphql/queries/alert_details.query.graphql';
import { import {
erroredGetAlertDetailsQuerySpy, erroredGetAlertDetailsQuerySpy,
getAlertDetailsQueryErrorMessage, getAlertDetailsQueryErrorMessage,
...@@ -44,6 +45,7 @@ describe('Alert Drawer', () => { ...@@ -44,6 +45,7 @@ describe('Alert Drawer', () => {
}); });
const findAlert = () => wrapper.findComponent(GlAlert); const findAlert = () => wrapper.findComponent(GlAlert);
const findAssignee = () => wrapper.findComponent(SidebarAssigneesWidget);
const findCreateIssueButton = () => wrapper.findByTestId('create-issue-button'); const findCreateIssueButton = () => wrapper.findByTestId('create-issue-button');
const findDrawer = () => wrapper.findComponent(GlDrawer); const findDrawer = () => wrapper.findComponent(GlDrawer);
const findIssueLink = () => wrapper.findByTestId('issue-link'); const findIssueLink = () => wrapper.findByTestId('issue-link');
...@@ -84,7 +86,7 @@ describe('Alert Drawer', () => { ...@@ -84,7 +86,7 @@ describe('Alert Drawer', () => {
provide: { provide: {
projectPath: DEFAULT_PROJECT_PATH, projectPath: DEFAULT_PROJECT_PATH,
}, },
stubs: { GlDrawer }, stubs: { GlDrawer, SidebarAssigneesWidget: true, SidebarStatus: true },
...apolloOptions, ...apolloOptions,
}); });
}; };
...@@ -94,6 +96,8 @@ describe('Alert Drawer', () => { ...@@ -94,6 +96,8 @@ describe('Alert Drawer', () => {
component | status | findComponent | state | mount component | status | findComponent | state | mount
${'alert'} | ${'does not display'} | ${findAlert} | ${false} | ${undefined} ${'alert'} | ${'does not display'} | ${findAlert} | ${false} | ${undefined}
${'"Create Issue" button'} | ${'does not display'} | ${findCreateIssueButton} | ${false} | ${undefined} ${'"Create Issue" button'} | ${'does not display'} | ${findCreateIssueButton} | ${false} | ${undefined}
${'assignee widget'} | ${'does display'} | ${findAssignee} | ${true} | ${undefined}
${'status widget'} | ${'does display'} | ${findStatus} | ${true} | ${undefined}
${'details list'} | ${'does display'} | ${findDetails} | ${true} | ${undefined} ${'details list'} | ${'does display'} | ${findDetails} | ${true} | ${undefined}
${'drawer'} | ${'does display'} | ${findDrawer} | ${true} | ${undefined} ${'drawer'} | ${'does display'} | ${findDrawer} | ${true} | ${undefined}
${'issue link'} | ${'does display'} | ${findIssueLink} | ${true} | ${undefined} ${'issue link'} | ${'does display'} | ${findIssueLink} | ${true} | ${undefined}
...@@ -163,6 +167,13 @@ describe('Alert Drawer', () => { ...@@ -163,6 +167,13 @@ describe('Alert Drawer', () => {
}); });
}); });
it('handles an alert assignee update', () => {
createWrapper({ props: { selectedAlert: mockAlerts[0] } });
expect(wrapper.emitted('alert-update')).toBeUndefined();
findAssignee().vm.$emit('assignees-updated');
expect(wrapper.emitted('alert-update')).toEqual([[]]);
});
it('handles an alert status update', () => { it('handles an alert status update', () => {
createWrapper({ props: { selectedAlert: mockAlerts[0] } }); createWrapper({ props: { selectedAlert: mockAlerts[0] } });
expect(wrapper.emitted('alert-update')).toBeUndefined(); expect(wrapper.emitted('alert-update')).toBeUndefined();
......
...@@ -97,6 +97,7 @@ export const mockAlerts = [ ...@@ -97,6 +97,7 @@ export const mockAlerts = [
assignees: { assignees: {
nodes: [ nodes: [
{ {
id: 'Alert:1',
name: 'Administrator', name: 'Administrator',
username: 'root', username: 'root',
avatarUrl: '/test-avatar-url', avatarUrl: '/test-avatar-url',
......
...@@ -21,6 +21,7 @@ describe('Alert Details Sidebar Assignees', () => { ...@@ -21,6 +21,7 @@ describe('Alert Details Sidebar Assignees', () => {
id: 1, id: 1,
name: 'User 1', name: 'User 1',
username: 'root', username: 'root',
webUrl: 'https://gitlab:3443/root',
}, },
{ {
avatar_url: avatar_url:
...@@ -28,6 +29,7 @@ describe('Alert Details Sidebar Assignees', () => { ...@@ -28,6 +29,7 @@ describe('Alert Details Sidebar Assignees', () => {
id: 2, id: 2,
name: 'User 2', name: 'User 2',
username: 'not-root', username: 'not-root',
webUrl: 'https://gitlab:3443/non-root',
}, },
]; ];
...@@ -128,7 +130,7 @@ describe('Alert Details Sidebar Assignees', () => { ...@@ -128,7 +130,7 @@ describe('Alert Details Sidebar Assignees', () => {
variables: { variables: {
iid: '1527542', iid: '1527542',
assigneeUsernames: ['root'], assigneeUsernames: ['root'],
projectPath: 'projectPath', fullPath: 'projectPath',
}, },
}); });
}); });
...@@ -137,7 +139,7 @@ describe('Alert Details Sidebar Assignees', () => { ...@@ -137,7 +139,7 @@ describe('Alert Details Sidebar Assignees', () => {
wrapper.setData({ isDropdownSearching: false }); wrapper.setData({ isDropdownSearching: false });
const errorMutationResult = { const errorMutationResult = {
data: { data: {
alertSetAssignees: { issuableSetAssignees: {
errors: ['There was a problem for sure.'], errors: ['There was a problem for sure.'],
alert: {}, alert: {},
}, },
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment