Commit 7aada820 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent b5ad0617
......@@ -264,7 +264,7 @@ gem 'licensee', '~> 8.9'
gem 'ace-rails-ap', '~> 4.1.0'
# Detect and convert string character encoding
gem 'charlock_holmes', '~> 0.7.5'
gem 'charlock_holmes', '~> 0.7.7'
# Detect mime content type from content
gem 'mimemagic', '~> 0.3.2'
......
......@@ -142,7 +142,7 @@ GEM
mime-types (>= 1.16)
cause (0.1)
character_set (1.1.2)
charlock_holmes (0.7.6)
charlock_holmes (0.7.7)
childprocess (0.9.0)
ffi (~> 1.0, >= 1.0.11)
chronic (0.10.2)
......@@ -1143,7 +1143,7 @@ DEPENDENCIES
capybara (~> 3.22.0)
capybara-screenshot (~> 1.0.22)
carrierwave (~> 1.3)
charlock_holmes (~> 0.7.5)
charlock_holmes (~> 0.7.7)
chronic (~> 0.10.2)
commonmarker (~> 0.20)
concurrent-ruby (~> 1.1)
......
......@@ -93,7 +93,7 @@ class List {
entityType = 'milestone_id';
}
return gl.boardService
return boardsStore
.createList(entity.id, entityType)
.then(res => res.data)
.then(data => {
......@@ -111,14 +111,14 @@ class List {
boardsStore.state.lists.splice(index, 1);
boardsStore.updateNewListDropdown(this.id);
gl.boardService.destroyList(this.id).catch(() => {
boardsStore.destroyList(this.id).catch(() => {
// TODO: handle request error
});
}
update() {
const collapsed = !this.isExpanded;
return gl.boardService.updateList(this.id, this.position, collapsed).catch(() => {
return boardsStore.updateList(this.id, this.position, collapsed).catch(() => {
// TODO: handle request error
});
}
......@@ -147,7 +147,7 @@ class List {
this.loading = true;
}
return gl.boardService
return boardsStore
.getIssuesForList(this.id, data)
.then(res => res.data)
.then(data => {
......@@ -168,7 +168,7 @@ class List {
this.addIssue(issue, null, 0);
this.issuesSize += 1;
return gl.boardService
return boardsStore
.newIssue(this.id, issue)
.then(res => res.data)
.then(data => this.onNewIssueResponse(issue, data));
......@@ -276,7 +276,7 @@ class List {
this.issues.splice(oldIndex, 1);
this.issues.splice(newIndex, 0, issue);
gl.boardService.moveIssue(issue.id, null, null, moveBeforeId, moveAfterId).catch(() => {
boardsStore.moveIssue(issue.id, null, null, moveBeforeId, moveAfterId).catch(() => {
// TODO: handle request error
});
}
......@@ -287,7 +287,7 @@ class List {
});
this.issues.splice(newIndex, 0, ...issues);
gl.boardService
boardsStore
.moveMultipleIssues({
ids: issues.map(issue => issue.id),
fromListId: null,
......@@ -299,15 +299,13 @@ class List {
}
updateIssueLabel(issue, listFrom, moveBeforeId, moveAfterId) {
gl.boardService
.moveIssue(issue.id, listFrom.id, this.id, moveBeforeId, moveAfterId)
.catch(() => {
boardsStore.moveIssue(issue.id, listFrom.id, this.id, moveBeforeId, moveAfterId).catch(() => {
// TODO: handle request error
});
}
updateMultipleIssues(issues, listFrom, moveBeforeId, moveAfterId) {
gl.boardService
boardsStore
.moveMultipleIssues({
ids: issues.map(issue => issue.id),
fromListId: listFrom.id,
......@@ -359,7 +357,7 @@ class List {
if (this.issuesSize > 1) {
const moveBeforeId = this.issues[1].id;
gl.boardService.moveIssue(issue.id, null, null, null, moveBeforeId);
boardsStore.moveIssue(issue.id, null, null, null, moveBeforeId);
}
}
}
......
......@@ -9,6 +9,12 @@ module Mutations
GitlabSchema.object_from_id(id)
end
def map_to_global_ids(ids)
return [] if ids.blank?
ids.map { |id| to_global_id(id) }
end
def to_global_id(id)
::URI::GID.build(app: GlobalID.app, model_name: Todo.name, model_id: id, params: nil).to_s
end
......
# frozen_string_literal: true
module Mutations
module Todos
class MarkAllDone < ::Mutations::Todos::Base
graphql_name 'TodosMarkAllDone'
authorize :update_user
field :updated_ids,
[GraphQL::ID_TYPE],
null: false,
description: 'Ids of the updated todos'
def resolve
authorize!(current_user)
updated_ids = mark_all_todos_done
{
updated_ids: map_to_global_ids(updated_ids),
errors: []
}
end
private
def mark_all_todos_done
return [] unless current_user
TodoService.new.mark_all_todos_as_done_by_user(current_user)
end
end
end
end
......@@ -22,6 +22,7 @@ module Types
mount_mutation Mutations::Notes::Destroy
mount_mutation Mutations::Todos::MarkDone
mount_mutation Mutations::Todos::Restore
mount_mutation Mutations::Todos::MarkAllDone
end
end
......
......@@ -174,6 +174,11 @@ class TodoService
mark_todos_as_done(todos, current_user)
end
def mark_all_todos_as_done_by_user(current_user)
todos = TodosFinder.new(current_user).execute
mark_todos_as_done(todos, current_user)
end
# When user marks some todos as pending
def mark_todos_as_pending(todos, current_user)
update_todos_state(todos, current_user, :pending)
......
---
title: Add GraphQL mutation to mark all todos done for a user
merge_request: 19482
author:
type: added
---
title: Removes references of BoardService in list file
merge_request: 20145
author: nuwe1
type: other
......@@ -3520,6 +3520,7 @@ type Mutation {
removeAwardEmoji(input: RemoveAwardEmojiInput!): RemoveAwardEmojiPayload
todoMarkDone(input: TodoMarkDoneInput!): TodoMarkDonePayload
todoRestore(input: TodoRestoreInput!): TodoRestorePayload
todosMarkAllDone(input: TodosMarkAllDoneInput!): TodosMarkAllDonePayload
toggleAwardEmoji(input: ToggleAwardEmojiInput!): ToggleAwardEmojiPayload
updateEpic(input: UpdateEpicInput!): UpdateEpicPayload
updateNote(input: UpdateNoteInput!): UpdateNotePayload
......@@ -5060,6 +5061,36 @@ enum TodoTargetEnum {
MERGEREQUEST
}
"""
Autogenerated input type of TodosMarkAllDone
"""
input TodosMarkAllDoneInput {
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
}
"""
Autogenerated return type of TodosMarkAllDone
"""
type TodosMarkAllDonePayload {
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
Reasons why the mutation failed.
"""
errors: [String!]!
"""
Ids of the updated todos
"""
updatedIds: [ID!]!
}
"""
Autogenerated input type of ToggleAwardEmoji
"""
......
......@@ -14355,6 +14355,33 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "todosMarkAllDone",
"description": null,
"args": [
{
"name": "input",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "INPUT_OBJECT",
"name": "TodosMarkAllDoneInput",
"ofType": null
}
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "TodosMarkAllDonePayload",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "toggleAwardEmoji",
"description": null,
......@@ -16825,6 +16852,106 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "TodosMarkAllDonePayload",
"description": "Autogenerated return type of TodosMarkAllDone",
"fields": [
{
"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": "Reasons why the mutation failed.",
"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": "updatedIds",
"description": "Ids of the updated todos",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
}
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "INPUT_OBJECT",
"name": "TodosMarkAllDoneInput",
"description": "Autogenerated input type of TodosMarkAllDone",
"fields": null,
"inputFields": [
{
"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": "DesignManagementUploadPayload",
......
......@@ -785,6 +785,14 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph
| `errors` | String! => Array | Reasons why the mutation failed. |
| `todo` | Todo! | The requested todo |
### TodosMarkAllDonePayload
| Name | Type | Description |
| --- | ---- | ---------- |
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `errors` | String! => Array | Reasons why the mutation failed. |
| `updatedIds` | ID! => Array | Ids of the updated todos |
### ToggleAwardEmojiPayload
| Name | Type | Description |
......
......@@ -1539,9 +1539,14 @@ cache:
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/18986) in GitLab v12.5.
If `cache:key:files` is added, one or two files must be defined with it. The cache `key`
will be a SHA computed from the most recent commits (one or two) that changed the
given files. If neither file was changed in any commits, the key will be `default`.
The `cache:key:files` keyword extends the `cache:key` functionality by making it easier
to reuse some caches, and rebuild them less often, which will speed up subsequent pipeline
runs.
When you include `cache:key:files`, you must also list the project files that will be used to generate the key, up to a maximum of two files.
The cache `key` will be a SHA checksum computed from the most recent commits (up to two, if two files are listed)
that changed the given files. If neither file was changed in any commits,
the fallback key will be `default`.
```yaml
cache:
......@@ -1554,20 +1559,26 @@ cache:
- node_modules
```
In this example we are creating a cache for Ruby and Nodejs dependencies that
is tied to current versions of the `Gemfile.lock` and `package.json` files. Whenever one of
these files changes, a new cache key is computed and a new cache is created. Any future
job runs using the same `Gemfile.lock` and `package.json` with `cache:key:files` will
use the new cache, instead of rebuilding the dependencies.
##### `cache:key:prefix`
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/18986) in GitLab v12.5.
The `prefix` parameter adds extra functionality to `key:files` by allowing the key to
be composed of the given `prefix` combined with the SHA computed for `cache:key:files`.
For example, adding a `prefix` of `rspec`, will
cause keys to look like: `rspec-feef9576d21ee9b6a32e30c5c79d0a0ceb68d1e5`. If neither
file was changed in any commits, the prefix is added to `default`, so the key in the
example would be `rspec-default`.
For example, adding a `prefix` of `test`, will cause keys to look like: `test-feef9576d21ee9b6a32e30c5c79d0a0ceb68d1e5`.
If neither file was changed in any commits, the prefix is added to `default`, so the
key in the example would be `test-default`.
`prefix` follows the same restrictions as `key`, so it can use any of the
[predefined variables](../variables/README.md). Similarly, the `/` character or the
equivalent URI-encoded `%2F`, or a value made only of `.` or `%2E`, is not allowed.
Like `cache:key`, `prefix` can use any of the [predefined variables](../variables/README.md),
but the following are not allowed:
- the `/` character (or the equivalent URI-encoded `%2F`)
- a value made only of `.` (or the equivalent URI-encoded `%2E`)
```yaml
cache:
......@@ -1577,8 +1588,20 @@ cache:
prefix: ${CI_JOB_NAME}
paths:
- vendor/ruby
rspec:
script:
- bundle exec rspec
```
For example, adding a `prefix` of `$CI_JOB_NAME` will
cause the key to look like: `rspec-feef9576d21ee9b6a32e30c5c79d0a0ceb68d1e5` and
the job cache is shared across different branches. If a branch changes
`Gemfile.lock`, that branch will have a new SHA checksum for `cache:key:files`. A new cache key
will be generated, and a new cache will be created for that key.
If `Gemfile.lock` is not found, the prefix is added to
`default`, so the key in the example would be `rspec-default`.
#### `cache:untracked`
Set `untracked: true` to cache all files that are untracked in your Git
......
......@@ -7,7 +7,7 @@ module Quality
class HelmClient
CommandFailedError = Class.new(StandardError)
attr_reader :namespace
attr_reader :tiller_namespace, :namespace
RELEASE_JSON_ATTRIBUTES = %w[Name Revision Updated Status Chart AppVersion Namespace].freeze
......@@ -24,7 +24,8 @@ module Quality
# A single page of data and the corresponding page number.
Page = Struct.new(:releases, :number)
def initialize(namespace:)
def initialize(tiller_namespace:, namespace:)
@tiller_namespace = tiller_namespace
@namespace = namespace
end
......@@ -35,7 +36,7 @@ module Quality
def delete(release_name:)
run_command([
'delete',
%(--tiller-namespace "#{namespace}"),
%(--tiller-namespace "#{tiller_namespace}"),
'--purge',
release_name
])
......@@ -60,7 +61,7 @@ module Quality
command = [
'list',
%(--namespace "#{namespace}"),
%(--tiller-namespace "#{namespace}" --output json),
%(--tiller-namespace "#{tiller_namespace}" --output json),
*args
]
json = JSON.parse(run_command(command))
......
......@@ -25,7 +25,6 @@ class AutomatedCleanup
def initialize(project_path: ENV['CI_PROJECT_PATH'], gitlab_token: ENV['GITLAB_BOT_REVIEW_APPS_CLEANUP_TOKEN'])
@project_path = project_path
@gitlab_token = gitlab_token
ENV['TILLER_NAMESPACE'] ||= review_apps_namespace
end
def gitlab
......@@ -45,7 +44,9 @@ class AutomatedCleanup
end
def helm
@helm ||= Quality::HelmClient.new(namespace: review_apps_namespace)
@helm ||= Quality::HelmClient.new(
tiller_namespace: review_apps_namespace,
namespace: review_apps_namespace)
end
def kubernetes
......
This diff is collapsed.
# frozen_string_literal: true
require 'spec_helper'
describe Mutations::Todos::MarkAllDone do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
let_it_be(:author) { create(:user) }
let_it_be(:other_user) { create(:user) }
let_it_be(:todo1) { create(:todo, user: current_user, author: author, state: :pending) }
let_it_be(:todo2) { create(:todo, user: current_user, author: author, state: :done) }
let_it_be(:todo3) { create(:todo, user: current_user, author: author, state: :pending) }
let_it_be(:other_user_todo) { create(:todo, user: other_user, author: author, state: :pending) }
let_it_be(:user3) { create(:user) }
describe '#resolve' do
it 'marks all pending todos as done' do
updated_todo_ids = mutation_for(current_user).resolve.dig(:updated_ids)
expect(todo1.reload.state).to eq('done')
expect(todo2.reload.state).to eq('done')
expect(todo3.reload.state).to eq('done')
expect(other_user_todo.reload.state).to eq('pending')
expect(updated_todo_ids).to contain_exactly(global_id_of(todo1), global_id_of(todo3))
end
it 'behaves as expected if there are no todos for the requesting user' do
updated_todo_ids = mutation_for(user3).resolve.dig(:updated_ids)
expect(todo1.reload.state).to eq('pending')
expect(todo2.reload.state).to eq('done')
expect(todo3.reload.state).to eq('pending')
expect(other_user_todo.reload.state).to eq('pending')
expect(updated_todo_ids).to be_empty
end
context 'when user is not logged in' do
it 'fails with the expected error' do
expect { mutation_for(nil).resolve }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
end
def mutation_for(user)
described_class.new(object: nil, context: { current_user: user })
end
end
......@@ -3,6 +3,8 @@
require 'spec_helper'
describe Mutations::Todos::MarkDone do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
let_it_be(:author) { create(:user) }
let_it_be(:other_user) { create(:user) }
......@@ -59,8 +61,4 @@ describe Mutations::Todos::MarkDone do
def mark_done_mutation(todo)
mutation.resolve(id: global_id_of(todo))
end
def global_id_of(todo)
todo.to_global_id.to_s
end
end
......@@ -11,7 +11,7 @@ import '~/boards/models/list';
import '~/boards/services/board_service';
import boardsStore from '~/boards/stores/boards_store';
import eventHub from '~/boards/eventhub';
import { listObj, listObjDuplicate, boardsMockInterceptor, mockBoardService } from './mock_data';
import { listObj, listObjDuplicate, boardsMockInterceptor } from './mock_data';
import waitForPromises from '../../frontend/helpers/wait_for_promises';
describe('Store', () => {
......@@ -20,17 +20,16 @@ describe('Store', () => {
beforeEach(() => {
mock = new MockAdapter(axios);
mock.onAny().reply(boardsMockInterceptor);
gl.boardService = mockBoardService();
boardsStore.create();
spyOn(gl.boardService, 'moveIssue').and.callFake(
spyOn(boardsStore, 'moveIssue').and.callFake(
() =>
new Promise(resolve => {
resolve();
}),
);
spyOn(gl.boardService, 'moveMultipleIssues').and.callFake(
spyOn(boardsStore, 'moveMultipleIssues').and.callFake(
() =>
new Promise(resolve => {
resolve();
......@@ -263,7 +262,7 @@ describe('Store', () => {
expect(listOne.issues.length).toBe(0);
expect(listTwo.issues.length).toBe(2);
expect(listTwo.issues[0].id).toBe(2);
expect(gl.boardService.moveIssue).toHaveBeenCalledWith(2, listOne.id, listTwo.id, null, 1);
expect(boardsStore.moveIssue).toHaveBeenCalledWith(2, listOne.id, listTwo.id, null, 1);
done();
}, 0);
......@@ -286,7 +285,7 @@ describe('Store', () => {
expect(listOne.issues.length).toBe(0);
expect(listTwo.issues.length).toBe(2);
expect(listTwo.issues[1].id).toBe(2);
expect(gl.boardService.moveIssue).toHaveBeenCalledWith(2, listOne.id, listTwo.id, 1, null);
expect(boardsStore.moveIssue).toHaveBeenCalledWith(2, listOne.id, listTwo.id, 1, null);
done();
}, 0);
......@@ -311,7 +310,7 @@ describe('Store', () => {
boardsStore.moveIssueInList(list, issue, 0, 1, [1, 2]);
expect(list.issues[0].id).toBe(2);
expect(gl.boardService.moveIssue).toHaveBeenCalledWith(2, null, null, 1, null);
expect(boardsStore.moveIssue).toHaveBeenCalledWith(2, null, null, 1, null);
done();
});
......@@ -495,7 +494,7 @@ describe('Store', () => {
expect(list.issues[0].id).toBe(issue1.id);
expect(gl.boardService.moveMultipleIssues).toHaveBeenCalledWith({
expect(boardsStore.moveMultipleIssues).toHaveBeenCalledWith({
ids: [issue1.id, issue2.id],
fromListId: null,
toListId: null,
......
......@@ -12,7 +12,7 @@ import '~/boards/models/issue';
import '~/boards/models/list';
import '~/boards/services/board_service';
import boardsStore from '~/boards/stores/boards_store';
import { listObj, listObjDuplicate, boardsMockInterceptor, mockBoardService } from './mock_data';
import { listObj, listObjDuplicate, boardsMockInterceptor } from './mock_data';
describe('List model', () => {
let list;
......@@ -21,9 +21,6 @@ describe('List model', () => {
beforeEach(() => {
mock = new MockAdapter(axios);
mock.onAny().reply(boardsMockInterceptor);
gl.boardService = mockBoardService({
bulkUpdatePath: '/test/issue-boards/board/1/lists',
});
boardsStore.create();
list = new List(listObj);
......@@ -110,11 +107,11 @@ describe('List model', () => {
list.issues.push(issue);
listDup.issues.push(issue);
spyOn(gl.boardService, 'moveIssue').and.callThrough();
spyOn(boardsStore, 'moveIssue').and.callThrough();
listDup.updateIssueLabel(issue, list);
expect(gl.boardService.moveIssue).toHaveBeenCalledWith(
expect(boardsStore.moveIssue).toHaveBeenCalledWith(
issue.id,
list.id,
listDup.id,
......@@ -172,7 +169,7 @@ describe('List model', () => {
describe('newIssue', () => {
beforeEach(() => {
spyOn(gl.boardService, 'newIssue').and.returnValue(
spyOn(boardsStore, 'newIssue').and.returnValue(
Promise.resolve({
data: {
id: 42,
......
......@@ -3,7 +3,8 @@
require 'fast_spec_helper'
RSpec.describe Quality::HelmClient do
let(:namespace) { 'review-apps-ee' }
let(:tiller_namespace) { 'review-apps-ee' }
let(:namespace) { tiller_namespace }
let(:release_name) { 'my-release' }
let(:raw_helm_list_page1) do
<<~OUTPUT
......@@ -30,12 +31,12 @@ RSpec.describe Quality::HelmClient do
OUTPUT
end
subject { described_class.new(namespace: namespace) }
subject { described_class.new(tiller_namespace: tiller_namespace, namespace: namespace) }
describe '#releases' do
it 'raises an error if the Helm command fails' do
expect(Gitlab::Popen).to receive(:popen_with_detail)
.with([%(helm list --namespace "#{namespace}" --tiller-namespace "#{namespace}" --output json)])
.with([%(helm list --namespace "#{namespace}" --tiller-namespace "#{tiller_namespace}" --output json)])
.and_return(Gitlab::Popen::Result.new([], '', '', double(success?: false)))
expect { subject.releases.to_a }.to raise_error(described_class::CommandFailedError)
......@@ -43,7 +44,7 @@ RSpec.describe Quality::HelmClient do
it 'calls helm list with default arguments' do
expect(Gitlab::Popen).to receive(:popen_with_detail)
.with([%(helm list --namespace "#{namespace}" --tiller-namespace "#{namespace}" --output json)])
.with([%(helm list --namespace "#{namespace}" --tiller-namespace "#{tiller_namespace}" --output json)])
.and_return(Gitlab::Popen::Result.new([], '', '', double(success?: true)))
subject.releases.to_a
......@@ -51,7 +52,7 @@ RSpec.describe Quality::HelmClient do
it 'calls helm list with extra arguments' do
expect(Gitlab::Popen).to receive(:popen_with_detail)
.with([%(helm list --namespace "#{namespace}" --tiller-namespace "#{namespace}" --output json --deployed)])
.with([%(helm list --namespace "#{namespace}" --tiller-namespace "#{tiller_namespace}" --output json --deployed)])
.and_return(Gitlab::Popen::Result.new([], '', '', double(success?: true)))
subject.releases(args: ['--deployed']).to_a
......@@ -59,7 +60,7 @@ RSpec.describe Quality::HelmClient do
it 'returns a list of Release objects' do
expect(Gitlab::Popen).to receive(:popen_with_detail)
.with([%(helm list --namespace "#{namespace}" --tiller-namespace "#{namespace}" --output json --deployed)])
.with([%(helm list --namespace "#{namespace}" --tiller-namespace "#{tiller_namespace}" --output json --deployed)])
.and_return(Gitlab::Popen::Result.new([], raw_helm_list_page2, '', double(success?: true)))
releases = subject.releases(args: ['--deployed']).to_a
......@@ -78,10 +79,10 @@ RSpec.describe Quality::HelmClient do
it 'automatically paginates releases' do
expect(Gitlab::Popen).to receive(:popen_with_detail).ordered
.with([%(helm list --namespace "#{namespace}" --tiller-namespace "#{namespace}" --output json)])
.with([%(helm list --namespace "#{namespace}" --tiller-namespace "#{tiller_namespace}" --output json)])
.and_return(Gitlab::Popen::Result.new([], raw_helm_list_page1, '', double(success?: true)))
expect(Gitlab::Popen).to receive(:popen_with_detail).ordered
.with([%(helm list --namespace "#{namespace}" --tiller-namespace "#{namespace}" --output json --offset review-6709-group-t40qbv)])
.with([%(helm list --namespace "#{namespace}" --tiller-namespace "#{tiller_namespace}" --output json --offset review-6709-group-t40qbv)])
.and_return(Gitlab::Popen::Result.new([], raw_helm_list_page2, '', double(success?: true)))
releases = subject.releases.to_a
......@@ -94,7 +95,7 @@ RSpec.describe Quality::HelmClient do
describe '#delete' do
it 'raises an error if the Helm command fails' do
expect(Gitlab::Popen).to receive(:popen_with_detail)
.with([%(helm delete --tiller-namespace "#{namespace}" --purge #{release_name})])
.with([%(helm delete --tiller-namespace "#{tiller_namespace}" --purge #{release_name})])
.and_return(Gitlab::Popen::Result.new([], '', '', double(success?: false)))
expect { subject.delete(release_name: release_name) }.to raise_error(described_class::CommandFailedError)
......@@ -102,7 +103,7 @@ RSpec.describe Quality::HelmClient do
it 'calls helm delete with default arguments' do
expect(Gitlab::Popen).to receive(:popen_with_detail)
.with([%(helm delete --tiller-namespace "#{namespace}" --purge #{release_name})])
.with([%(helm delete --tiller-namespace "#{tiller_namespace}" --purge #{release_name})])
.and_return(Gitlab::Popen::Result.new([], '', '', double(success?: true)))
expect(subject.delete(release_name: release_name)).to eq('')
......@@ -113,7 +114,7 @@ RSpec.describe Quality::HelmClient do
it 'raises an error if the Helm command fails' do
expect(Gitlab::Popen).to receive(:popen_with_detail)
.with([%(helm delete --tiller-namespace "#{namespace}" --purge #{release_name.join(' ')})])
.with([%(helm delete --tiller-namespace "#{tiller_namespace}" --purge #{release_name.join(' ')})])
.and_return(Gitlab::Popen::Result.new([], '', '', double(success?: false)))
expect { subject.delete(release_name: release_name) }.to raise_error(described_class::CommandFailedError)
......@@ -121,7 +122,7 @@ RSpec.describe Quality::HelmClient do
it 'calls helm delete with multiple release names' do
expect(Gitlab::Popen).to receive(:popen_with_detail)
.with([%(helm delete --tiller-namespace "#{namespace}" --purge #{release_name.join(' ')})])
.with([%(helm delete --tiller-namespace "#{tiller_namespace}" --purge #{release_name.join(' ')})])
.and_return(Gitlab::Popen::Result.new([], '', '', double(success?: true)))
expect(subject.delete(release_name: release_name)).to eq('')
......
# frozen_string_literal: true
require 'spec_helper'
describe 'Marking all todos done' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
let_it_be(:author) { create(:user) }
let_it_be(:other_user) { create(:user) }
let_it_be(:other_user2) { create(:user) }
let_it_be(:todo1) { create(:todo, user: current_user, author: author, state: :pending) }
let_it_be(:todo2) { create(:todo, user: current_user, author: author, state: :done) }
let_it_be(:todo3) { create(:todo, user: current_user, author: author, state: :pending) }
let_it_be(:other_user_todo) { create(:todo, user: other_user, author: author, state: :pending) }
let(:input) { {} }
let(:mutation) do
graphql_mutation(:todos_mark_all_done, input,
<<-QL.strip_heredoc
clientMutationId
errors
updatedIds
QL
)
end
def mutation_response
graphql_mutation_response(:todos_mark_all_done)
end
it 'marks all pending todos as done' do
post_graphql_mutation(mutation, current_user: current_user)
expect(todo1.reload.state).to eq('done')
expect(todo2.reload.state).to eq('done')
expect(todo3.reload.state).to eq('done')
expect(other_user_todo.reload.state).to eq('pending')
updated_todo_ids = mutation_response['updatedIds']
expect(updated_todo_ids).to contain_exactly(global_id_of(todo1), global_id_of(todo3))
end
it 'behaves as expected if there are no todos for the requesting user' do
post_graphql_mutation(mutation, current_user: other_user2)
expect(todo1.reload.state).to eq('pending')
expect(todo2.reload.state).to eq('done')
expect(todo3.reload.state).to eq('pending')
expect(other_user_todo.reload.state).to eq('pending')
updated_todo_ids = mutation_response['updatedIds']
expect(updated_todo_ids).to be_empty
end
context 'when user is not logged in' do
let(:current_user) { nil }
it_behaves_like 'a mutation that returns top-level errors',
errors: ['The resource that you are attempting to access does not exist or you don\'t have permission to perform this action']
end
end
......@@ -1015,6 +1015,21 @@ describe TodoService do
end
end
describe '#mark_all_todos_as_done_by_user' do
it 'marks all todos done' do
todo1 = create(:todo, user: john_doe, state: :pending)
todo2 = create(:todo, user: john_doe, state: :done)
todo3 = create(:todo, user: john_doe, state: :pending)
ids = described_class.new.mark_all_todos_as_done_by_user(john_doe)
expect(ids).to contain_exactly(todo1.id, todo3.id)
expect(todo1.reload.state).to eq('done')
expect(todo2.reload.state).to eq('done')
expect(todo3.reload.state).to eq('done')
end
end
describe '#mark_todos_as_done_by_ids' do
let(:issue) { create(:issue, project: project, author: author, assignees: [john_doe]) }
let(:another_issue) { create(:issue, project: project, author: author, assignees: [john_doe]) }
......
......@@ -297,6 +297,10 @@ module GraphqlHelpers
extract_attribute ? item['node'][extract_attribute] : item['node']
end
end
def global_id_of(model)
model.to_global_id.to_s
end
end
# This warms our schema, doing this as part of loading the helpers to avoid
......
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