Commit f03d361b authored by Allison Browne's avatar Allison Browne Committed by charlie ablett

Assign alerts todo ui

Hook up todo mutation to button
parent 827ac51d
...@@ -51,9 +51,18 @@ export default { ...@@ -51,9 +51,18 @@ export default {
<div class="issuable-sidebar js-issuable-update"> <div class="issuable-sidebar js-issuable-update">
<sidebar-header <sidebar-header
:sidebar-collapsed="sidebarStatus" :sidebar-collapsed="sidebarStatus"
:project-path="projectPath"
:alert="alert"
@toggle-sidebar="$emit('toggle-sidebar')" @toggle-sidebar="$emit('toggle-sidebar')"
@alert-error="$emit('alert-error', $event)"
/>
<sidebar-todo
v-if="sidebarStatus"
:project-path="projectPath"
:alert="alert"
:sidebar-collapsed="sidebarStatus"
@alert-error="$emit('alert-error', $event)"
/> />
<sidebar-todo v-if="sidebarStatus" :sidebar-collapsed="sidebarStatus" />
<sidebar-status <sidebar-status
:project-path="projectPath" :project-path="projectPath"
:alert="alert" :alert="alert"
......
...@@ -167,7 +167,7 @@ export default { ...@@ -167,7 +167,7 @@ export default {
if (errors[0]) { if (errors[0]) {
this.$emit( this.$emit(
'alert-sidebar-error', 'alert-error',
`${this.$options.i18n.UPDATE_ALERT_ASSIGNEES_GRAPHQL_ERROR} ${errors[0]}.`, `${this.$options.i18n.UPDATE_ALERT_ASSIGNEES_GRAPHQL_ERROR} ${errors[0]}.`,
); );
} }
......
...@@ -8,6 +8,14 @@ export default { ...@@ -8,6 +8,14 @@ export default {
SidebarTodo, SidebarTodo,
}, },
props: { props: {
alert: {
type: Object,
required: true,
},
projectPath: {
type: String,
required: true,
},
sidebarCollapsed: { sidebarCollapsed: {
type: Boolean, type: Boolean,
required: true, required: true,
...@@ -17,18 +25,17 @@ export default { ...@@ -17,18 +25,17 @@ export default {
</script> </script>
<template> <template>
<div class="block d-flex justify-content-between"> <div class="block gl-display-flex gl-justify-content-space-between">
<span class="issuable-header-text hide-collapsed"> <span class="issuable-header-text hide-collapsed">
{{ __('Quick actions') }} {{ __('To Do') }}
</span> </span>
<toggle-sidebar <sidebar-todo
:collapsed="sidebarCollapsed" v-if="!sidebarCollapsed"
css-classes="ml-auto" :project-path="projectPath"
@toggle="$emit('toggle-sidebar')" :alert="alert"
:sidebar-collapsed="sidebarCollapsed"
@alert-error="$emit('alert-error', $event)"
/> />
<!-- TODO: Implement after or as part of: https://gitlab.com/gitlab-org/gitlab/-/issues/215946 --> <toggle-sidebar :collapsed="sidebarCollapsed" @toggle="$emit('toggle-sidebar')" />
<template v-if="false">
<sidebar-todo v-if="!sidebarCollapsed" :sidebar-collapsed="sidebarCollapsed" />
</template>
</div> </div>
</template> </template>
<script> <script>
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.graphql';
export default { export default {
i18n: {
UPDATE_ALERT_TODO_ERROR: s__(
'AlertManagement|There was an error while updating the To Do of the alert.',
),
},
components: { components: {
Todo, Todo,
}, },
props: { props: {
alert: {
type: Object,
required: true,
},
projectPath: {
type: String,
required: true,
},
sidebarCollapsed: { sidebarCollapsed: {
type: Boolean, type: Boolean,
required: true, required: true,
}, },
}, },
data() {
return {
isUpdating: false,
isTodo: false,
todo: '',
};
},
computed: {
alertID() {
return parseInt(this.alert.iid, 10);
},
},
methods: {
updateToDoCount(add) {
const oldCount = parseInt(document.querySelector('.todos-count').innerText, 10);
const count = add ? oldCount + 1 : oldCount - 1;
const headerTodoEvent = new CustomEvent('todo:toggle', {
detail: {
count,
},
});
return document.dispatchEvent(headerTodoEvent);
},
toggleTodo() {
if (this.todo) {
return this.markAsDone();
}
this.isUpdating = true;
return this.$apollo
.mutate({
mutation: createAlertTodo,
variables: {
iid: this.alert.iid,
projectPath: this.projectPath,
},
})
.then(({ data: { alertTodoCreate: { todo = {}, errors = [] } } = {} } = {}) => {
if (errors[0]) {
return this.$emit(
'alert-error',
`${this.$options.i18n.UPDATE_ALERT_TODO_ERROR} ${errors[0]}.`,
);
}
this.todo = todo.id;
return this.updateToDoCount(true);
})
.catch(() => {
this.$emit(
'alert-error',
`${this.$options.i18n.UPDATE_ALERT_TODO_ERROR} ${s__(
'AlertManagement|Please try again.',
)}`,
);
})
.finally(() => {
this.isUpdating = false;
});
},
markAsDone() {
this.isUpdating = true;
return axios
.delete(`/dashboard/todos/${this.todo.split('/').pop()}`)
.then(() => {
this.todo = '';
return this.updateToDoCount(false);
})
.catch(() => {
this.$emit('alert-error', this.$options.i18n.UPDATE_ALERT_TODO_ERROR);
})
.finally(() => {
this.isUpdating = false;
});
},
},
}; };
</script> </script>
<!-- TODO: Implement after or as part of: https://gitlab.com/gitlab-org/gitlab/-/issues/215946 -->
<template> <template>
<div v-if="false" :class="{ 'block todo': sidebarCollapsed }"> <div :class="{ 'block todo': sidebarCollapsed, 'gl-ml-auto': !sidebarCollapsed }">
<todo <todo
data-testid="alert-todo-button"
:collapsed="sidebarCollapsed" :collapsed="sidebarCollapsed"
:issuable-id="1" :issuable-id="alertID"
:is-todo="false" :is-todo="todo !== ''"
:is-action-active="false" :is-action-active="isUpdating"
issuable-type="alert" issuable-type="alert"
@toggleTodo="() => {}" @toggleTodo="toggleTodo"
/> />
</div> </div>
</template> </template>
mutation($projectPath: ID!, $iid: String!) {
alertTodoCreate(input: { iid: $iid, projectPath: $projectPath }) {
errors
alert {
iid
}
todo {
id
}
}
}
...@@ -16,10 +16,11 @@ import Tracking from '~/tracking'; ...@@ -16,10 +16,11 @@ import Tracking from '~/tracking';
*/ */
export default function initTodoToggle() { export default function initTodoToggle() {
$(document).on('todo:toggle', (e, count) => { $(document).on('todo:toggle', (e, count) => {
const updatedCount = count || e?.detail?.count || 0;
const $todoPendingCount = $('.todos-count'); const $todoPendingCount = $('.todos-count');
$todoPendingCount.text(highCountTrim(count)); $todoPendingCount.text(highCountTrim(updatedCount));
$todoPendingCount.toggleClass('hidden', count === 0); $todoPendingCount.toggleClass('hidden', updatedCount === 0);
}); });
} }
......
# frozen_string_literal: true
module Mutations
module AlertManagement
module Alerts
module Todo
class Create < Base
graphql_name 'AlertTodoCreate'
def resolve(args)
alert = authorized_find!(project_path: args[:project_path], iid: args[:iid])
result = ::AlertManagement::Alerts::Todo::CreateService.new(alert, current_user).execute
prepare_response(result)
end
private
def prepare_response(result)
{
alert: result.payload[:alert],
todo: result.payload[:todo],
errors: result.error? ? [result.message] : []
}
end
end
end
end
end
end
...@@ -18,6 +18,11 @@ module Mutations ...@@ -18,6 +18,11 @@ module Mutations
null: true, null: true,
description: "The alert after mutation" description: "The alert after mutation"
field :todo,
Types::TodoType,
null: true,
description: "The todo after mutation"
field :issue, field :issue,
Types::IssueType, Types::IssueType,
null: true, null: true,
......
...@@ -10,6 +10,7 @@ module Types ...@@ -10,6 +10,7 @@ module Types
mount_mutation Mutations::AlertManagement::CreateAlertIssue mount_mutation Mutations::AlertManagement::CreateAlertIssue
mount_mutation Mutations::AlertManagement::UpdateAlertStatus mount_mutation Mutations::AlertManagement::UpdateAlertStatus
mount_mutation Mutations::AlertManagement::Alerts::SetAssignees mount_mutation Mutations::AlertManagement::Alerts::SetAssignees
mount_mutation Mutations::AlertManagement::Alerts::Todo::Create
mount_mutation Mutations::AwardEmojis::Add mount_mutation Mutations::AwardEmojis::Add
mount_mutation Mutations::AwardEmojis::Remove mount_mutation Mutations::AwardEmojis::Remove
mount_mutation Mutations::AwardEmojis::Toggle mount_mutation Mutations::AwardEmojis::Toggle
......
...@@ -6,6 +6,7 @@ module Types ...@@ -6,6 +6,7 @@ module Types
value 'ISSUE', value: 'Issue', description: 'An Issue' value 'ISSUE', value: 'Issue', description: 'An Issue'
value 'MERGEREQUEST', value: 'MergeRequest', description: 'A MergeRequest' value 'MERGEREQUEST', value: 'MergeRequest', description: 'A MergeRequest'
value 'DESIGN', value: 'DesignManagement::Design', description: 'A Design' value 'DESIGN', value: 'DesignManagement::Design', description: 'A Design'
value 'ALERT', value: 'AlertManagement::Alert', description: 'An Alert'
end end
end end
......
# frozen_string_literal: true
module AlertManagement
module Alerts
module Todo
class CreateService
# @param alert [AlertManagement::Alert]
# @param current_user [User]
def initialize(alert, current_user)
@alert = alert
@current_user = current_user
end
def execute
return error_no_permissions unless allowed?
todos = TodoService.new.mark_todo(alert, current_user)
todo = todos&.first
return error_existing_todo unless todo
success(todo)
end
private
attr_reader :alert, :current_user
def allowed?
current_user&.can?(:update_alert_management_alert, alert)
end
def error(message)
ServiceResponse.error(payload: { alert: alert, todo: nil }, message: message)
end
def success(todo)
ServiceResponse.success(payload: { alert: alert, todo: todo })
end
def error_no_permissions
error(_('You have insufficient permissions to create a Todo for this alert'))
end
def error_existing_todo
error(_('You already have pending todo for this alert'))
end
end
end
end
end
...@@ -162,9 +162,9 @@ class TodoService ...@@ -162,9 +162,9 @@ class TodoService
create_assignment_todo(alert, current_user, []) create_assignment_todo(alert, current_user, [])
end end
# When user marks an issue as todo # When user marks a target as todo
def mark_todo(issuable, current_user) def mark_todo(target, current_user)
attributes = attributes_for_todo(issuable.project, issuable, current_user, Todo::MARKED) attributes = attributes_for_todo(target.project, target, current_user, Todo::MARKED)
create_todos(current_user, attributes) create_todos(current_user, attributes)
end end
......
---
title: Add ability for user to manually create a todo for an alert
merge_request: 34175
author:
type: added
...@@ -603,6 +603,61 @@ type AlertSetAssigneesPayload { ...@@ -603,6 +603,61 @@ type AlertSetAssigneesPayload {
The issue created after mutation The issue created after mutation
""" """
issue: Issue issue: Issue
"""
The todo after mutation
"""
todo: Todo
}
"""
Autogenerated input type of AlertTodoCreate
"""
input AlertTodoCreateInput {
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
The iid of the alert to mutate
"""
iid: String!
"""
The project the alert to mutate is in
"""
projectPath: ID!
}
"""
Autogenerated return type of AlertTodoCreate
"""
type AlertTodoCreatePayload {
"""
The alert after mutation
"""
alert: AlertManagementAlert
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
Errors encountered during execution of the mutation.
"""
errors: [String!]!
"""
The issue created after mutation
"""
issue: Issue
"""
The todo after mutation
"""
todo: Todo
} }
""" """
...@@ -1575,6 +1630,11 @@ type CreateAlertIssuePayload { ...@@ -1575,6 +1630,11 @@ type CreateAlertIssuePayload {
The issue created after mutation The issue created after mutation
""" """
issue: Issue issue: Issue
"""
The todo after mutation
"""
todo: Todo
} }
""" """
...@@ -8054,6 +8114,7 @@ type Mutation { ...@@ -8054,6 +8114,7 @@ type Mutation {
addProjectToSecurityDashboard(input: AddProjectToSecurityDashboardInput!): AddProjectToSecurityDashboardPayload addProjectToSecurityDashboard(input: AddProjectToSecurityDashboardInput!): AddProjectToSecurityDashboardPayload
adminSidekiqQueuesDeleteJobs(input: AdminSidekiqQueuesDeleteJobsInput!): AdminSidekiqQueuesDeleteJobsPayload adminSidekiqQueuesDeleteJobs(input: AdminSidekiqQueuesDeleteJobsInput!): AdminSidekiqQueuesDeleteJobsPayload
alertSetAssignees(input: AlertSetAssigneesInput!): AlertSetAssigneesPayload alertSetAssignees(input: AlertSetAssigneesInput!): AlertSetAssigneesPayload
alertTodoCreate(input: AlertTodoCreateInput!): AlertTodoCreatePayload
awardEmojiAdd(input: AwardEmojiAddInput!): AwardEmojiAddPayload awardEmojiAdd(input: AwardEmojiAddInput!): AwardEmojiAddPayload
awardEmojiRemove(input: AwardEmojiRemoveInput!): AwardEmojiRemovePayload awardEmojiRemove(input: AwardEmojiRemoveInput!): AwardEmojiRemovePayload
awardEmojiToggle(input: AwardEmojiToggleInput!): AwardEmojiTogglePayload awardEmojiToggle(input: AwardEmojiToggleInput!): AwardEmojiTogglePayload
...@@ -13268,6 +13329,11 @@ enum TodoStateEnum { ...@@ -13268,6 +13329,11 @@ enum TodoStateEnum {
} }
enum TodoTargetEnum { enum TodoTargetEnum {
"""
An Alert
"""
ALERT
""" """
A Commit A Commit
""" """
...@@ -13589,6 +13655,11 @@ type UpdateAlertStatusPayload { ...@@ -13589,6 +13655,11 @@ type UpdateAlertStatusPayload {
The issue created after mutation The issue created after mutation
""" """
issue: Issue issue: Issue
"""
The todo after mutation
"""
todo: Todo
} }
""" """
......
...@@ -1460,6 +1460,164 @@ ...@@ -1460,6 +1460,164 @@
}, },
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
},
{
"name": "todo",
"description": "The todo after mutation",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "Todo",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "INPUT_OBJECT",
"name": "AlertTodoCreateInput",
"description": "Autogenerated input type of AlertTodoCreate",
"fields": null,
"inputFields": [
{
"name": "projectPath",
"description": "The project the alert to mutate is in",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "iid",
"description": "The iid of the alert to mutate",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "clientMutationId",
"description": "A unique identifier for the client performing the mutation.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
}
],
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "AlertTodoCreatePayload",
"description": "Autogenerated return type of AlertTodoCreate",
"fields": [
{
"name": "alert",
"description": "The alert after mutation",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "AlertManagementAlert",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "clientMutationId",
"description": "A unique identifier for the client performing the mutation.",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "errors",
"description": "Errors encountered during execution of the mutation.",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
}
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "issue",
"description": "The issue created after mutation",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "Issue",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "todo",
"description": "The todo after mutation",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "Todo",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
} }
], ],
"inputFields": null, "inputFields": null,
...@@ -4177,6 +4335,20 @@ ...@@ -4177,6 +4335,20 @@
}, },
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
},
{
"name": "todo",
"description": "The todo after mutation",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "Todo",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
} }
], ],
"inputFields": null, "inputFields": null,
...@@ -22674,6 +22846,33 @@ ...@@ -22674,6 +22846,33 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
}, },
{
"name": "alertTodoCreate",
"description": null,
"args": [
{
"name": "input",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "INPUT_OBJECT",
"name": "AlertTodoCreateInput",
"ofType": null
}
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "AlertTodoCreatePayload",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "awardEmojiAdd", "name": "awardEmojiAdd",
"description": null, "description": null,
...@@ -39273,6 +39472,12 @@ ...@@ -39273,6 +39472,12 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
}, },
{
"name": "ALERT",
"description": "An Alert",
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "EPIC", "name": "EPIC",
"description": "An Epic", "description": "An Epic",
...@@ -40159,6 +40364,20 @@ ...@@ -40159,6 +40364,20 @@
}, },
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
},
{
"name": "todo",
"description": "The todo after mutation",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "Todo",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
} }
], ],
"inputFields": null, "inputFields": null,
...@@ -101,6 +101,19 @@ Autogenerated return type of AlertSetAssignees ...@@ -101,6 +101,19 @@ Autogenerated return type of AlertSetAssignees
| `clientMutationId` | String | A unique identifier for the client performing the mutation. | | `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `errors` | String! => Array | Errors encountered during execution of the mutation. | | `errors` | String! => Array | Errors encountered during execution of the mutation. |
| `issue` | Issue | The issue created after mutation | | `issue` | Issue | The issue created after mutation |
| `todo` | Todo | The todo after mutation |
## AlertTodoCreatePayload
Autogenerated return type of AlertTodoCreate
| Name | Type | Description |
| --- | ---- | ---------- |
| `alert` | AlertManagementAlert | The alert after mutation |
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
| `issue` | Issue | The issue created after mutation |
| `todo` | Todo | The todo after mutation |
## AwardEmoji ## AwardEmoji
...@@ -274,6 +287,7 @@ Autogenerated return type of CreateAlertIssue ...@@ -274,6 +287,7 @@ Autogenerated return type of CreateAlertIssue
| `clientMutationId` | String | A unique identifier for the client performing the mutation. | | `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `errors` | String! => Array | Errors encountered during execution of the mutation. | | `errors` | String! => Array | Errors encountered during execution of the mutation. |
| `issue` | Issue | The issue created after mutation | | `issue` | Issue | The issue created after mutation |
| `todo` | Todo | The todo after mutation |
## CreateAnnotationPayload ## CreateAnnotationPayload
...@@ -2049,6 +2063,7 @@ Autogenerated return type of UpdateAlertStatus ...@@ -2049,6 +2063,7 @@ Autogenerated return type of UpdateAlertStatus
| `clientMutationId` | String | A unique identifier for the client performing the mutation. | | `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `errors` | String! => Array | Errors encountered during execution of the mutation. | | `errors` | String! => Array | Errors encountered during execution of the mutation. |
| `issue` | Issue | The issue created after mutation | | `issue` | Issue | The issue created after mutation |
| `todo` | Todo | The todo after mutation |
## UpdateContainerExpirationPolicyPayload ## UpdateContainerExpirationPolicyPayload
......
...@@ -2060,6 +2060,9 @@ msgstr "" ...@@ -2060,6 +2060,9 @@ msgstr ""
msgid "AlertManagement|Overview" msgid "AlertManagement|Overview"
msgstr "" msgstr ""
msgid "AlertManagement|Please try again."
msgstr ""
msgid "AlertManagement|Reported %{when}" msgid "AlertManagement|Reported %{when}"
msgstr "" msgstr ""
...@@ -2090,6 +2093,9 @@ msgstr "" ...@@ -2090,6 +2093,9 @@ msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear." msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr "" msgstr ""
msgid "AlertManagement|There was an error while updating the To Do of the alert."
msgstr ""
msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again." msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
msgstr "" msgstr ""
...@@ -19131,9 +19137,6 @@ msgstr "" ...@@ -19131,9 +19137,6 @@ msgstr ""
msgid "Queued" msgid "Queued"
msgstr "" msgstr ""
msgid "Quick actions"
msgstr ""
msgid "Quick actions can be used in the issues description and comment boxes." msgid "Quick actions can be used in the issues description and comment boxes."
msgstr "" msgstr ""
...@@ -26720,6 +26723,9 @@ msgstr "" ...@@ -26720,6 +26723,9 @@ msgstr ""
msgid "You" msgid "You"
msgstr "" msgstr ""
msgid "You already have pending todo for this alert"
msgstr ""
msgid "You are about to delete %{domain} from your instance. This domain will no longer be available to any Knative application." msgid "You are about to delete %{domain} from your instance. This domain will no longer be available to any Knative application."
msgstr "" msgstr ""
...@@ -26999,6 +27005,9 @@ msgstr "" ...@@ -26999,6 +27005,9 @@ msgstr ""
msgid "You have imported from this project %{numberOfPreviousImports} times before. Each new import will create duplicate issues." msgid "You have imported from this project %{numberOfPreviousImports} times before. Each new import will create duplicate issues."
msgstr "" msgstr ""
msgid "You have insufficient permissions to create a Todo for this alert"
msgstr ""
msgid "You have no permissions" msgid "You have no permissions"
msgstr "" msgstr ""
......
import { mount } from '@vue/test-utils';
import SidebarTodo from '~/alert_management/components/sidebar/sidebar_todo.vue';
import AlertMarkTodo from '~/alert_management/graphql/mutations/alert_todo_create.graphql';
import mockAlerts from '../mocks/alerts.json';
const mockAlert = mockAlerts[0];
describe('Alert Details Sidebar To Do', () => {
let wrapper;
function mountComponent({ data, sidebarCollapsed = true, loading = false, stubs = {} } = {}) {
wrapper = mount(SidebarTodo, {
propsData: {
alert: { ...mockAlert },
...data,
sidebarCollapsed,
projectPath: 'projectPath',
},
mocks: {
$apollo: {
mutate: jest.fn(),
queries: {
alert: {
loading,
},
},
},
},
stubs,
});
}
afterEach(() => {
wrapper.destroy();
});
describe('updating the alert to do', () => {
const mockUpdatedMutationResult = {
data: {
updateAlertTodo: {
errors: [],
alert: {},
},
},
};
beforeEach(() => {
mountComponent({
data: { alert: mockAlert },
sidebarCollapsed: false,
loading: false,
});
});
it('renders a button for adding a To Do', () => {
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.find('[data-testid="alert-todo-button"]').text()).toBe('Add a To Do');
});
});
it('calls `$apollo.mutate` with `AlertMarkTodo` mutation and variables containing `iid`, `todoEvent`, & `projectPath`', () => {
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(mockUpdatedMutationResult);
return wrapper.vm.$nextTick().then(() => {
wrapper.find('button').trigger('click');
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
mutation: AlertMarkTodo,
variables: {
iid: '1527542',
projectPath: 'projectPath',
},
});
});
});
});
});
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Mutations::AlertManagement::Alerts::Todo::Create do
subject(:mutation) { described_class.new(object: project, context: { current_user: current_user }, field: nil) }
let_it_be(:alert) { create(:alert_management_alert) }
let_it_be(:project) { alert.project }
let(:current_user) { project.owner }
let(:args) { { project_path: project.full_path, iid: alert.iid } }
specify { expect(described_class).to require_graphql_authorizations(:update_alert_management_alert) }
describe '#resolve' do
subject(:resolve) { mutation.resolve(args) }
context 'when user does not have permissions' do
let(:current_user) { nil }
specify { expect { resolve }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) }
end
context 'when project is invalid' do
let(:args) { { project_path: 'bunk/path', iid: alert.iid } }
specify { expect { resolve }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) }
end
context 'when alert is invalid' do
let(:args) { { project_path: project.full_path, iid: "-1" } }
specify { expect { resolve }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) }
end
context 'when the create service yields errors' do
let(:error_response) { double(error?: true, message: 'error', payload: { alert: {} }) }
before do
allow_next_instance_of(::AlertManagement::Alerts::Todo::CreateService) do |service|
allow(service).to receive(:execute).and_return(error_response)
end
end
specify { expect { resolve }.not_to change(Todo, :count) }
specify { expect(resolve[:errors]).to eq([error_response.message]) }
end
context 'with valid inputs' do
it 'creates a new todo' do
expect { resolve }.to change { Todo.where(user: current_user, action: Todo::MARKED).count }.by(1)
end
it { is_expected.to eq(alert: alert, todo: Todo.last, errors: []) }
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Creating a todo for the alert' do
include GraphqlHelpers
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let(:alert) { create(:alert_management_alert, project: project) }
let(:mutation) do
variables = {
project_path: project.full_path,
iid: alert.iid.to_s
}
graphql_mutation(:alert_todo_create, variables) do
<<~QL
clientMutationId
errors
todo {
author {
username
}
}
QL
end
end
let(:mutation_response) { graphql_mutation_response(:alert_todo_create) }
before do
project.add_developer(user)
end
it 'creates a todo for the current user' do
post_graphql_mutation(mutation, current_user: user)
expect(response).to have_gitlab_http_status(:success)
expect(mutation_response['todo']['author']['username']).to eq(user.username)
end
context 'todo already exists' do
before do
create(:todo, :pending, project: project, user: user, target: alert)
end
it 'surfaces an error' do
post_graphql_mutation(mutation, current_user: user)
expect(response).to have_gitlab_http_status(:success)
expect(mutation_response['errors']).to eq(['You already have pending todo for this alert'])
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe AlertManagement::Alerts::Todo::CreateService do
let_it_be(:user) { create(:user) }
let_it_be(:alert) { create(:alert_management_alert) }
let(:current_user) { user }
describe '#execute' do
subject(:result) { AlertManagement::Alerts::Todo::CreateService.new(alert, current_user).execute }
shared_examples 'permissions error' do
it 'returns an error', :aggregate_failures do
expect(result.error?).to be(true)
expect(result.message).to eq('You have insufficient permissions to create a Todo for this alert')
expect(result.payload[:todo]).to be(nil)
expect(result.payload[:alert]).to be(alert)
end
end
context 'when the user is anonymous' do
let(:current_user) { nil }
it_behaves_like 'permissions error'
end
context 'when the user does not have permission' do
it_behaves_like 'permissions error'
end
context 'when user has permission' do
before do
alert.project.add_developer(user)
end
it 'creates a todo' do
expect { result }.to change { Todo.count }.by(1)
end
it 'returns the alert and todo in the payload', :aggregate_failures do
expect(result.success?).to be(true)
expect(result.payload[:alert][:id]).to be(alert.id)
expect(result.payload[:todo][:id]).to be(Todo.last.id)
end
context 'when the user has a marked todo for the alert' do
let_it_be(:todo_params) do
{ project: alert.project,
target: alert,
user: user,
action: Todo::MARKED }
end
context 'when todo is pending' do
before_all do
create(:todo, :pending, **todo_params)
end
it 'does not create a todo' do
expect { result }.not_to change { Todo.count }
end
it 'returns an error', :aggregate_failures do
expect(result.error?).to be(true)
expect(result.message).to be('You already have pending todo for this alert')
expect(result.payload[:todo]).to be(nil)
expect(result.payload[:alert]).to be(alert)
end
end
context 'when todo is done' do
before do
create(:todo, :done, **todo_params)
end
it { expect(result.success?).to be(true) }
it { expect { result }.to change { Todo.count }.by(1) }
end
end
end
end
end
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