Commit bb340771 authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch '222359-alert-todo' into 'master'

Alert To Do UI

Closes #222359

See merge request gitlab-org/gitlab!38595
parents d03735af 593b3eab
<script> <script>
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 axios from '~/lib/utils/axios_utils'; import createAlertTodo from '../../graphql/mutations/alert_todo_create.mutation.graphql';
import createAlertTodo from '../../graphql/mutations/alert_todo_create.graphql'; import todoMarkDone from '../../graphql/mutations/alert_todo_mark_done.mutation.graphql';
import alertQuery from '../../graphql/queries/details.query.graphql';
export default { export default {
i18n: { i18n: {
...@@ -30,14 +31,24 @@ export default { ...@@ -30,14 +31,24 @@ export default {
data() { data() {
return { return {
isUpdating: false, isUpdating: false,
isTodo: false,
todo: '',
}; };
}, },
computed: { computed: {
alertID() { alertID() {
return parseInt(this.alert.iid, 10); return parseInt(this.alert.iid, 10);
}, },
firstToDoId() {
return this.alert?.todos?.nodes[0]?.id;
},
hasPendingTodos() {
return this.alert?.todos?.nodes.length > 0;
},
getAlertQueryVariables() {
return {
fullPath: this.projectPath,
alertId: this.alert.iid,
};
},
}, },
methods: { methods: {
updateToDoCount(add) { updateToDoCount(add) {
...@@ -51,11 +62,7 @@ export default { ...@@ -51,11 +62,7 @@ export default {
return document.dispatchEvent(headerTodoEvent); return document.dispatchEvent(headerTodoEvent);
}, },
toggleTodo() { addToDo() {
if (this.todo) {
return this.markAsDone();
}
this.isUpdating = true; this.isUpdating = true;
return this.$apollo return this.$apollo
.mutate({ .mutate({
...@@ -65,24 +72,14 @@ export default { ...@@ -65,24 +72,14 @@ export default {
projectPath: this.projectPath, projectPath: this.projectPath,
}, },
}) })
.then(({ data: { alertTodoCreate: { todo = {}, errors = [] } } = {} } = {}) => { .then(({ data: { errors = [] } }) => {
if (errors[0]) { if (errors[0]) {
return this.$emit( return this.throwError(errors[0]);
'alert-error',
`${this.$options.i18n.UPDATE_ALERT_TODO_ERROR} ${errors[0]}.`,
);
} }
this.todo = todo.id;
return this.updateToDoCount(true); return this.updateToDoCount(true);
}) })
.catch(() => { .catch(() => {
this.$emit( this.throwError();
'alert-error',
`${this.$options.i18n.UPDATE_ALERT_TODO_ERROR} ${s__(
'AlertManagement|Please try again.',
)}`,
);
}) })
.finally(() => { .finally(() => {
this.isUpdating = false; this.isUpdating = false;
...@@ -90,20 +87,45 @@ export default { ...@@ -90,20 +87,45 @@ export default {
}, },
markAsDone() { markAsDone() {
this.isUpdating = true; this.isUpdating = true;
return this.$apollo
return axios .mutate({
.delete(`/dashboard/todos/${this.todo.split('/').pop()}`) mutation: todoMarkDone,
.then(() => { variables: {
this.todo = ''; id: this.firstToDoId,
},
update: this.updateCache,
})
.then(({ data: { errors = [] } }) => {
if (errors[0]) {
return this.throwError(errors[0]);
}
return this.updateToDoCount(false); return this.updateToDoCount(false);
}) })
.catch(() => { .catch(() => {
this.$emit('alert-error', this.$options.i18n.UPDATE_ALERT_TODO_ERROR); this.throwError();
}) })
.finally(() => { .finally(() => {
this.isUpdating = false; this.isUpdating = false;
}); });
}, },
updateCache(store) {
const data = store.readQuery({
query: alertQuery,
variables: this.getAlertQueryVariables,
});
data.project.alertManagementAlerts.nodes[0].todos.nodes.shift();
store.writeQuery({
query: alertQuery,
variables: this.getAlertQueryVariables,
data,
});
},
throwError(err = '') {
const error = err || s__('AlertManagement|Please try again.');
this.$emit('alert-error', `${this.$options.i18n.UPDATE_ALERT_TODO_ERROR} ${error}`);
},
}, },
}; };
</script> </script>
...@@ -114,10 +136,10 @@ export default { ...@@ -114,10 +136,10 @@ export default {
data-testid="alert-todo-button" data-testid="alert-todo-button"
:collapsed="sidebarCollapsed" :collapsed="sidebarCollapsed"
:issuable-id="alertID" :issuable-id="alertID"
:is-todo="todo !== ''" :is-todo="hasPendingTodos"
:is-action-active="isUpdating" :is-action-active="isUpdating"
issuable-type="alert" issuable-type="alert"
@toggleTodo="toggleTodo" @toggleTodo="hasPendingTodos ? markAsDone() : addToDo()"
/> />
</div> </div>
</template> </template>
...@@ -11,6 +11,11 @@ fragment AlertDetailItem on AlertManagementAlert { ...@@ -11,6 +11,11 @@ fragment AlertDetailItem on AlertManagementAlert {
updatedAt updatedAt
endedAt endedAt
details details
todos {
nodes {
id
}
}
notes { notes {
nodes { nodes {
...AlertNote ...AlertNote
......
mutation($projectPath: ID!, $iid: String!) { #import "../fragments/detail_item.fragment.graphql"
mutation alertTodoCreate($projectPath: ID!, $iid: String!) {
alertTodoCreate(input: { iid: $iid, projectPath: $projectPath }) { alertTodoCreate(input: { iid: $iid, projectPath: $projectPath }) {
errors errors
alert { alert {
iid ...AlertDetailItem
}
todo {
id
} }
} }
} }
mutation todoMarkDone($id: ID!) {
todoMarkDone(input: { id: $id }) {
errors
todo {
id
}
}
}
---
title: Add Mark as done capability to Alert To Do's
merge_request: 38595
author:
type: changed
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import SidebarTodo from '~/alert_management/components/sidebar/sidebar_todo.vue'; import SidebarTodo from '~/alert_management/components/sidebar/sidebar_todo.vue';
import AlertMarkTodo from '~/alert_management/graphql/mutations/alert_todo_create.graphql'; import AlertMarkTodo from '~/alert_management/graphql/mutations/alert_todo_create.mutation.graphql';
import mockAlerts from '../mocks/alerts.json'; import mockAlerts from '../mocks/alerts.json';
const mockAlert = mockAlerts[0]; const mockAlert = mockAlerts[0];
...@@ -34,6 +34,8 @@ describe('Alert Details Sidebar To Do', () => { ...@@ -34,6 +34,8 @@ describe('Alert Details Sidebar To Do', () => {
wrapper.destroy(); wrapper.destroy();
}); });
const findToDoButton = () => wrapper.find('[data-testid="alert-todo-button"]');
describe('updating the alert to do', () => { describe('updating the alert to do', () => {
const mockUpdatedMutationResult = { const mockUpdatedMutationResult = {
data: { data: {
...@@ -44,6 +46,7 @@ describe('Alert Details Sidebar To Do', () => { ...@@ -44,6 +46,7 @@ describe('Alert Details Sidebar To Do', () => {
}, },
}; };
describe('adding a todo', () => {
beforeEach(() => { beforeEach(() => {
mountComponent({ mountComponent({
data: { alert: mockAlert }, data: { alert: mockAlert },
...@@ -52,17 +55,18 @@ describe('Alert Details Sidebar To Do', () => { ...@@ -52,17 +55,18 @@ describe('Alert Details Sidebar To Do', () => {
}); });
}); });
it('renders a button for adding a To-Do', () => { it('renders a button for adding a To-Do', async () => {
return wrapper.vm.$nextTick().then(() => { await wrapper.vm.$nextTick();
expect(wrapper.find('[data-testid="alert-todo-button"]').text()).toBe('Add a To-Do');
}); expect(findToDoButton().text()).toBe('Add a To-Do');
}); });
it('calls `$apollo.mutate` with `AlertMarkTodo` mutation and variables containing `iid`, `todoEvent`, & `projectPath`', () => { it('calls `$apollo.mutate` with `AlertMarkTodo` mutation and variables containing `iid`, `todoEvent`, & `projectPath`', async () => {
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(mockUpdatedMutationResult); jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(mockUpdatedMutationResult);
return wrapper.vm.$nextTick().then(() => { findToDoButton().trigger('click');
wrapper.find('button').trigger('click'); await wrapper.vm.$nextTick();
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({ expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
mutation: AlertMarkTodo, mutation: AlertMarkTodo,
variables: { variables: {
...@@ -72,5 +76,28 @@ describe('Alert Details Sidebar To Do', () => { ...@@ -72,5 +76,28 @@ describe('Alert Details Sidebar To Do', () => {
}); });
}); });
}); });
describe('removing a todo', () => {
beforeEach(() => {
mountComponent({
data: { alert: { ...mockAlert, todos: { nodes: [{ id: '1234' }] } } },
sidebarCollapsed: false,
loading: false,
});
});
it('renders a Mark As Done button when todo is present', async () => {
await wrapper.vm.$nextTick();
expect(findToDoButton().text()).toBe('Mark as done');
});
it('calls `$apollo.mutate` with `AlertMarkTodoDone` mutation and variables containing `id`', async () => {
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(mockUpdatedMutationResult);
findToDoButton().trigger('click');
await wrapper.vm.$nextTick();
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledTimes(1);
});
});
}); });
}); });
...@@ -9,7 +9,8 @@ ...@@ -9,7 +9,8 @@
"endedAt": "2020-04-17T23:18:14.996Z", "endedAt": "2020-04-17T23:18:14.996Z",
"status": "TRIGGERED", "status": "TRIGGERED",
"assignees": { "nodes": [] }, "assignees": { "nodes": [] },
"notes": { "nodes": [] } "notes": { "nodes": [] },
"todos": { "nodes": [] }
}, },
{ {
"iid": "1527543", "iid": "1527543",
...@@ -37,7 +38,8 @@ ...@@ -37,7 +38,8 @@
"systemNoteIconName": "user" "systemNoteIconName": "user"
} }
] ]
} },
"todos": { "nodes": [] }
}, },
{ {
"iid": "1527544", "iid": "1527544",
...@@ -63,6 +65,7 @@ ...@@ -63,6 +65,7 @@
} }
} }
] ]
} },
"todos": { "nodes": [] }
} }
] ]
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