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 {
<div class="issuable-sidebar js-issuable-update">
<sidebar-header
:sidebar-collapsed="sidebarStatus"
:project-path="projectPath"
:alert="alert"
@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
:project-path="projectPath"
:alert="alert"
......
......@@ -167,7 +167,7 @@ export default {
if (errors[0]) {
this.$emit(
'alert-sidebar-error',
'alert-error',
`${this.$options.i18n.UPDATE_ALERT_ASSIGNEES_GRAPHQL_ERROR} ${errors[0]}.`,
);
}
......
......@@ -8,6 +8,14 @@ export default {
SidebarTodo,
},
props: {
alert: {
type: Object,
required: true,
},
projectPath: {
type: String,
required: true,
},
sidebarCollapsed: {
type: Boolean,
required: true,
......@@ -17,18 +25,17 @@ export default {
</script>
<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">
{{ __('Quick actions') }}
{{ __('To Do') }}
</span>
<toggle-sidebar
:collapsed="sidebarCollapsed"
css-classes="ml-auto"
@toggle="$emit('toggle-sidebar')"
<sidebar-todo
v-if="!sidebarCollapsed"
:project-path="projectPath"
: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 -->
<template v-if="false">
<sidebar-todo v-if="!sidebarCollapsed" :sidebar-collapsed="sidebarCollapsed" />
</template>
<toggle-sidebar :collapsed="sidebarCollapsed" @toggle="$emit('toggle-sidebar')" />
</div>
</template>
<script>
import { s__ } from '~/locale';
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 {
i18n: {
UPDATE_ALERT_TODO_ERROR: s__(
'AlertManagement|There was an error while updating the To Do of the alert.',
),
},
components: {
Todo,
},
props: {
alert: {
type: Object,
required: true,
},
projectPath: {
type: String,
required: true,
},
sidebarCollapsed: {
type: Boolean,
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>
<!-- TODO: Implement after or as part of: https://gitlab.com/gitlab-org/gitlab/-/issues/215946 -->
<template>
<div v-if="false" :class="{ 'block todo': sidebarCollapsed }">
<div :class="{ 'block todo': sidebarCollapsed, 'gl-ml-auto': !sidebarCollapsed }">
<todo
data-testid="alert-todo-button"
:collapsed="sidebarCollapsed"
:issuable-id="1"
:is-todo="false"
:is-action-active="false"
:issuable-id="alertID"
:is-todo="todo !== ''"
:is-action-active="isUpdating"
issuable-type="alert"
@toggleTodo="() => {}"
@toggleTodo="toggleTodo"
/>
</div>
</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';
*/
export default function initTodoToggle() {
$(document).on('todo:toggle', (e, count) => {
const updatedCount = count || e?.detail?.count || 0;
const $todoPendingCount = $('.todos-count');
$todoPendingCount.text(highCountTrim(count));
$todoPendingCount.toggleClass('hidden', count === 0);
$todoPendingCount.text(highCountTrim(updatedCount));
$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
null: true,
description: "The alert after mutation"
field :todo,
Types::TodoType,
null: true,
description: "The todo after mutation"
field :issue,
Types::IssueType,
null: true,
......
......@@ -10,6 +10,7 @@ module Types
mount_mutation Mutations::AlertManagement::CreateAlertIssue
mount_mutation Mutations::AlertManagement::UpdateAlertStatus
mount_mutation Mutations::AlertManagement::Alerts::SetAssignees
mount_mutation Mutations::AlertManagement::Alerts::Todo::Create
mount_mutation Mutations::AwardEmojis::Add
mount_mutation Mutations::AwardEmojis::Remove
mount_mutation Mutations::AwardEmojis::Toggle
......
......@@ -6,6 +6,7 @@ module Types
value 'ISSUE', value: 'Issue', description: 'An Issue'
value 'MERGEREQUEST', value: 'MergeRequest', description: 'A MergeRequest'
value 'DESIGN', value: 'DesignManagement::Design', description: 'A Design'
value 'ALERT', value: 'AlertManagement::Alert', description: 'An Alert'
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
create_assignment_todo(alert, current_user, [])
end
# When user marks an issue as todo
def mark_todo(issuable, current_user)
attributes = attributes_for_todo(issuable.project, issuable, current_user, Todo::MARKED)
# When user marks a target as todo
def mark_todo(target, current_user)
attributes = attributes_for_todo(target.project, target, current_user, Todo::MARKED)
create_todos(current_user, attributes)
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 {
The issue created after mutation
"""
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 {
The issue created after mutation
"""
issue: Issue
"""
The todo after mutation
"""
todo: Todo
}
"""
......@@ -8054,6 +8114,7 @@ type Mutation {
addProjectToSecurityDashboard(input: AddProjectToSecurityDashboardInput!): AddProjectToSecurityDashboardPayload
adminSidekiqQueuesDeleteJobs(input: AdminSidekiqQueuesDeleteJobsInput!): AdminSidekiqQueuesDeleteJobsPayload
alertSetAssignees(input: AlertSetAssigneesInput!): AlertSetAssigneesPayload
alertTodoCreate(input: AlertTodoCreateInput!): AlertTodoCreatePayload
awardEmojiAdd(input: AwardEmojiAddInput!): AwardEmojiAddPayload
awardEmojiRemove(input: AwardEmojiRemoveInput!): AwardEmojiRemovePayload
awardEmojiToggle(input: AwardEmojiToggleInput!): AwardEmojiTogglePayload
......@@ -13268,6 +13329,11 @@ enum TodoStateEnum {
}
enum TodoTargetEnum {
"""
An Alert
"""
ALERT
"""
A Commit
"""
......@@ -13589,6 +13655,11 @@ type UpdateAlertStatusPayload {
The issue created after mutation
"""
issue: Issue
"""
The todo after mutation
"""
todo: Todo
}
"""
......
......@@ -1460,6 +1460,164 @@
},
"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,
"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,
......@@ -4177,6 +4335,20 @@
},
"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,
......@@ -22674,6 +22846,33 @@
"isDeprecated": false,
"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",
"description": null,
......@@ -39273,6 +39472,12 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "ALERT",
"description": "An Alert",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "EPIC",
"description": "An Epic",
......@@ -40159,6 +40364,20 @@
},
"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,
......@@ -101,6 +101,19 @@ Autogenerated return type of AlertSetAssignees
| `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 |
## 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
......@@ -274,6 +287,7 @@ Autogenerated return type of CreateAlertIssue
| `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 |
## CreateAnnotationPayload
......@@ -2049,6 +2063,7 @@ Autogenerated return type of UpdateAlertStatus
| `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 |
## UpdateContainerExpirationPolicyPayload
......
......@@ -2060,6 +2060,9 @@ msgstr ""
msgid "AlertManagement|Overview"
msgstr ""
msgid "AlertManagement|Please try again."
msgstr ""
msgid "AlertManagement|Reported %{when}"
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."
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."
msgstr ""
......@@ -19131,9 +19137,6 @@ msgstr ""
msgid "Queued"
msgstr ""
msgid "Quick actions"
msgstr ""
msgid "Quick actions can be used in the issues description and comment boxes."
msgstr ""
......@@ -26720,6 +26723,9 @@ msgstr ""
msgid "You"
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."
msgstr ""
......@@ -26999,6 +27005,9 @@ msgstr ""
msgid "You have imported from this project %{numberOfPreviousImports} times before. Each new import will create duplicate issues."
msgstr ""
msgid "You have insufficient permissions to create a Todo for this alert"
msgstr ""
msgid "You have no permissions"
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