diff options
23 files changed, 461 insertions, 207 deletions
diff --git a/app/assets/javascripts/repository/components/breadcrumbs.vue b/app/assets/javascripts/repository/components/breadcrumbs.vue index afb58a60155..f6b9ea5d30d 100644 --- a/app/assets/javascripts/repository/components/breadcrumbs.vue +++ b/app/assets/javascripts/repository/components/breadcrumbs.vue @@ -124,7 +124,7 @@ export default { }, { attrs: { - href: this.newBlobPath, + href: `${this.newBlobPath}${this.currentPath}`, class: 'qa-new-file-option', }, text: __('New file'), diff --git a/app/assets/javascripts/repository/index.js b/app/assets/javascripts/repository/index.js index d826f209815..ae6409a0ac9 100644 --- a/app/assets/javascripts/repository/index.js +++ b/app/assets/javascripts/repository/index.js @@ -7,6 +7,7 @@ import TreeActionLink from './components/tree_action_link.vue'; import DirectoryDownloadLinks from './components/directory_download_links.vue'; import apolloProvider from './graphql'; import { setTitle } from './utils/title'; +import { updateFormAction } from './utils/dom'; import { parseBoolean } from '../lib/utils/common_utils'; import { webIDEUrl } from '../lib/utils/url_utility'; import { __ } from '../locale'; @@ -42,8 +43,15 @@ export default function setupVueRepositoryList() { forkNewBlobPath, forkNewDirectoryPath, forkUploadBlobPath, + uploadPath, + newDirPath, } = breadcrumbEl.dataset; + router.afterEach(({ params: { pathMatch = '/' } }) => { + updateFormAction('.js-upload-blob-form', uploadPath, pathMatch); + updateFormAction('.js-create-dir-form', newDirPath, pathMatch); + }); + // eslint-disable-next-line no-new new Vue({ el: breadcrumbEl, diff --git a/app/assets/javascripts/repository/utils/dom.js b/app/assets/javascripts/repository/utils/dom.js index 963e6fc0bc4..81565a00d82 100644 --- a/app/assets/javascripts/repository/utils/dom.js +++ b/app/assets/javascripts/repository/utils/dom.js @@ -1,4 +1,11 @@ -// eslint-disable-next-line import/prefer-default-export export const updateElementsVisibility = (selector, isVisible) => { document.querySelectorAll(selector).forEach(elem => elem.classList.toggle('hidden', !isVisible)); }; + +export const updateFormAction = (selector, basePath, path) => { + const form = document.querySelector(selector); + + if (form) { + form.action = `${basePath}${path}`; + } +}; diff --git a/app/graphql/mutations/issues/base.rb b/app/graphql/mutations/issues/base.rb new file mode 100644 index 00000000000..b7fa234a50b --- /dev/null +++ b/app/graphql/mutations/issues/base.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module Mutations + module Issues + class Base < BaseMutation + include Mutations::ResolvesProject + + argument :project_path, GraphQL::ID_TYPE, + required: true, + description: "The project the issue to mutate is in" + + argument :iid, GraphQL::STRING_TYPE, + required: true, + description: "The iid of the issue to mutate" + + field :issue, + Types::IssueType, + null: true, + description: "The issue after mutation" + + authorize :update_issue + + private + + def find_object(project_path:, iid:) + project = resolve_project(full_path: project_path) + resolver = Resolvers::IssuesResolver + .single.new(object: project, context: context) + + resolver.resolve(iid: iid) + end + end + end +end diff --git a/app/graphql/mutations/issues/set_due_date.rb b/app/graphql/mutations/issues/set_due_date.rb new file mode 100644 index 00000000000..1855c6f053b --- /dev/null +++ b/app/graphql/mutations/issues/set_due_date.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module Mutations + module Issues + class SetDueDate < Base + graphql_name 'IssueSetDueDate' + + argument :due_date, + Types::TimeType, + required: true, + description: 'The desired due date for the issue' + + def resolve(project_path:, iid:, due_date:) + issue = authorized_find!(project_path: project_path, iid: iid) + project = issue.project + + ::Issues::UpdateService.new(project, current_user, due_date: due_date) + .execute(issue) + + { + issue: issue, + errors: issue.errors.full_messages + } + end + end + end +end diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb index 2408dc7fd1b..ecdbba477d7 100644 --- a/app/graphql/types/mutation_type.rb +++ b/app/graphql/types/mutation_type.rb @@ -9,6 +9,7 @@ module Types mount_mutation Mutations::AwardEmojis::Add mount_mutation Mutations::AwardEmojis::Remove mount_mutation Mutations::AwardEmojis::Toggle + mount_mutation Mutations::Issues::SetDueDate mount_mutation Mutations::MergeRequests::SetLabels mount_mutation Mutations::MergeRequests::SetLocked mount_mutation Mutations::MergeRequests::SetMilestone diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb index fc25b78da93..af1919eeb40 100644 --- a/app/helpers/tree_helper.rb +++ b/app/helpers/tree_helper.rb @@ -158,7 +158,9 @@ module TreeHelper def breadcrumb_data_attributes attrs = { can_collaborate: can_collaborate_with_project?(@project).to_s, - new_blob_path: project_new_blob_path(@project, @id), + new_blob_path: project_new_blob_path(@project, @ref), + upload_path: project_create_blob_path(@project, @ref), + new_dir_path: project_create_dir_path(@project, @ref), new_branch_path: new_project_branch_path(@project), new_tag_path: new_project_tag_path(@project), can_edit_tree: can_edit_tree?.to_s diff --git a/changelogs/unreleased/36313-graphql-mutation-for-changing-due-date-of-an-issue.yml b/changelogs/unreleased/36313-graphql-mutation-for-changing-due-date-of-an-issue.yml new file mode 100644 index 00000000000..f63605bd593 --- /dev/null +++ b/changelogs/unreleased/36313-graphql-mutation-for-changing-due-date-of-an-issue.yml @@ -0,0 +1,5 @@ +--- +title: Add GraphQL mutation for changing due date of an issue +merge_request: 20577 +author: +type: added diff --git a/changelogs/unreleased/37033-auto-devops-suppress-progress-on-pulling-docker-base-image-to-be-ru.yml b/changelogs/unreleased/37033-auto-devops-suppress-progress-on-pulling-docker-base-image-to-be-ru.yml new file mode 100644 index 00000000000..dbaa3ec44be --- /dev/null +++ b/changelogs/unreleased/37033-auto-devops-suppress-progress-on-pulling-docker-base-image-to-be-ru.yml @@ -0,0 +1,5 @@ +--- +title: Suppress progress on pulling image on Code Quality of Auto DevOps +merge_request: 20604 +author: Takuya Noguchi +type: other diff --git a/changelogs/unreleased/new-33257-prevent-accidental-deletions-via-soft-delete-for-groups-db-chan.yml b/changelogs/unreleased/new-33257-prevent-accidental-deletions-via-soft-delete-for-groups-db-chan.yml new file mode 100644 index 00000000000..6b300dd53f1 --- /dev/null +++ b/changelogs/unreleased/new-33257-prevent-accidental-deletions-via-soft-delete-for-groups-db-chan.yml @@ -0,0 +1,5 @@ +--- +title: Add migrations for 'soft-delete for groups' feature +merge_request: 20276 +author: +type: added diff --git a/db/migrate/20191118053631_add_group_deletion_schedules.rb b/db/migrate/20191118053631_add_group_deletion_schedules.rb new file mode 100644 index 00000000000..6f3ed27e156 --- /dev/null +++ b/db/migrate/20191118053631_add_group_deletion_schedules.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +class AddGroupDeletionSchedules < ActiveRecord::Migration[5.2] + DOWNTIME = false + + def up + create_table :group_deletion_schedules, id: false do |t| + t.references :group, + foreign_key: { on_delete: :cascade, to_table: :namespaces }, + default: nil, + index: false, + primary_key: true + + t.references :user, + index: true, + foreign_key: { on_delete: :nullify }, + null: false + + t.date :marked_for_deletion_on, + index: true, + null: false + end + end + + def down + drop_table :group_deletion_schedules + end +end diff --git a/db/schema.rb b/db/schema.rb index b31ed03a34f..8c8b48f0bbe 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -1892,6 +1892,13 @@ ActiveRecord::Schema.define(version: 2019_11_19_023952) do t.index ["key", "value"], name: "index_group_custom_attributes_on_key_and_value" end + create_table "group_deletion_schedules", primary_key: "group_id", id: :bigint, default: nil, force: :cascade do |t| + t.bigint "user_id", null: false + t.date "marked_for_deletion_on", null: false + t.index ["marked_for_deletion_on"], name: "index_group_deletion_schedules_on_marked_for_deletion_on" + t.index ["user_id"], name: "index_group_deletion_schedules_on_user_id" + end + create_table "group_group_links", force: :cascade do |t| t.datetime_with_timezone "created_at", null: false t.datetime_with_timezone "updated_at", null: false @@ -4413,6 +4420,8 @@ ActiveRecord::Schema.define(version: 2019_11_19_023952) do add_foreign_key "gpg_signatures", "projects", on_delete: :cascade add_foreign_key "grafana_integrations", "projects", on_delete: :cascade add_foreign_key "group_custom_attributes", "namespaces", column: "group_id", on_delete: :cascade + add_foreign_key "group_deletion_schedules", "namespaces", column: "group_id", on_delete: :cascade + add_foreign_key "group_deletion_schedules", "users", on_delete: :nullify add_foreign_key "group_group_links", "namespaces", column: "shared_group_id", on_delete: :cascade add_foreign_key "group_group_links", "namespaces", column: "shared_with_group_id", on_delete: :cascade add_foreign_key "identities", "saml_providers", name: "fk_aade90f0fc", on_delete: :cascade diff --git a/doc/README.md b/doc/README.md index c8438f5fb8b..fb343ae91bb 100644 --- a/doc/README.md +++ b/doc/README.md @@ -23,7 +23,7 @@ No matter how you use GitLab, we have documentation for you. | Essential Documentation | Essential Documentation | |:-------------------------------------------------------------------------------------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------| | [**User Documentation**](user/index.md)<br/>Discover features and concepts for GitLab users. | [**Administrator documentation**](administration/index.md)<br/>Everything GitLab self-managed administrators need to know. | -| [**Contributing to GitLab**](#contributing-to-gitlab)<br/>At GitLab, everyone can contribute! | [**New to Git and GitLab?**](#new-to-git-and-gitlab)<br/>We have resources to get you started. | +| [**Contributing to GitLab**](#contributing-to-gitlab)<br/>At GitLab, everyone can contribute! | [**New to Git and GitLab?**](#new-to-git-and-gitlab)<br/>We have the resources to get you started. | | [**Building an integration with GitLab?**](#building-an-integration-with-gitlab)<br/>Consult our automation and integration documentation. | [**Coming to GitLab from another platform?**](#coming-to-gitlab-from-another-platform)<br/>Consult our handy guides. | | [**Install GitLab**](https://about.gitlab.com/install/)<br/>Installation options for different platforms. | [**Customers**](subscriptions/index.md)<br/>Information for new and existing customers. | | [**Update GitLab**](update/README.md)<br/>Update your GitLab self-managed instance to the latest version. | [**GitLab Releases**](https://about.gitlab.com/releases/)<br/>What's new in GitLab. | @@ -412,7 +412,7 @@ Learn more about using Git, and using Git with GitLab: | Topic | Description | |:----------------------------------------------------------------------------|:---------------------------------------------------------------------------| | [Git](topics/git/index.md) | Getting started with Git, branching strategies, Git LFS, and advanced use. | -| [Git cheatsheet](https://about.gitlab.com/images/press/git-cheat-sheet.pdf) | Download a PDF describing the most used Git operations. | +| [Git cheat sheet](https://about.gitlab.com/images/press/git-cheat-sheet.pdf) | Download a PDF describing the most used Git operations. | | [GitLab Flow](topics/gitlab_flow.md) | Explore the best of Git with the GitLab Flow strategy. | <div align="right"> diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql index 42446fb6ce1..d8902be92eb 100644 --- a/doc/api/graphql/reference/gitlab_schema.graphql +++ b/doc/api/graphql/reference/gitlab_schema.graphql @@ -2519,6 +2519,51 @@ type IssuePermissions { } """ +Autogenerated input type of IssueSetDueDate +""" +input IssueSetDueDateInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The desired due date for the issue + """ + dueDate: Time! + + """ + The iid of the issue to mutate + """ + iid: String! + + """ + The project the issue to mutate is in + """ + projectPath: ID! +} + +""" +Autogenerated return type of IssueSetDueDate +""" +type IssueSetDueDatePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Reasons why the mutation failed. + """ + errors: [String!]! + + """ + The issue after mutation + """ + issue: Issue +} + +""" Values for sorting issues """ enum IssueSort { @@ -3511,6 +3556,7 @@ type Mutation { destroyNote(input: DestroyNoteInput!): DestroyNotePayload epicSetSubscription(input: EpicSetSubscriptionInput!): EpicSetSubscriptionPayload epicTreeReorder(input: EpicTreeReorderInput!): EpicTreeReorderPayload + issueSetDueDate(input: IssueSetDueDateInput!): IssueSetDueDatePayload mergeRequestSetAssignees(input: MergeRequestSetAssigneesInput!): MergeRequestSetAssigneesPayload mergeRequestSetLabels(input: MergeRequestSetLabelsInput!): MergeRequestSetLabelsPayload mergeRequestSetLocked(input: MergeRequestSetLockedInput!): MergeRequestSetLockedPayload diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json index 07b91c1af1a..fba3dcca14d 100644 --- a/doc/api/graphql/reference/gitlab_schema.json +++ b/doc/api/graphql/reference/gitlab_schema.json @@ -14113,6 +14113,33 @@ "deprecationReason": null }, { + "name": "issueSetDueDate", + "description": null, + "args": [ + { + "name": "input", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "IssueSetDueDateInput", + "ofType": null + } + }, + "defaultValue": null + } + ], + "type": { + "kind": "OBJECT", + "name": "IssueSetDueDatePayload", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { "name": "mergeRequestSetAssignees", "description": null, "args": [ @@ -14960,6 +14987,136 @@ }, { "kind": "OBJECT", + "name": "IssueSetDueDatePayload", + "description": "Autogenerated return type of IssueSetDueDate", + "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": "issue", + "description": "The issue after mutation", + "args": [ + + ], + "type": { + "kind": "OBJECT", + "name": "Issue", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [ + + ], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "INPUT_OBJECT", + "name": "IssueSetDueDateInput", + "description": "Autogenerated input type of IssueSetDueDate", + "fields": null, + "inputFields": [ + { + "name": "projectPath", + "description": "The project the issue 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 issue to mutate", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null + }, + { + "name": "dueDate", + "description": "The desired due date for the issue", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Time", + "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": "MergeRequestSetLabelsPayload", "description": "Autogenerated return type of MergeRequestSetLabels", "fields": [ diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 18d49c9a7e1..4b71c9a8eaf 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -375,6 +375,14 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph | `createDesign` | Boolean! | Whether or not a user can perform `create_design` on this resource | | `destroyDesign` | Boolean! | Whether or not a user can perform `destroy_design` on this resource | +### IssueSetDueDatePayload + +| Name | Type | Description | +| --- | ---- | ---------- | +| `clientMutationId` | String | A unique identifier for the client performing the mutation. | +| `errors` | String! => Array | Reasons why the mutation failed. | +| `issue` | Issue | The issue after mutation | + ### Label | Name | Type | Description | diff --git a/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml index a60b00b2ee8..8f50f38bbed 100644 --- a/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml @@ -7,6 +7,7 @@ code_quality: variables: DOCKER_DRIVER: overlay2 DOCKER_TLS_CERTDIR: "" + CODE_QUALITY_IMAGE: "registry.gitlab.com/gitlab-org/security-products/codequality:12-5-stable" script: - | if ! docker info &>/dev/null; then @@ -14,11 +15,12 @@ code_quality: export DOCKER_HOST='tcp://localhost:2375' fi fi + - docker pull --quiet "$CODE_QUALITY_IMAGE" - docker run --env SOURCE_CODE="$PWD" --volume "$PWD":/code --volume /var/run/docker.sock:/var/run/docker.sock - "registry.gitlab.com/gitlab-org/security-products/codequality:12-5-stable" /code + "$CODE_QUALITY_IMAGE" /code artifacts: reports: codequality: gl-code-quality-report.json diff --git a/lib/gitlab/graphql/connections/keyset/connection.rb b/lib/gitlab/graphql/connections/keyset/connection.rb index e42a705a78a..5de075f2f7a 100644 --- a/lib/gitlab/graphql/connections/keyset/connection.rb +++ b/lib/gitlab/graphql/connections/keyset/connection.rb @@ -32,18 +32,11 @@ module Gitlab class Connection < GraphQL::Relay::BaseConnection include Gitlab::Utils::StrongMemoize - # TODO https://gitlab.com/gitlab-org/gitlab/issues/35104 - include Gitlab::Graphql::Connections::Keyset::LegacyKeysetConnection - def cursor_from_node(node) - return legacy_cursor_from_node(node) if use_legacy_pagination? - encoded_json_from_ordering(node) end def sliced_nodes - return legacy_sliced_nodes if use_legacy_pagination? - @sliced_nodes ||= begin OrderInfo.validate_ordering(ordered_nodes, order_list) diff --git a/lib/gitlab/graphql/connections/keyset/legacy_keyset_connection.rb b/lib/gitlab/graphql/connections/keyset/legacy_keyset_connection.rb deleted file mode 100644 index baf900d1048..00000000000 --- a/lib/gitlab/graphql/connections/keyset/legacy_keyset_connection.rb +++ /dev/null @@ -1,66 +0,0 @@ -# frozen_string_literal: true - -# TODO https://gitlab.com/gitlab-org/gitlab/issues/35104 -module Gitlab - module Graphql - module Connections - module Keyset - module LegacyKeysetConnection - def legacy_cursor_from_node(node) - encode(node[legacy_order_field].to_s) - end - - # rubocop: disable CodeReuse/ActiveRecord - def legacy_sliced_nodes - @sliced_nodes ||= - begin - sliced = nodes - - sliced = sliced.where(legacy_before_slice) if before.present? - sliced = sliced.where(legacy_after_slice) if after.present? - - sliced - end - end - # rubocop: enable CodeReuse/ActiveRecord - - private - - def use_legacy_pagination? - strong_memoize(:feature_disabled) do - Feature.disabled?(:graphql_keyset_pagination, default_enabled: true) - end - end - - def legacy_before_slice - if legacy_sort_direction == :asc - arel_table[legacy_order_field].lt(decode(before)) - else - arel_table[legacy_order_field].gt(decode(before)) - end - end - - def legacy_after_slice - if legacy_sort_direction == :asc - arel_table[legacy_order_field].gt(decode(after)) - else - arel_table[legacy_order_field].lt(decode(after)) - end - end - - def legacy_order_info - @legacy_order_info ||= nodes.order_values.first - end - - def legacy_order_field - @legacy_order_field ||= legacy_order_info&.expr&.name || nodes.primary_key - end - - def legacy_sort_direction - @legacy_order_direction ||= legacy_order_info&.direction || :desc - end - end - end - end - end -end diff --git a/spec/frontend/repository/utils/dom_spec.js b/spec/frontend/repository/utils/dom_spec.js index 678d444904d..bf98a9e1a4d 100644 --- a/spec/frontend/repository/utils/dom_spec.js +++ b/spec/frontend/repository/utils/dom_spec.js @@ -1,5 +1,5 @@ import { setHTMLFixture } from '../../helpers/fixtures'; -import { updateElementsVisibility } from '~/repository/utils/dom'; +import { updateElementsVisibility, updateFormAction } from '~/repository/utils/dom'; describe('updateElementsVisibility', () => { it('adds hidden class', () => { @@ -18,3 +18,13 @@ describe('updateElementsVisibility', () => { expect(document.querySelector('.js-test').classList).not.toContain('hidden'); }); }); + +describe('updateFormAction', () => { + it('updates form action', () => { + setHTMLFixture('<form class="js-test" action="/"></form>'); + + updateFormAction('.js-test', '/gitlab/create', '/test'); + + expect(document.querySelector('.js-test').action).toBe('http://localhost/gitlab/create/test'); + }); +}); diff --git a/spec/graphql/mutations/issues/set_due_date_spec.rb b/spec/graphql/mutations/issues/set_due_date_spec.rb new file mode 100644 index 00000000000..9a1f0925fe3 --- /dev/null +++ b/spec/graphql/mutations/issues/set_due_date_spec.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Mutations::Issues::SetDueDate do + let(:issue) { create(:issue) } + let(:user) { create(:user) } + subject(:mutation) { described_class.new(object: nil, context: { current_user: user }) } + + describe '#resolve' do + let(:due_date) { 2.days.since } + let(:mutated_issue) { subject[:issue] } + subject { mutation.resolve(project_path: issue.project.full_path, iid: issue.iid, due_date: due_date) } + + it 'raises an error if the resource is not accessible to the user' do + expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end + + context 'when the user can update the issue' do + before do + issue.project.add_developer(user) + end + + it 'returns the issue with updated due date' do + expect(mutated_issue).to eq(issue) + expect(mutated_issue.due_date).to eq(Date.today + 2.days) + expect(subject[:errors]).to be_empty + end + + context 'when passing incorrect due date value' do + let(:due_date) { 'test' } + + it 'does not update due date' do + expect(mutated_issue.due_date).to eq(issue.due_date) + end + end + end + end +end diff --git a/spec/lib/gitlab/graphql/connections/keyset/legacy_keyset_connection_spec.rb b/spec/lib/gitlab/graphql/connections/keyset/legacy_keyset_connection_spec.rb deleted file mode 100644 index aaf28fed684..00000000000 --- a/spec/lib/gitlab/graphql/connections/keyset/legacy_keyset_connection_spec.rb +++ /dev/null @@ -1,127 +0,0 @@ -# frozen_string_literal: true - -# TODO https://gitlab.com/gitlab-org/gitlab/issues/35104 -require 'spec_helper' - -describe Gitlab::Graphql::Connections::Keyset::LegacyKeysetConnection do - describe 'old keyset_connection' do - let(:described_class) { Gitlab::Graphql::Connections::Keyset::Connection } - let(:nodes) { Project.all.order(id: :asc) } - let(:arguments) { {} } - subject(:connection) do - described_class.new(nodes, arguments, max_page_size: 3) - end - - before do - stub_feature_flags(graphql_keyset_pagination: false) - end - - def encoded_property(value) - Base64Bp.urlsafe_encode64(value.to_s, padding: false) - end - - describe '#cursor_from_nodes' do - let(:project) { create(:project) } - - it 'returns an encoded ID' do - expect(connection.cursor_from_node(project)) - .to eq(encoded_property(project.id)) - end - - context 'when an order was specified' do - let(:nodes) { Project.order(:updated_at) } - - it 'returns the encoded value of the order' do - expect(connection.cursor_from_node(project)) - .to eq(encoded_property(project.updated_at)) - end - end - end - - describe '#sliced_nodes' do - let(:projects) { create_list(:project, 4) } - - context 'when before is passed' do - let(:arguments) { { before: encoded_property(projects[1].id) } } - - it 'only returns the project before the selected one' do - expect(subject.sliced_nodes).to contain_exactly(projects.first) - end - - context 'when the sort order is descending' do - let(:nodes) { Project.all.order(id: :desc) } - - it 'returns the correct nodes' do - expect(subject.sliced_nodes).to contain_exactly(*projects[2..-1]) - end - end - end - - context 'when after is passed' do - let(:arguments) { { after: encoded_property(projects[1].id) } } - - it 'only returns the project before the selected one' do - expect(subject.sliced_nodes).to contain_exactly(*projects[2..-1]) - end - - context 'when the sort order is descending' do - let(:nodes) { Project.all.order(id: :desc) } - - it 'returns the correct nodes' do - expect(subject.sliced_nodes).to contain_exactly(projects.first) - end - end - end - - context 'when both before and after are passed' do - let(:arguments) do - { - after: encoded_property(projects[1].id), - before: encoded_property(projects[3].id) - } - end - - it 'returns the expected set' do - expect(subject.sliced_nodes).to contain_exactly(projects[2]) - end - end - end - - describe '#paged_nodes' do - let!(:projects) { create_list(:project, 5) } - - it 'returns the collection limited to max page size' do - expect(subject.paged_nodes.size).to eq(3) - end - - it 'is a loaded memoized array' do - expect(subject.paged_nodes).to be_an(Array) - expect(subject.paged_nodes.object_id).to eq(subject.paged_nodes.object_id) - end - - context 'when `first` is passed' do - let(:arguments) { { first: 2 } } - - it 'returns only the first elements' do - expect(subject.paged_nodes).to contain_exactly(projects.first, projects.second) - end - end - - context 'when `last` is passed' do - let(:arguments) { { last: 2 } } - - it 'returns only the last elements' do - expect(subject.paged_nodes).to contain_exactly(projects[3], projects[4]) - end - end - - context 'when both are passed' do - let(:arguments) { { first: 2, last: 2 } } - - it 'raises an error' do - expect { subject.paged_nodes }.to raise_error(Gitlab::Graphql::Errors::ArgumentError) - end - end - end - end -end diff --git a/spec/requests/api/graphql/mutations/issues/set_due_date_spec.rb b/spec/requests/api/graphql/mutations/issues/set_due_date_spec.rb new file mode 100644 index 00000000000..1efa9e16233 --- /dev/null +++ b/spec/requests/api/graphql/mutations/issues/set_due_date_spec.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'Setting Due Date of an issue' do + include GraphqlHelpers + + let(:current_user) { create(:user) } + let(:issue) { create(:issue) } + let(:project) { issue.project } + let(:input) { { due_date: 2.days.since } } + + let(:mutation) do + variables = { + project_path: project.full_path, + iid: issue.iid.to_s + } + graphql_mutation(:issue_set_due_date, variables.merge(input), + <<-QL.strip_heredoc + clientMutationId + errors + issue { + iid + dueDate + } + QL + ) + end + + def mutation_response + graphql_mutation_response(:issue_set_due_date) + end + + before do + project.add_developer(current_user) + end + + it 'returns an error if the user is not allowed to update the issue' do + error = "The resource that you are attempting to access does not exist or you don't have permission to perform this action" + post_graphql_mutation(mutation, current_user: create(:user)) + + expect(graphql_errors).to include(a_hash_including('message' => error)) + end + + it 'updates the issue due date' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(response).to have_gitlab_http_status(:success) + expect(mutation_response['issue']['dueDate']).to eq(2.days.since.to_date.to_s) + end + + context 'when passing due date without a date value' do + let(:input) { { due_date: 'test' } } + + it 'returns internal server error' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(graphql_errors).to include(a_hash_including('message' => 'Internal server error')) + end + end +end |