diff options
40 files changed, 339 insertions, 62 deletions
diff --git a/app/assets/javascripts/boards/components/board.js b/app/assets/javascripts/boards/components/board.js index 75477ebb3b3..623cda5679a 100644 --- a/app/assets/javascripts/boards/components/board.js +++ b/app/assets/javascripts/boards/components/board.js @@ -53,6 +53,9 @@ export default Vue.extend({ const { issuesSize } = this.list; return `${n__('%d issue', '%d issues', issuesSize)}`; }, + isNewIssueShown() { + return this.list.type === 'backlog' || (!this.disabled && this.list.type !== 'closed'); + } }, watch: { filter: { diff --git a/app/assets/javascripts/boards/components/project_select.vue b/app/assets/javascripts/boards/components/project_select.vue index 4e8fe16160a..427a0868b0c 100644 --- a/app/assets/javascripts/boards/components/project_select.vue +++ b/app/assets/javascripts/boards/components/project_select.vue @@ -46,7 +46,7 @@ export default { selectable: true, data: (term, callback) => { this.loading = true; - return Api.groupProjects(this.groupId, term, {}, projects => { + return Api.groupProjects(this.groupId, term, {with_issues_enabled: true}, projects => { this.loading = false; callback(projects); }); diff --git a/app/assets/javascripts/jobs/components/sidebar.vue b/app/assets/javascripts/jobs/components/sidebar.vue index 906769ee6a2..28a02230d89 100644 --- a/app/assets/javascripts/jobs/components/sidebar.vue +++ b/app/assets/javascripts/jobs/components/sidebar.vue @@ -31,7 +31,7 @@ export default { }, }, computed: { - ...mapState(['job', 'stages', 'jobs', 'selectedStage']), + ...mapState(['job', 'stages', 'jobs', 'selectedStage', 'isLoadingStages']), coverage() { return `${this.job.coverage}%`; }, @@ -59,10 +59,10 @@ export default { return ''; } - let t = this.job.metadata.timeout_human_readable; - if (this.job.metadata.timeout_source !== '') { - t += ` (from ${this.job.metadata.timeout_source})`; - } + let t = this.job.metadata.timeout_human_readable; + if (this.job.metadata.timeout_source !== '') { + t += ` (from ${this.job.metadata.timeout_source})`; + } return t; }, @@ -270,6 +270,7 @@ export default { /> <stages-dropdown + v-if="!isLoadingStages" :stages="stages" :pipeline="job.pipeline" :selected-stage="selectedStage" diff --git a/app/assets/javascripts/jobs/components/stages_dropdown.vue b/app/assets/javascripts/jobs/components/stages_dropdown.vue index e5e1d56e287..dc26b246d71 100644 --- a/app/assets/javascripts/jobs/components/stages_dropdown.vue +++ b/app/assets/javascripts/jobs/components/stages_dropdown.vue @@ -22,7 +22,6 @@ export default { required: true, }, }, - computed: { hasRef() { return !_.isEmpty(this.pipeline.ref); diff --git a/app/assets/javascripts/jobs/store/mutations.js b/app/assets/javascripts/jobs/store/mutations.js index 4195d787f12..cd440d21c1f 100644 --- a/app/assets/javascripts/jobs/store/mutations.js +++ b/app/assets/javascripts/jobs/store/mutations.js @@ -71,7 +71,7 @@ export default { * after the first request, * and we do not want to hijack that */ - if (state.selectedStage === 'More' && job.stage) { + if (state.selectedStage === '' && job.stage) { state.selectedStage = job.stage; } }, diff --git a/app/assets/javascripts/jobs/store/state.js b/app/assets/javascripts/jobs/store/state.js index 0eb269ca38f..04825187c99 100644 --- a/app/assets/javascripts/jobs/store/state.js +++ b/app/assets/javascripts/jobs/store/state.js @@ -1,5 +1,3 @@ -import { __ } from '~/locale'; - export default () => ({ jobEndpoint: null, traceEndpoint: null, @@ -29,7 +27,7 @@ export default () => ({ // sidebar dropdown & list of jobs isLoadingStages: false, isLoadingJobs: false, - selectedStage: __('More'), + selectedStage: '', stages: [], jobs: [], }); diff --git a/app/assets/javascripts/lib/utils/text_markdown.js b/app/assets/javascripts/lib/utils/text_markdown.js index e26a6b986be..c52cfb806a2 100644 --- a/app/assets/javascripts/lib/utils/text_markdown.js +++ b/app/assets/javascripts/lib/utils/text_markdown.js @@ -2,6 +2,8 @@ import $ from 'jquery'; import { insertText } from '~/lib/utils/common_utils'; +const LINK_TAG_PATTERN = '[{text}](url)'; + function selectedText(text, textarea) { return text.substring(textarea.selectionStart, textarea.selectionEnd); } @@ -76,6 +78,21 @@ export function insertMarkdownText({ textArea, text, tag, blockTag, selected, wr removedFirstNewLine = false; currentLineEmpty = false; + // check for link pattern and selected text is an URL + // if so fill in the url part instead of the text part of the pattern. + if (tag === LINK_TAG_PATTERN) { + if (URL) { + try { + const ignoredUrl = new URL(selected); + // valid url + tag = '[text]({text})'; + select = 'text'; + } catch (e) { + // ignore - no valid url + } + } + } + // Remove the first newline if (selected.indexOf('\n') === 0) { removedFirstNewLine = true; diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index 6d891e21556..e261bd7c0ca 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -34,7 +34,7 @@ margin-bottom: 0; } - *:first-child:not(.katex-display) { + *:first-child { margin-top: 0; } diff --git a/app/controllers/concerns/boards_responses.rb b/app/controllers/concerns/boards_responses.rb index b7e4f9b81f1..3cdf4ddf8bb 100644 --- a/app/controllers/concerns/boards_responses.rb +++ b/app/controllers/concerns/boards_responses.rb @@ -50,7 +50,10 @@ module BoardsResponses end def authorize_create_issue - authorize_action_for!(project, :admin_issue) + list = List.find(issue_params[:list_id]) + action = list.backlog? ? :create_issue : :admin_issue + + authorize_action_for!(project, action) end def authorize_admin_list diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index 8abfe0c4c17..eb3d2498830 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -14,7 +14,7 @@ # project_id: integer # milestone_title: string # author_id: integer -# assignee_id: integer +# assignee_id: integer or 'None' or 'Any' # search: string # label_name: string # sort: string @@ -34,6 +34,11 @@ class IssuableFinder requires_cross_project_access unless: -> { project? } + # This is used as a common filter for None / Any + FILTER_NONE = 'none'.freeze + FILTER_ANY = 'any'.freeze + + # This is accepted as a deprecated filter and is also used in unassigning users NONE = '0'.freeze attr_accessor :current_user, :params @@ -236,16 +241,20 @@ class IssuableFinder # rubocop: enable CodeReuse/ActiveRecord def assignee_id? - params[:assignee_id].present? && params[:assignee_id].to_s != NONE + params[:assignee_id].present? end def assignee_username? - params[:assignee_username].present? && params[:assignee_username].to_s != NONE + params[:assignee_username].present? end - def no_assignee? + def filter_by_no_assignee? # Assignee_id takes precedence over assignee_username - params[:assignee_id].to_s == NONE || params[:assignee_username].to_s == NONE + [NONE, FILTER_NONE].include?(params[:assignee_id].to_s.downcase) || params[:assignee_username].to_s == NONE + end + + def filter_by_any_assignee? + params[:assignee_id].to_s.downcase == FILTER_ANY end # rubocop: disable CodeReuse/ActiveRecord @@ -399,15 +408,17 @@ class IssuableFinder # rubocop: disable CodeReuse/ActiveRecord def by_assignee(items) - if assignee - items = items.where(assignee_id: assignee.id) - elsif no_assignee? - items = items.where(assignee_id: nil) + if filter_by_no_assignee? + items.where(assignee_id: nil) + elsif filter_by_any_assignee? + items.where('assignee_id IS NOT NULL') + elsif assignee + items.where(assignee_id: assignee.id) elsif assignee_id? || assignee_username? # assignee not found - items = items.none + items.none + else + items end - - items end # rubocop: enable CodeReuse/ActiveRecord diff --git a/app/finders/issues_finder.rb b/app/finders/issues_finder.rb index abdc47b9866..cee57a83df4 100644 --- a/app/finders/issues_finder.rb +++ b/app/finders/issues_finder.rb @@ -137,10 +137,12 @@ class IssuesFinder < IssuableFinder # rubocop: disable CodeReuse/ActiveRecord def by_assignee(items) - if assignee - items.assigned_to(assignee) - elsif no_assignee? + if filter_by_no_assignee? items.unassigned + elsif filter_by_any_assignee? + items.assigned + elsif assignee + items.assigned_to(assignee) elsif assignee_id? || assignee_username? # assignee not found items.none else diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 06507345fe8..344f091c872 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -109,7 +109,7 @@ class CommitStatus < ActiveRecord::Base before_transition any => :failed do |commit_status, transition| failure_reason = transition.args.first - commit_status.failure_reason = failure_reason + commit_status.failure_reason = CommitStatus.failure_reasons[failure_reason] end after_transition do |commit_status, transition| diff --git a/app/services/resource_events/merge_into_notes_service.rb b/app/services/resource_events/merge_into_notes_service.rb index 596c0105ea0..7504773a002 100644 --- a/app/services/resource_events/merge_into_notes_service.rb +++ b/app/services/resource_events/merge_into_notes_service.rb @@ -34,7 +34,7 @@ module ResourceEvents def label_events_by_discussion_id return [] unless resource.respond_to?(:resource_label_events) - events = resource.resource_label_events.includes(:label, :user) + events = resource.resource_label_events.includes(:label, user: :status) events = since_fetch_at(events) events.group_by { |event| event.discussion_id } diff --git a/app/views/shared/boards/components/_board.html.haml b/app/views/shared/boards/components/_board.html.haml index e26f5260e5b..c6c5cadc3f5 100644 --- a/app/views/shared/boards/components/_board.html.haml +++ b/app/views/shared/boards/components/_board.html.haml @@ -39,14 +39,13 @@ {{ list.issuesSize }} = render_if_exists "shared/boards/components/list_weight" - - if can?(current_user, :admin_list, current_board_parent) - %button.issue-count-badge-add-button.btn.btn-sm.btn-default.ml-1.has-tooltip.js-no-trigger-collapse{ type: "button", - "@click" => "showNewIssueForm", - "v-if" => 'list.type !== "closed"', - "aria-label" => _("New issue"), - "title" => _("New issue"), - data: { placement: "top", container: "body" } } - = icon("plus", class: "js-no-trigger-collapse") + %button.issue-count-badge-add-button.btn.btn-sm.btn-default.ml-1.has-tooltip.js-no-trigger-collapse{ type: "button", + "@click" => "showNewIssueForm", + "v-if" => "isNewIssueShown", + "aria-label" => _("New issue"), + "title" => _("New issue"), + data: { placement: "top", container: "body" } } + = icon("plus", class: "js-no-trigger-collapse") %board-list{ "v-if" => 'list.type !== "blank" && list.type !== "promotion"', ":list" => "list", diff --git a/app/views/shared/notifications/_button.html.haml b/app/views/shared/notifications/_button.html.haml index 09ddf732ada..f6c7ca70ebd 100644 --- a/app/views/shared/notifications/_button.html.haml +++ b/app/views/shared/notifications/_button.html.haml @@ -9,11 +9,11 @@ %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: "Notification setting", "aria-label" => "Notification setting: #{notification_title(notification_setting.level)}", data: { container: "body", toggle: "modal", target: "#" + notifications_menu_identifier("modal", notification_setting), display: 'static' } } = icon("bell", class: "js-notification-loading") = notification_title(notification_setting.level) - %button.btn.dropdown-toggle{ data: { toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting) } } + %button.btn.dropdown-toggle{ data: { toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } } = icon('caret-down') .sr-only Toggle dropdown - else - %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: "Notification setting", "aria-label" => "Notification setting: #{notification_title(notification_setting.level)}", data: { container: "body", toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), display: 'static' } } + %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: "Notification setting", "aria-label" => "Notification setting: #{notification_title(notification_setting.level)}", data: { container: "body", toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } } = icon("bell", class: "js-notification-loading") = notification_title(notification_setting.level) = icon("caret-down") diff --git a/changelogs/unreleased/51306-fix-inaccessible-dropdown-for-codeless-projects.yml b/changelogs/unreleased/51306-fix-inaccessible-dropdown-for-codeless-projects.yml new file mode 100644 index 00000000000..13e3bb66430 --- /dev/null +++ b/changelogs/unreleased/51306-fix-inaccessible-dropdown-for-codeless-projects.yml @@ -0,0 +1,5 @@ +--- +title: Fix inaccessible dropdown for code-less projects +merge_request: 22137 +author: +type: other diff --git a/changelogs/unreleased/52115-Link-button-in-markdown-editor-should-recognize-URLs.yml b/changelogs/unreleased/52115-Link-button-in-markdown-editor-should-recognize-URLs.yml new file mode 100644 index 00000000000..8521335c2ea --- /dev/null +++ b/changelogs/unreleased/52115-Link-button-in-markdown-editor-should-recognize-URLs.yml @@ -0,0 +1,5 @@ +--- +title: Link button in markdown editor recognize URLs +merge_request: 1983 +author: Johann Hubert Sonntagbauer +type: changed diff --git a/changelogs/unreleased/52384-api-filter-assignee-none-any.yml b/changelogs/unreleased/52384-api-filter-assignee-none-any.yml new file mode 100644 index 00000000000..9acec04d946 --- /dev/null +++ b/changelogs/unreleased/52384-api-filter-assignee-none-any.yml @@ -0,0 +1,5 @@ +--- +title: Add None/Any option for assignee_id in Issues and Merge Requests API +merge_request: 22598 +author: Heinrich Lee Yu +type: added diff --git a/changelogs/unreleased/52545-guest-create-issue-in-group-board.yml b/changelogs/unreleased/52545-guest-create-issue-in-group-board.yml new file mode 100644 index 00000000000..5701e44eb32 --- /dev/null +++ b/changelogs/unreleased/52545-guest-create-issue-in-group-board.yml @@ -0,0 +1,5 @@ +--- +title: Always show new issue button in boards' Open list +merge_request: 22557 +author: Heinrich Lee Yu +type: fixed diff --git a/changelogs/unreleased/53133-jobs-list.yml b/changelogs/unreleased/53133-jobs-list.yml new file mode 100644 index 00000000000..2e13edc0e76 --- /dev/null +++ b/changelogs/unreleased/53133-jobs-list.yml @@ -0,0 +1,5 @@ +--- +title: Fix stage dropdown not rendering in different languages +merge_request: 22604 +author: +type: other diff --git a/changelogs/unreleased/avoid-lock-when-introduce-new-failure-reason.yml b/changelogs/unreleased/avoid-lock-when-introduce-new-failure-reason.yml new file mode 100644 index 00000000000..30b9ae032d4 --- /dev/null +++ b/changelogs/unreleased/avoid-lock-when-introduce-new-failure-reason.yml @@ -0,0 +1,5 @@ +--- +title: Support backward compatibility when introduce new failure reason +merge_request: 22566 +author: +type: changed diff --git a/changelogs/unreleased/bvl-preload-user-status-for-events.yml b/changelogs/unreleased/bvl-preload-user-status-for-events.yml new file mode 100644 index 00000000000..e13b19b19c1 --- /dev/null +++ b/changelogs/unreleased/bvl-preload-user-status-for-events.yml @@ -0,0 +1,5 @@ +--- +title: Show user status for label events in system notes +merge_request: 22609 +author: +type: fixed diff --git a/doc/api/issues.md b/doc/api/issues.md index cc1d6834a20..57e861bc62e 100644 --- a/doc/api/issues.md +++ b/doc/api/issues.md @@ -40,7 +40,7 @@ GET /issues?my_reaction_emoji=star | `milestone` | string | no | The milestone title. `No+Milestone` lists all issues with no milestone. `Any+Milestone` lists all issues that have an assigned milestone | | `scope` | string | no | Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`. Defaults to `created_by_me`<br> For versions before 11.0, use the now deprecated `created-by-me` or `assigned-to-me` scopes instead.<br> _([Introduced][ce-13004] in GitLab 9.5. [Changed to snake_case][ce-18935] in GitLab 11.0)_ | | `author_id` | integer | no | Return issues created by the given user `id`. Combine with `scope=all` or `scope=assigned_to_me`. _([Introduced][ce-13004] in GitLab 9.5)_ | -| `assignee_id` | integer | no | Return issues assigned to the given user `id` _([Introduced][ce-13004] in GitLab 9.5)_ | +| `assignee_id` | integer | no | Return issues assigned to the given user `id`. `None` returns unassigned issues. `Any` returns issues with an assignee. _([Introduced][ce-13004] in GitLab 9.5)_ | | `my_reaction_emoji` | string | no | Return issues reacted by the authenticated user by the given `emoji` _([Introduced][ce-14016] in GitLab 10.0)_ | | `iids[]` | Array[integer] | no | Return only the issues having the given `iid` | | `order_by` | string | no | Return issues ordered by `created_at` or `updated_at` fields. Default is `created_at` | @@ -154,7 +154,7 @@ GET /groups/:id/issues?my_reaction_emoji=star | `milestone` | string | no | The milestone title. `No+Milestone` lists all issues with no milestone | | `scope` | string | no | Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`.<br> For versions before 11.0, use the now deprecated `created-by-me` or `assigned-to-me` scopes instead.<br> _([Introduced][ce-13004] in GitLab 9.5. [Changed to snake_case][ce-18935] in GitLab 11.0)_ | | `author_id` | integer | no | Return issues created by the given user `id` _([Introduced][ce-13004] in GitLab 9.5)_ | -| `assignee_id` | integer | no | Return issues assigned to the given user `id` _([Introduced][ce-13004] in GitLab 9.5)_ | +| `assignee_id` | integer | no | Return issues assigned to the given user `id`. `None` returns unassigned issues. `Any` returns issues with an assignee. _([Introduced][ce-13004] in GitLab 9.5)_ | | `my_reaction_emoji` | string | no | Return issues reacted by the authenticated user by the given `emoji` _([Introduced][ce-14016] in GitLab 10.0)_ | | `order_by` | string | no | Return issues ordered by `created_at` or `updated_at` fields. Default is `created_at` | | `sort` | string | no | Return issues sorted in `asc` or `desc` order. Default is `desc` | @@ -268,7 +268,7 @@ GET /projects/:id/issues?my_reaction_emoji=star | `milestone` | string | no | The milestone title. `No+Milestone` lists all issues with no milestone | | `scope` | string | no | Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`.<br> For versions before 11.0, use the now deprecated `created-by-me` or `assigned-to-me` scopes instead.<br> _([Introduced][ce-13004] in GitLab 9.5. [Changed to snake_case][ce-18935] in GitLab 11.0)_ | | `author_id` | integer | no | Return issues created by the given user `id` _([Introduced][ce-13004] in GitLab 9.5)_ | -| `assignee_id` | integer | no | Return issues assigned to the given user `id` _([Introduced][ce-13004] in GitLab 9.5)_ | +| `assignee_id` | integer | no | Return issues assigned to the given user `id`. `None` returns unassigned issues. `Any` returns issues with an assignee. _([Introduced][ce-13004] in GitLab 9.5)_ | | `my_reaction_emoji` | string | no | Return issues reacted by the authenticated user by the given `emoji` _([Introduced][ce-14016] in GitLab 10.0)_ | | `order_by` | string | no | Return issues ordered by `created_at` or `updated_at` fields. Default is `created_at` | | `sort` | string | no | Return issues sorted in `asc` or `desc` order. Default is `desc` | diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index 862ee398a84..0291b7e00c2 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -42,7 +42,7 @@ Parameters: | `updated_before` | datetime | no | Return merge requests updated on or before the given time | | `scope` | string | no | Return merge requests for the given scope: `created_by_me`, `assigned_to_me` or `all`. Defaults to `created_by_me`<br> For versions before 11.0, use the now deprecated `created-by-me` or `assigned-to-me` scopes instead. | | `author_id` | integer | no | Returns merge requests created by the given user `id`. Combine with `scope=all` or `scope=assigned_to_me` | -| `assignee_id` | integer | no | Returns merge requests assigned to the given user `id` | +| `assignee_id` | integer | no | Returns merge requests assigned to the given user `id`. `None` returns unassigned merge requests. `Any` returns merge requests with an assignee. | | `my_reaction_emoji` | string | no | Return merge requests reacted by the authenticated user by the given `emoji` _([Introduced][ce-14016] in GitLab 10.0)_ | | `source_branch` | string | no | Return merge requests with the given source branch | | `target_branch` | string | no | Return merge requests with the given target branch | @@ -166,7 +166,7 @@ Parameters: | `updated_before` | datetime | no | Return merge requests updated on or before the given time | | `scope` | string | no | Return merge requests for the given scope: `created_by_me`, `assigned_to_me` or `all`.<br> For versions before 11.0, use the now deprecated `created-by-me` or `assigned-to-me` scopes instead.<br> _([Introduced][ce-13060] in GitLab 9.5. [Changed to snake_case][ce-18935] in GitLab 11.0)_ | | `author_id` | integer | no | Returns merge requests created by the given user `id` _([Introduced][ce-13060] in GitLab 9.5)_ | -| `assignee_id` | integer | no | Returns merge requests assigned to the given user `id` _([Introduced][ce-13060] in GitLab 9.5)_ | +| `assignee_id` | integer | no | Returns merge requests assigned to the given user `id`. `None` returns unassigned merge requests. `Any` returns merge requests with an assignee. _([Introduced][ce-13060] in GitLab 9.5)_ | | `my_reaction_emoji` | string | no | Return merge requests reacted by the authenticated user by the given `emoji` _([Introduced][ce-14016] in GitLab 10.0)_ | | `source_branch` | string | no | Return merge requests with the given source branch | | `target_branch` | string | no | Return merge requests with the given target branch | @@ -279,7 +279,7 @@ Parameters: | `updated_before` | datetime | no | Return merge requests updated on or before the given time | | `scope` | string | no | Return merge requests for the given scope: `created_by_me`, `assigned_to_me` or `all`.<br> | | `author_id` | integer | no | Returns merge requests created by the given user `id` _([Introduced][ce-13060] in GitLab 9.5)_ | -| `assignee_id` | integer | no | Returns merge requests assigned to the given user `id` _([Introduced][ce-13060] in GitLab 9.5)_ | +| `assignee_id` | integer | no | Returns merge requests assigned to the given user `id`. `None` returns unassigned merge requests. `Any` returns merge requests with an assignee. _([Introduced][ce-13060] in GitLab 9.5)_ | | `my_reaction_emoji` | string | no | Return merge requests reacted by the authenticated user by the given `emoji` _([Introduced][ce-14016] in GitLab 10.0)_ | | `source_branch` | string | no | Return merge requests with the given source branch | | `target_branch` | string | no | Return merge requests with the given target branch | diff --git a/lib/api/helpers/custom_validators.rb b/lib/api/helpers/custom_validators.rb index 23b1cd1ad45..1058f4e8a5e 100644 --- a/lib/api/helpers/custom_validators.rb +++ b/lib/api/helpers/custom_validators.rb @@ -10,8 +10,21 @@ module API raise Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: message(:absence) end end + + class IntegerNoneAny < Grape::Validations::Base + def validate_param!(attr_name, params) + value = params[attr_name] + + return if value.is_a?(Integer) || + [IssuableFinder::FILTER_NONE, IssuableFinder::FILTER_ANY].include?(value.to_s.downcase) + + raise Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], + message: "should be an integer, 'None' or 'Any'" + end + end end end end Grape::Validations.register_validator(:absence, ::API::Helpers::CustomValidators::Absence) +Grape::Validations.register_validator(:integer_none_any, ::API::Helpers::CustomValidators::IntegerNoneAny) diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 25d78053c88..405fc30a2ed 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -40,7 +40,8 @@ module API optional :updated_after, type: DateTime, desc: 'Return issues updated after the specified time' optional :updated_before, type: DateTime, desc: 'Return issues updated before the specified time' optional :author_id, type: Integer, desc: 'Return issues which are authored by the user with the given ID' - optional :assignee_id, type: Integer, desc: 'Return issues which are assigned to the user with the given ID' + optional :assignee_id, types: [Integer, String], integer_none_any: true, + desc: 'Return issues which are assigned to the user with the given ID' optional :scope, type: String, values: %w[created-by-me assigned-to-me created_by_me assigned_to_me all], desc: 'Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`' optional :my_reaction_emoji, type: String, desc: 'Return issues reacted by the authenticated user by the given emoji' diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index 440d94ae186..a617efaaa4c 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -89,7 +89,8 @@ module API optional :updated_before, type: DateTime, desc: 'Return merge requests updated before the specified time' optional :view, type: String, values: %w[simple], desc: 'If simple, returns the `iid`, URL, title, description, and basic state of merge request' optional :author_id, type: Integer, desc: 'Return merge requests which are authored by the user with the given ID' - optional :assignee_id, type: Integer, desc: 'Return merge requests which are assigned to the user with the given ID' + optional :assignee_id, types: [Integer, String], integer_none_any: true, + desc: 'Return merge requests which are assigned to the user with the given ID' optional :scope, type: String, values: %w[created-by-me assigned-to-me created_by_me assigned_to_me all], desc: 'Return merge requests for the given scope: `created_by_me`, `assigned_to_me` or `all`' optional :my_reaction_emoji, type: String, desc: 'Return issues reacted by the authenticated user by the given emoji' diff --git a/lib/api/runner.rb b/lib/api/runner.rb index d8768a54986..2f15f3a7d76 100644 --- a/lib/api/runner.rb +++ b/lib/api/runner.rb @@ -142,8 +142,7 @@ module API requires :id, type: Integer, desc: %q(Job's ID) optional :trace, type: String, desc: %q(Job's full trace) optional :state, type: String, desc: %q(Job's status: success, failed) - optional :failure_reason, type: String, values: CommitStatus.failure_reasons.keys, - desc: %q(Job's failure_reason) + optional :failure_reason, type: String, desc: %q(Job's failure_reason) end put '/:id' do job = authenticate_job! diff --git a/spec/controllers/boards/issues_controller_spec.rb b/spec/controllers/boards/issues_controller_spec.rb index c365988a100..98946e4287b 100644 --- a/spec/controllers/boards/issues_controller_spec.rb +++ b/spec/controllers/boards/issues_controller_spec.rb @@ -208,11 +208,22 @@ describe Boards::IssuesController do end end - context 'with unauthorized user' do - it 'returns a forbidden 403 response' do - create_issue user: guest, board: board, list: list1, title: 'New issue' + context 'with guest user' do + context 'in open list' do + it 'returns a successful 200 response' do + open_list = board.lists.create(list_type: :backlog) + create_issue user: guest, board: board, list: open_list, title: 'New issue' - expect(response).to have_gitlab_http_status(403) + expect(response).to have_gitlab_http_status(200) + end + end + + context 'in label list' do + it 'returns a forbidden 403 response' do + create_issue user: guest, board: board, list: list1, title: 'New issue' + + expect(response).to have_gitlab_http_status(403) + end end end diff --git a/spec/features/boards/new_issue_spec.rb b/spec/features/boards/new_issue_spec.rb index 0bf1ecbc433..164442a47f5 100644 --- a/spec/features/boards/new_issue_spec.rb +++ b/spec/features/boards/new_issue_spec.rb @@ -94,8 +94,14 @@ describe 'Issue Boards new issue', :js do wait_for_requests end - it 'does not display new issue button' do - expect(page).to have_selector('.issue-count-badge-add-button', count: 0) + it 'displays new issue button in open list' do + expect(first('.board')).to have_selector('.issue-count-badge-add-button', count: 1) + end + + it 'does not display new issue button in label list' do + page.within('.board:nth-child(2)') do + expect(page).not_to have_selector('.issue-count-badge-add-button') + end end end end diff --git a/spec/features/issues/resource_label_events_spec.rb b/spec/features/issues/resource_label_events_spec.rb index 40c452c991a..b0764db7751 100644 --- a/spec/features/issues/resource_label_events_spec.rb +++ b/spec/features/issues/resource_label_events_spec.rb @@ -7,6 +7,7 @@ describe 'List issue resource label events', :js do let(:project) { create(:project, :public) } let(:issue) { create(:issue, project: project, author: user) } let!(:label) { create(:label, project: project, title: 'foo') } + let!(:user_status) { create(:user_status, user: user) } context 'when user displays the issue' do let!(:note) { create(:note_on_issue, author: user, project: project, noteable: issue, note: 'some note') } @@ -23,6 +24,12 @@ describe 'List issue resource label events', :js do expect(find("#note_#{event.discussion_id}")).to have_content 'added foo label' end end + + it 'shows the user status on the system note for the label' do + page.within("#note_#{event.discussion_id}") do + expect(page).to show_user_status user_status + end + end end context 'when user adds label to the issue' do diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb index 0689c843104..2f164ffa8b0 100644 --- a/spec/finders/issues_finder_spec.rb +++ b/spec/finders/issues_finder_spec.rb @@ -57,11 +57,37 @@ describe IssuesFinder do end context 'filtering by no assignee' do - let(:params) { { assignee_id: 0 } } + let(:params) { { assignee_id: 'None' } } - it 'returns issues not assign to any assignee' do + it 'returns issues not assigned to any assignee' do expect(issues).to contain_exactly(issue4) end + + it 'returns issues not assigned to any assignee' do + params[:assignee_id] = 0 + + expect(issues).to contain_exactly(issue4) + end + + it 'returns issues not assigned to any assignee' do + params[:assignee_id] = 'none' + + expect(issues).to contain_exactly(issue4) + end + end + + context 'filtering by any assignee' do + let(:params) { { assignee_id: 'Any' } } + + it 'returns issues assigned to any assignee' do + expect(issues).to contain_exactly(issue1, issue2, issue3) + end + + it 'returns issues assigned to any assignee' do + params[:assignee_id] = 'any' + + expect(issues).to contain_exactly(issue1, issue2, issue3) + end end context 'filtering by group_id' do diff --git a/spec/javascripts/jobs/components/sidebar_spec.js b/spec/javascripts/jobs/components/sidebar_spec.js index 460a2e1b5da..424092d2d88 100644 --- a/spec/javascripts/jobs/components/sidebar_spec.js +++ b/spec/javascripts/jobs/components/sidebar_spec.js @@ -140,10 +140,11 @@ describe('Sidebar details block', () => { }); describe('while fetching stages', () => { - it('renders dropdown with More label', () => { + it('it does not render dropdown', () => { + store.dispatch('requestStages'); vm = mountComponentWithStore(SidebarComponent, { store }); - expect(vm.$el.querySelector('.js-selected-stage').textContent.trim()).toEqual('More'); + expect(vm.$el.querySelector('.js-selected-stage')).toBeNull(); }); }); diff --git a/spec/javascripts/jobs/store/mutations_spec.js b/spec/javascripts/jobs/store/mutations_spec.js index 4230a7c42cf..d7908efcf13 100644 --- a/spec/javascripts/jobs/store/mutations_spec.js +++ b/spec/javascripts/jobs/store/mutations_spec.js @@ -124,8 +124,8 @@ describe('Jobs Store Mutations', () => { expect(stateCopy.job).toEqual({ id: 1312321 }); }); - it('sets selectedStage when the selectedStage is More', () => { - expect(stateCopy.selectedStage).toEqual('More'); + it('sets selectedStage when the selectedStage is empty', () => { + expect(stateCopy.selectedStage).toEqual(''); mutations[types.RECEIVE_JOB_SUCCESS](stateCopy, { id: 1312321, stage: 'deploy' }); expect(stateCopy.selectedStage).toEqual('deploy'); diff --git a/spec/javascripts/lib/utils/text_markdown_spec.js b/spec/javascripts/lib/utils/text_markdown_spec.js index bb7a29fe30a..b9e805628f8 100644 --- a/spec/javascripts/lib/utils/text_markdown_spec.js +++ b/spec/javascripts/lib/utils/text_markdown_spec.js @@ -166,6 +166,33 @@ describe('init markdown', () => { expect(textArea.selectionStart).toEqual(expectedText.lastIndexOf(select)); expect(textArea.selectionEnd).toEqual(expectedText.lastIndexOf(select) + select.length); }); + + it('should support selected urls', () => { + const expectedUrl = 'http://www.gitlab.com'; + const expectedSelectionText = 'text'; + const expectedText = `text [${expectedSelectionText}](${expectedUrl}) text`; + const initialValue = `text ${expectedUrl} text`; + + textArea.value = initialValue; + const selectedIndex = initialValue.indexOf(expectedUrl); + textArea.setSelectionRange(selectedIndex, selectedIndex + expectedUrl.length); + + insertMarkdownText({ + textArea, + text: textArea.value, + tag, + blockTag: null, + selected: expectedUrl, + wrap: false, + select, + }); + + expect(textArea.value).toEqual(expectedText); + expect(textArea.selectionStart).toEqual(expectedText.indexOf(expectedSelectionText, 1)); + expect(textArea.selectionEnd).toEqual( + expectedText.indexOf(expectedSelectionText, 1) + expectedSelectionText.length, + ); + }); }); }); }); diff --git a/spec/lib/api/helpers/custom_validators_spec.rb b/spec/lib/api/helpers/custom_validators_spec.rb new file mode 100644 index 00000000000..41e6fb47b11 --- /dev/null +++ b/spec/lib/api/helpers/custom_validators_spec.rb @@ -0,0 +1,64 @@ +require 'spec_helper' + +describe API::Helpers::CustomValidators do + let(:scope) do + Struct.new(:opts) do + def full_name(attr_name) + attr_name + end + end + end + + describe API::Helpers::CustomValidators::Absence do + subject do + described_class.new(['test'], {}, false, scope.new) + end + + context 'empty param' do + it 'does not raise a validation error' do + expect_no_validation_error({}) + end + end + + context 'invalid parameters' do + it 'should raise a validation error' do + expect_validation_error({ 'test' => 'some_value' }) + end + end + end + + describe API::Helpers::CustomValidators::IntegerNoneAny do + subject do + described_class.new(['test'], {}, false, scope.new) + end + + context 'valid parameters' do + it 'does not raise a validation error' do + expect_no_validation_error({ 'test' => 2 }) + expect_no_validation_error({ 'test' => 100 }) + expect_no_validation_error({ 'test' => 'None' }) + expect_no_validation_error({ 'test' => 'Any' }) + expect_no_validation_error({ 'test' => 'none' }) + expect_no_validation_error({ 'test' => 'any' }) + end + end + + context 'invalid parameters' do + it 'should raise a validation error' do + expect_validation_error({ 'test' => 'some_other_string' }) + end + end + end + + def expect_no_validation_error(params) + expect { validate_test_param!(params) }.not_to raise_error + end + + def expect_validation_error(params) + expect { validate_test_param!(params) }.to raise_error(Grape::Exceptions::Validation) + end + + def validate_test_param!(params) + subject.validate_param!('test', params) + end +end diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index 9f6cf12f9a7..9cda39a569b 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -178,6 +178,24 @@ describe API::Issues do expect(first_issue['id']).to eq(issue2.id) end + it 'returns issues with no assignee' do + issue2 = create(:issue, author: user2, project: project) + + get api('/issues', user), assignee_id: 'None', scope: 'all' + + expect_paginated_array_response(size: 1) + expect(first_issue['id']).to eq(issue2.id) + end + + it 'returns issues with any assignee' do + # This issue without assignee should not be returned + create(:issue, author: user2, project: project) + + get api('/issues', user), assignee_id: 'Any', scope: 'all' + + expect_paginated_array_response(size: 3) + end + it 'returns issues reacted by the authenticated user by the given emoji' do issue2 = create(:issue, project: project, author: user, assignees: [user]) award_emoji = create(:award_emoji, awardable: issue2, user: user2, name: 'star') diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 07d19e3ad29..e4e0ca285e0 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -143,6 +143,23 @@ describe API::MergeRequests do expect_response_ordered_exactly(merge_request3) end + it 'returns an array of merge requests with no assignee' do + merge_request3 = create(:merge_request, :simple, author: user, source_project: project2, target_project: project2, source_branch: 'other-branch') + + get api('/merge_requests', user), assignee_id: 'None', scope: :all + + expect_response_ordered_exactly(merge_request3) + end + + it 'returns an array of merge requests with any assignee' do + # This MR with no assignee should not be returned + create(:merge_request, :simple, author: user, source_project: project2, target_project: project2, source_branch: 'other-branch') + + get api('/merge_requests', user), assignee_id: 'Any', scope: :all + + expect_response_contain_exactly(merge_request, merge_request2, merge_request_closed, merge_request_merged, merge_request_locked) + end + it 'returns an array of merge requests assigned to me' do merge_request3 = create(:merge_request, :simple, author: user, assignee: user2, source_project: project2, target_project: project2, source_branch: 'other-branch') diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb index 43ceb332cfb..c0d5a3ad74b 100644 --- a/spec/requests/api/runner_spec.rb +++ b/spec/requests/api/runner_spec.rb @@ -797,6 +797,15 @@ describe API::Runner, :clean_gitlab_redis_shared_state do it { expect(job).to be_runner_system_failure } end + + context 'when failure_reason is unrecognized value' do + before do + update_job(state: 'failed', failure_reason: 'what_is_this') + job.reload + end + + it { expect(job).to be_unknown_failure } + end end context 'when trace is given' do diff --git a/spec/services/resource_events/merge_into_notes_service_spec.rb b/spec/services/resource_events/merge_into_notes_service_spec.rb index 0d333d541c9..c76f6e6f77e 100644 --- a/spec/services/resource_events/merge_into_notes_service_spec.rb +++ b/spec/services/resource_events/merge_into_notes_service_spec.rb @@ -66,5 +66,14 @@ describe ResourceEvents::MergeIntoNotesService do expect(notes.count).to eq 1 expect(notes.first.discussion_id).to eq event.discussion_id end + + it "preloads the note author's status" do + event = create_event(created_at: time) + create(:user_status, user: event.user) + + notes = described_class.new(resource, user).execute + + expect(notes.first.author.association(:status)).to be_loaded + end end end |