diff options
194 files changed, 1434 insertions, 1059 deletions
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index d443238b9e1..16b0b5c95e2 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -143,7 +143,7 @@ Lint/MissingCopEnableDirective: Lint/NestedPercentLiteral: Exclude: - 'lib/gitlab/git/repository.rb' - - 'spec/support/email_format_shared_examples.rb' + - 'spec/support/shared_examples/email_format_shared_examples.rb' # Offense count: 1 Lint/ReturnInVoidContext: @@ -195,8 +195,8 @@ Naming/HeredocDelimiterCase: - 'spec/lib/gitlab/diff/parser_spec.rb' - 'spec/lib/json_web_token/rsa_token_spec.rb' - 'spec/models/commit_spec.rb' - - 'spec/support/repo_helpers.rb' - - 'spec/support/seed_repo.rb' + - 'spec/support/helpers/repo_helpers.rb' + - 'spec/support/helpers/seed_repo.rb' # Offense count: 112 # Configuration parameters: Blacklist. @@ -496,7 +496,7 @@ Style/EmptyLiteral: - 'spec/lib/gitlab/request_context_spec.rb' - 'spec/lib/gitlab/workhorse_spec.rb' - 'spec/requests/api/jobs_spec.rb' - - 'spec/support/chat_slash_commands_shared_examples.rb' + - 'spec/support/shared_examples/chat_slash_commands_shared_examples.rb' # Offense count: 102 # Cop supports --auto-correct. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 02599907af7..29009010f0e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -26,7 +26,9 @@ _This notice should stay as the first item in the CONTRIBUTING.md file._ - [Type labels (~"feature proposal", ~bug, ~customer, etc.)](#type-labels-feature-proposal-bug-customer-etc) - [Subject labels (~wiki, ~"container registry", ~ldap, ~api, etc.)](#subject-labels-wiki-container-registry-ldap-api-etc) - [Team labels (~"CI/CD", ~Discussion, ~Edge, ~Platform, etc.)](#team-labels-cicd-discussion-edge-platform-etc) - - [Priority labels (~Deliverable, ~Stretch, ~"Next Patch Release")](#priority-labels-deliverable-stretch-next-patch-release) + - [Milestone labels (~Deliverable, ~Stretch, ~"Next Patch Release")](#milestone-labels-deliverable-stretch-next-patch-release) + - [Priority labels (~Deliverable, ~Stretch, ~"Next Patch Release")](#bug-priority-labels-p1-p2-p3-etc) + - [Severity labels (~Deliverable, ~Stretch, ~"Next Patch Release")](#bug-severity-labels-s1-s2-s3-etc) - [Label for community contributors (~"Accepting Merge Requests")](#label-for-community-contributors-accepting-merge-requests) - [Implement design & UI elements](#implement-design-ui-elements) - [Issue tracker](#issue-tracker) @@ -127,6 +129,8 @@ Most issues will have labels for at least one of the following: - Subject: ~wiki, ~"container registry", ~ldap, ~api, ~frontend, etc. - Team: ~"CI/CD", ~Discussion, ~Edge, ~Platform, etc. - Milestone: ~Deliverable, ~Stretch, ~"Next Patch Release" +- Priority: ~P1, ~P2, ~P3, ~P4 +- Severity: ~S1, ~S2, ~S3, ~S4 All labels, their meaning and priority are defined on the [labels page][labels-page]. @@ -210,7 +214,7 @@ This label documents the planned timeline & urgency which is used to measure aga | Label | Meaning | Estimate time to fix | Guidance | |-------|-----------------|------------------------------------------------------------------|----------| -| ~P1 | Urgent Priority | The current release | | +| ~P1 | Urgent Priority | The current release + potentially immediate hotfix to GitLab.com | | | ~P2 | High Priority | The next release | | | ~P3 | Medium Priority | Within the next 3 releases (approx one quarter) | | | ~P4 | Low Priority | Anything outside the next 3 releases (approx beyond one quarter) | The issue is prominent but does not impact user workflow and a workaround is documented | diff --git a/app/assets/javascripts/pipelines/components/graph/action_component.vue b/app/assets/javascripts/pipelines/components/graph/action_component.vue index e99d949801f..29ee73a2a6f 100644 --- a/app/assets/javascripts/pipelines/components/graph/action_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/action_component.vue @@ -32,26 +32,38 @@ export default { required: true, }, - buttonDisabled: { + requestFinishedFor: { type: String, required: false, - default: null, + default: '', }, }, + data() { + return { + isDisabled: false, + linkRequested: '', + }; + }, + computed: { cssClass() { const actionIconDash = dasherize(this.actionIcon); return `${actionIconDash} js-icon-${actionIconDash}`; }, - isDisabled() { - return this.buttonDisabled === this.link; + }, + watch: { + requestFinishedFor() { + if (this.requestFinishedFor === this.linkRequested) { + this.isDisabled = false; + } }, }, - methods: { onClickAction() { $(this.$el).tooltip('hide'); eventHub.$emit('graphAction', this.link); + this.linkRequested = this.link; + this.isDisabled = true; }, }, }; @@ -62,7 +74,8 @@ export default { @click="onClickAction" v-tooltip :title="tooltipText" - class="btn btn-blank btn-transparent ci-action-icon-container ci-action-icon-wrapper" + class="js-ci-action btn btn-blank +btn-transparent ci-action-icon-container ci-action-icon-wrapper" :class="cssClass" data-container="body" :disabled="isDisabled" diff --git a/app/assets/javascripts/pipelines/components/graph/dropdown_action_component.vue b/app/assets/javascripts/pipelines/components/graph/dropdown_action_component.vue deleted file mode 100644 index 7c4fd65e36f..00000000000 --- a/app/assets/javascripts/pipelines/components/graph/dropdown_action_component.vue +++ /dev/null @@ -1,53 +0,0 @@ -<script> - import icon from '../../../vue_shared/components/icon.vue'; - import tooltip from '../../../vue_shared/directives/tooltip'; - - /** - * Renders either a cancel, retry or play icon pointing to the given path. - * TODO: Remove UJS from here and use an async request instead. - */ - export default { - components: { - icon, - }, - - directives: { - tooltip, - }, - props: { - tooltipText: { - type: String, - required: true, - }, - - link: { - type: String, - required: true, - }, - - actionMethod: { - type: String, - required: true, - }, - - actionIcon: { - type: String, - required: true, - }, - }, - }; -</script> -<template> - <a - v-tooltip - :data-method="actionMethod" - :title="tooltipText" - :href="link" - rel="nofollow" - class="ci-action-icon-wrapper js-ci-status-icon" - data-container="body" - aria-label="Job's action" - > - <icon :name="actionIcon" /> - </a> -</template> diff --git a/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue b/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue index be213c2ee78..43121dd38f3 100644 --- a/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue @@ -1,77 +1,83 @@ <script> - import $ from 'jquery'; - import jobNameComponent from './job_name_component.vue'; - import jobComponent from './job_component.vue'; - import tooltip from '../../../vue_shared/directives/tooltip'; +import $ from 'jquery'; +import JobNameComponent from './job_name_component.vue'; +import JobComponent from './job_component.vue'; +import tooltip from '../../../vue_shared/directives/tooltip'; - /** - * Renders the dropdown for the pipeline graph. - * - * The following object should be provided as `job`: - * - * { - * "id": 4256, - * "name": "test", - * "status": { - * "icon": "icon_status_success", - * "text": "passed", - * "label": "passed", - * "group": "success", - * "details_path": "/root/ci-mock/builds/4256", - * "action": { - * "icon": "retry", - * "title": "Retry", - * "path": "/root/ci-mock/builds/4256/retry", - * "method": "post" - * } - * } - * } - */ - export default { - directives: { - tooltip, - }, +/** + * Renders the dropdown for the pipeline graph. + * + * The following object should be provided as `job`: + * + * { + * "id": 4256, + * "name": "test", + * "status": { + * "icon": "icon_status_success", + * "text": "passed", + * "label": "passed", + * "group": "success", + * "details_path": "/root/ci-mock/builds/4256", + * "action": { + * "icon": "retry", + * "title": "Retry", + * "path": "/root/ci-mock/builds/4256/retry", + * "method": "post" + * } + * } + * } + */ +export default { + directives: { + tooltip, + }, - components: { - jobComponent, - jobNameComponent, - }, + components: { + JobComponent, + JobNameComponent, + }, - props: { - job: { - type: Object, - required: true, - }, + props: { + job: { + type: Object, + required: true, }, - - computed: { - tooltipText() { - return `${this.job.name} - ${this.job.status.label}`; - }, + requestFinishedFor: { + type: String, + required: false, + default: '', }, + }, - mounted() { - this.stopDropdownClickPropagation(); + computed: { + tooltipText() { + return `${this.job.name} - ${this.job.status.label}`; }, + }, + + mounted() { + this.stopDropdownClickPropagation(); + }, - methods: { - /** - * When the user right clicks or cmd/ctrl + click in the job name - * the dropdown should not be closed and the link should open in another tab, - * so we stop propagation of the click event inside the dropdown. + methods: { + /** + * When the user right clicks or cmd/ctrl + click in the job name or the action icon + * the dropdown should not be closed so we stop propagation + * of the click event inside the dropdown. * * Since this component is rendered multiple times per page we need to guarantee we only * target the click event of this component. */ - stopDropdownClickPropagation() { - $(this.$el - .querySelectorAll('.js-grouped-pipeline-dropdown a.mini-pipeline-graph-dropdown-item')) - .on('click', (e) => { - e.stopPropagation(); - }); - }, + stopDropdownClickPropagation() { + $( + '.js-grouped-pipeline-dropdown button, .js-grouped-pipeline-dropdown a.mini-pipeline-graph-dropdown-item', + this.$el, + ).on('click', e => { + e.stopPropagation(); + }); }, - }; + }, +}; </script> <template> <div class="ci-job-dropdown-container"> @@ -101,8 +107,8 @@ :key="i"> <job-component :job="item" - :is-dropdown="true" css-class-job-name="mini-pipeline-graph-dropdown-item" + :request-finished-for="requestFinishedFor" /> </li> </ul> diff --git a/app/assets/javascripts/pipelines/components/graph/graph_component.vue b/app/assets/javascripts/pipelines/components/graph/graph_component.vue index ac9ce7e47d6..7b8a5edcbff 100644 --- a/app/assets/javascripts/pipelines/components/graph/graph_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/graph_component.vue @@ -7,7 +7,6 @@ export default { StageColumnComponent, LoadingIcon, }, - props: { isLoading: { type: Boolean, @@ -17,10 +16,10 @@ export default { type: Object, required: true, }, - actionDisabled: { + requestFinishedFor: { type: String, required: false, - default: null, + default: '', }, }, @@ -75,7 +74,7 @@ export default { :key="stage.name" :stage-connector-class="stageConnectorClass(index, stage)" :is-first-column="isFirstColumn(index)" - :action-disabled="actionDisabled" + :request-finished-for="requestFinishedFor" /> </ul> </div> diff --git a/app/assets/javascripts/pipelines/components/graph/job_component.vue b/app/assets/javascripts/pipelines/components/graph/job_component.vue index c6e5ae6df41..4fcd4b79f4a 100644 --- a/app/assets/javascripts/pipelines/components/graph/job_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/job_component.vue @@ -1,6 +1,5 @@ <script> import ActionComponent from './action_component.vue'; -import DropdownActionComponent from './dropdown_action_component.vue'; import JobNameComponent from './job_name_component.vue'; import tooltip from '../../../vue_shared/directives/tooltip'; @@ -32,10 +31,8 @@ import tooltip from '../../../vue_shared/directives/tooltip'; export default { components: { ActionComponent, - DropdownActionComponent, JobNameComponent, }, - directives: { tooltip, }, @@ -44,26 +41,17 @@ export default { type: Object, required: true, }, - cssClassJobName: { type: String, required: false, default: '', }, - - isDropdown: { - type: Boolean, - required: false, - default: false, - }, - - actionDisabled: { + requestFinishedFor: { type: String, required: false, - default: null, + default: '', }, }, - computed: { status() { return this.job && this.job.status ? this.job.status : {}; @@ -134,19 +122,11 @@ export default { </div> <action-component - v-if="hasAction && !isDropdown" - :tooltip-text="status.action.title" - :link="status.action.path" - :action-icon="status.action.icon" - :button-disabled="actionDisabled" - /> - - <dropdown-action-component - v-if="hasAction && isDropdown" + v-if="hasAction" :tooltip-text="status.action.title" :link="status.action.path" :action-icon="status.action.icon" - :action-method="status.action.method" + :request-finished-for="requestFinishedFor" /> </div> </template> diff --git a/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue b/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue index f6e6569e15b..5461fdbbadd 100644 --- a/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue @@ -29,10 +29,11 @@ export default { required: false, default: '', }, - actionDisabled: { + + requestFinishedFor: { type: String, required: false, - default: null, + default: '', }, }, @@ -74,12 +75,12 @@ export default { v-if="job.size === 1" :job="job" css-class-job-name="build-content" - :action-disabled="actionDisabled" /> <dropdown-job-component v-if="job.size > 1" :job="job" + :request-finished-for="requestFinishedFor" /> </li> diff --git a/app/assets/javascripts/pipelines/pipeline_details_bundle.js b/app/assets/javascripts/pipelines/pipeline_details_bundle.js index 900eb7855f4..6584f96130b 100644 --- a/app/assets/javascripts/pipelines/pipeline_details_bundle.js +++ b/app/assets/javascripts/pipelines/pipeline_details_bundle.js @@ -25,7 +25,7 @@ export default () => { data() { return { mediator, - actionDisabled: null, + requestFinishedFor: null, }; }, created() { @@ -36,15 +36,17 @@ export default () => { }, methods: { postAction(action) { - this.actionDisabled = action; + // Click was made, reset this variable + this.requestFinishedFor = null; - this.mediator.service.postAction(action) + this.mediator.service + .postAction(action) .then(() => { this.mediator.refreshPipeline(); - this.actionDisabled = null; + this.requestFinishedFor = action; }) .catch(() => { - this.actionDisabled = null; + this.requestFinishedFor = action; Flash(__('An error occurred while making the request.')); }); }, @@ -54,7 +56,7 @@ export default () => { props: { isLoading: this.mediator.state.isLoading, pipeline: this.mediator.store.state.pipeline, - actionDisabled: this.actionDisabled, + requestFinishedFor: this.requestFinishedFor, }, }); }, @@ -79,7 +81,8 @@ export default () => { }, methods: { postAction(action) { - this.mediator.service.postAction(action.path) + this.mediator.service + .postAction(action.path) .then(() => this.mediator.refreshPipeline()) .catch(() => Flash(__('An error occurred while making the request.'))); }, diff --git a/app/assets/javascripts/vue_shared/components/ci_badge_link.vue b/app/assets/javascripts/vue_shared/components/ci_badge_link.vue index 5324d5dc797..0d64efcbf68 100644 --- a/app/assets/javascripts/vue_shared/components/ci_badge_link.vue +++ b/app/assets/javascripts/vue_shared/components/ci_badge_link.vue @@ -1,52 +1,52 @@ <script> - import ciIcon from './ci_icon.vue'; - import tooltip from '../directives/tooltip'; - /** - * Renders CI Badge link with CI icon and status text based on - * API response shared between all places where it is used. - * - * Receives status object containing: - * status: { - * details_path: "/gitlab-org/gitlab-ce/pipelines/8150156" // url - * group:"running" // used for CSS class - * icon: "icon_status_running" // used to render the icon - * label:"running" // used for potential tooltip - * text:"running" // text rendered - * } - * - * Used in: - * - Pipelines table - first column - * - Jobs table - first column - * - Pipeline show view - header - * - Job show view - header - * - MR widget - */ +import CiIcon from './ci_icon.vue'; +import tooltip from '../directives/tooltip'; +/** + * Renders CI Badge link with CI icon and status text based on + * API response shared between all places where it is used. + * + * Receives status object containing: + * status: { + * details_path: "/gitlab-org/gitlab-ce/pipelines/8150156" // url + * group:"running" // used for CSS class + * icon: "icon_status_running" // used to render the icon + * label:"running" // used for potential tooltip + * text:"running" // text rendered + * } + * + * Used in: + * - Pipelines table - first column + * - Jobs table - first column + * - Pipeline show view - header + * - Job show view - header + * - MR widget + */ - export default { - components: { - ciIcon, +export default { + components: { + CiIcon, + }, + directives: { + tooltip, + }, + props: { + status: { + type: Object, + required: true, }, - directives: { - tooltip, + showText: { + type: Boolean, + required: false, + default: true, }, - props: { - status: { - type: Object, - required: true, - }, - showText: { - type: Boolean, - required: false, - default: true, - }, + }, + computed: { + cssClass() { + const className = this.status.group; + return className ? `ci-status ci-${className}` : 'ci-status'; }, - computed: { - cssClass() { - const className = this.status.group; - return className ? `ci-status ci-${className}` : 'ci-status'; - }, - }, - }; + }, +}; </script> <template> <a diff --git a/app/assets/javascripts/vue_shared/components/ci_icon.vue b/app/assets/javascripts/vue_shared/components/ci_icon.vue index 8fea746f4de..fcab8f571dd 100644 --- a/app/assets/javascripts/vue_shared/components/ci_icon.vue +++ b/app/assets/javascripts/vue_shared/components/ci_icon.vue @@ -1,45 +1,44 @@ <script> - import icon from '../../vue_shared/components/icon.vue'; +import Icon from '../../vue_shared/components/icon.vue'; - /** - * Renders CI icon based on API response shared between all places where it is used. - * - * Receives status object containing: - * status: { - * details_path: "/gitlab-org/gitlab-ce/pipelines/8150156" // url - * group:"running" // used for CSS class - * icon: "icon_status_running" // used to render the icon - * label:"running" // used for potential tooltip - * text:"running" // text rendered - * } - * - * Used in: - * - Pipelines table Badge - * - Pipelines table mini graph - * - Pipeline graph - * - Pipeline show view badge - * - Jobs table - * - Jobs show view header - * - Jobs show view sidebar - */ - export default { - components: { - icon, +/** + * Renders CI icon based on API response shared between all places where it is used. + * + * Receives status object containing: + * status: { + * details_path: "/gitlab-org/gitlab-ce/pipelines/8150156" // url + * group:"running" // used for CSS class + * icon: "icon_status_running" // used to render the icon + * label:"running" // used for potential tooltip + * text:"running" // text rendered + * } + * + * Used in: + * - Pipelines table Badge + * - Pipelines table mini graph + * - Pipeline graph + * - Pipeline show view badge + * - Jobs table + * - Jobs show view header + * - Jobs show view sidebar + */ +export default { + components: { + Icon, + }, + props: { + status: { + type: Object, + required: true, }, - props: { - status: { - type: Object, - required: true, - }, + }, + computed: { + cssClass() { + const status = this.status.group; + return `ci-status-icon ci-status-icon-${status} js-ci-status-icon-${status}`; }, - - computed: { - cssClass() { - const status = this.status.group; - return `ci-status-icon ci-status-icon-${status} js-ci-status-icon-${status}`; - }, - }, - }; + }, +}; </script> <template> <span :class="cssClass"> diff --git a/app/assets/javascripts/vue_shared/components/clipboard_button.vue b/app/assets/javascripts/vue_shared/components/clipboard_button.vue index cab126a7eca..cb2cc3901ad 100644 --- a/app/assets/javascripts/vue_shared/components/clipboard_button.vue +++ b/app/assets/javascripts/vue_shared/components/clipboard_button.vue @@ -1,40 +1,50 @@ <script> - /** - * Falls back to the code used in `copy_to_clipboard.js` - */ - import tooltip from '../directives/tooltip'; +/** + * Falls back to the code used in `copy_to_clipboard.js` + * + * Renders a button with a clipboard icon that copies the content of `data-clipboard-text` + * when clicked. + * + * @example + * <clipboard-button + * title="Copy to clipbard" + * text="Content to be copied" + * css-class="btn-transparent" + * /> + */ +import tooltip from '../directives/tooltip'; - export default { - name: 'ClipboardButton', - directives: { - tooltip, +export default { + name: 'ClipboardButton', + directives: { + tooltip, + }, + props: { + text: { + type: String, + required: true, }, - props: { - text: { - type: String, - required: true, - }, - title: { - type: String, - required: true, - }, - tooltipPlacement: { - type: String, - required: false, - default: 'top', - }, - tooltipContainer: { - type: [String, Boolean], - required: false, - default: false, - }, - cssClass: { - type: String, - required: false, - default: 'btn-default', - }, + title: { + type: String, + required: true, }, - }; + tooltipPlacement: { + type: String, + required: false, + default: 'top', + }, + tooltipContainer: { + type: [String, Boolean], + required: false, + default: false, + }, + cssClass: { + type: String, + required: false, + default: 'btn-default', + }, + }, +}; </script> <template> diff --git a/app/assets/javascripts/vue_shared/components/commit.vue b/app/assets/javascripts/vue_shared/components/commit.vue index b8875d04488..8f250a6c989 100644 --- a/app/assets/javascripts/vue_shared/components/commit.vue +++ b/app/assets/javascripts/vue_shared/components/commit.vue @@ -1,119 +1,111 @@ <script> - import commitIconSvg from 'icons/_icon_commit.svg'; - import userAvatarLink from './user_avatar/user_avatar_link.vue'; - import tooltip from '../directives/tooltip'; - import icon from '../../vue_shared/components/icon.vue'; +import UserAvatarLink from './user_avatar/user_avatar_link.vue'; +import tooltip from '../directives/tooltip'; +import Icon from '../../vue_shared/components/icon.vue'; - export default { - directives: { - tooltip, +export default { + directives: { + tooltip, + }, + components: { + UserAvatarLink, + Icon, + }, + props: { + /** + * Indicates the existance of a tag. + * Used to render the correct icon, if true will render `fa-tag` icon, + * if false will render a svg sprite fork icon + */ + tag: { + type: Boolean, + required: false, + default: false, }, - components: { - userAvatarLink, - icon, + /** + * If provided is used to render the branch name and url. + * Should contain the following properties: + * name + * ref_url + */ + commitRef: { + type: Object, + required: false, + default: () => ({}), + }, + /** + * Used to link to the commit sha. + */ + commitUrl: { + type: String, + required: false, + default: '', }, - props: { - /** - * Indicates the existance of a tag. - * Used to render the correct icon, if true will render `fa-tag` icon, - * if false will render a svg sprite fork icon - */ - tag: { - type: Boolean, - required: false, - default: false, - }, - /** - * If provided is used to render the branch name and url. - * Should contain the following properties: - * name - * ref_url - */ - commitRef: { - type: Object, - required: false, - default: () => ({}), - }, - /** - * Used to link to the commit sha. - */ - commitUrl: { - type: String, - required: false, - default: '', - }, - /** - * Used to show the commit short sha that links to the commit url. - */ - shortSha: { - type: String, - required: false, - default: '', - }, - /** - * If provided shows the commit tile. - */ - title: { - type: String, - required: false, - default: '', - }, - /** - * If provided renders information about the author of the commit. - * When provided should include: - * `avatar_url` to render the avatar icon - * `web_url` to link to user profile - * `username` to render alt and title tags - */ - author: { - type: Object, - required: false, - default: () => ({}), - }, - showBranch: { - type: Boolean, - required: false, - default: true, - }, + /** + * Used to show the commit short sha that links to the commit url. + */ + shortSha: { + type: String, + required: false, + default: '', + }, + /** + * If provided shows the commit tile. + */ + title: { + type: String, + required: false, + default: '', + }, + /** + * If provided renders information about the author of the commit. + * When provided should include: + * `avatar_url` to render the avatar icon + * `web_url` to link to user profile + * `username` to render alt and title tags + */ + author: { + type: Object, + required: false, + default: () => ({}), + }, + showBranch: { + type: Boolean, + required: false, + default: true, }, - computed: { - /** - * Used to verify if all the properties needed to render the commit - * ref section were provided. - * - * @returns {Boolean} - */ - hasCommitRef() { - return this.commitRef && this.commitRef.name && this.commitRef.ref_url; - }, - /** - * Used to verify if all the properties needed to render the commit - * author section were provided. - * - * @returns {Boolean} - */ - hasAuthor() { - return this.author && - this.author.avatar_url && - this.author.path && - this.author.username; - }, - /** - * If information about the author is provided will return a string - * to be rendered as the alt attribute of the img tag. - * - * @returns {String} - */ - userImageAltDescription() { - return this.author && - this.author.username ? `${this.author.username}'s avatar` : null; - }, + }, + computed: { + /** + * Used to verify if all the properties needed to render the commit + * ref section were provided. + * + * @returns {Boolean} + */ + hasCommitRef() { + return this.commitRef && this.commitRef.name && this.commitRef.ref_url; }, - created() { - this.commitIconSvg = commitIconSvg; + /** + * Used to verify if all the properties needed to render the commit + * author section were provided. + * + * @returns {Boolean} + */ + hasAuthor() { + return this.author && this.author.avatar_url && this.author.path && this.author.username; }, - }; + /** + * If information about the author is provided will return a string + * to be rendered as the alt attribute of the img tag. + * + * @returns {String} + */ + userImageAltDescription() { + return this.author && this.author.username ? `${this.author.username}'s avatar` : null; + }, + }, +}; </script> <template> <div class="branch-commit"> @@ -141,11 +133,10 @@ {{ commitRef.name }} </a> </template> - <div - v-html="commitIconSvg" + <icon + name="commit" class="commit-icon js-commit-icon" - > - </div> + /> <a class="commit-sha" diff --git a/app/assets/javascripts/vue_shared/components/expand_button.vue b/app/assets/javascripts/vue_shared/components/expand_button.vue index c943c8d98a4..9295be3e2b2 100644 --- a/app/assets/javascripts/vue_shared/components/expand_button.vue +++ b/app/assets/javascripts/vue_shared/components/expand_button.vue @@ -1,33 +1,33 @@ <script> - import { __ } from '~/locale'; - /** - * Port of detail_behavior expand button. - * - * @example - * <expand-button> - * <template slot="expanded"> - * Text goes here. - * </template> - * </expand-button> - */ - export default { - name: 'ExpandButton', - data() { - return { - isCollapsed: true, - }; +import { __ } from '~/locale'; +/** + * Port of detail_behavior expand button. + * + * @example + * <expand-button> + * <template slot="expanded"> + * Text goes here. + * </template> + * </expand-button> + */ +export default { + name: 'ExpandButton', + data() { + return { + isCollapsed: true, + }; + }, + computed: { + ariaLabel() { + return __('Click to expand text'); }, - computed: { - ariaLabel() { - return __('Click to expand text'); - }, + }, + methods: { + onClick() { + this.isCollapsed = !this.isCollapsed; }, - methods: { - onClick() { - this.isCollapsed = !this.isCollapsed; - }, - }, - }; + }, +}; </script> <template> <span> diff --git a/app/assets/javascripts/vue_shared/components/header_ci_component.vue b/app/assets/javascripts/vue_shared/components/header_ci_component.vue index a0cd0cbd200..088187ed348 100644 --- a/app/assets/javascripts/vue_shared/components/header_ci_component.vue +++ b/app/assets/javascripts/vue_shared/components/header_ci_component.vue @@ -1,78 +1,78 @@ <script> - import ciIconBadge from './ci_badge_link.vue'; - import loadingIcon from './loading_icon.vue'; - import timeagoTooltip from './time_ago_tooltip.vue'; - import tooltip from '../directives/tooltip'; - import userAvatarImage from './user_avatar/user_avatar_image.vue'; +import CiIconBadge from './ci_badge_link.vue'; +import LoadingIcon from './loading_icon.vue'; +import TimeagoTooltip from './time_ago_tooltip.vue'; +import tooltip from '../directives/tooltip'; +import UserAvatarImage from './user_avatar/user_avatar_image.vue'; - /** - * Renders header component for job and pipeline page based on UI mockups - * - * Used in: - * - job show page - * - pipeline show page - */ - export default { - components: { - ciIconBadge, - loadingIcon, - timeagoTooltip, - userAvatarImage, +/** + * Renders header component for job and pipeline page based on UI mockups + * + * Used in: + * - job show page + * - pipeline show page + */ +export default { + components: { + CiIconBadge, + LoadingIcon, + TimeagoTooltip, + UserAvatarImage, + }, + directives: { + tooltip, + }, + props: { + status: { + type: Object, + required: true, }, - directives: { - tooltip, + itemName: { + type: String, + required: true, }, - props: { - status: { - type: Object, - required: true, - }, - itemName: { - type: String, - required: true, - }, - itemId: { - type: Number, - required: true, - }, - time: { - type: String, - required: true, - }, - user: { - type: Object, - required: false, - default: () => ({}), - }, - actions: { - type: Array, - required: false, - default: () => [], - }, - hasSidebarButton: { - type: Boolean, - required: false, - default: false, - }, - shouldRenderTriggeredLabel: { - type: Boolean, - required: false, - default: true, - }, + itemId: { + type: Number, + required: true, }, + time: { + type: String, + required: true, + }, + user: { + type: Object, + required: false, + default: () => ({}), + }, + actions: { + type: Array, + required: false, + default: () => [], + }, + hasSidebarButton: { + type: Boolean, + required: false, + default: false, + }, + shouldRenderTriggeredLabel: { + type: Boolean, + required: false, + default: true, + }, + }, - computed: { - userAvatarAltText() { - return `${this.user.name}'s avatar`; - }, + computed: { + userAvatarAltText() { + return `${this.user.name}'s avatar`; }, + }, - methods: { - onClickAction(action) { - this.$emit('actionClicked', action); - }, + methods: { + onClickAction(action) { + this.$emit('actionClicked', action); }, - }; + }, +}; </script> <template> diff --git a/app/assets/javascripts/vue_shared/components/icon.vue b/app/assets/javascripts/vue_shared/components/icon.vue index 6a2e05000e1..1a0df49bc29 100644 --- a/app/assets/javascripts/vue_shared/components/icon.vue +++ b/app/assets/javascripts/vue_shared/components/icon.vue @@ -1,76 +1,75 @@ <script> +/* This is a re-usable vue component for rendering a svg sprite + icon - /* This is a re-usable vue component for rendering a svg sprite - icon + Sample configuration: - Sample configuration: + <icon + name="retry" + :size="32" + css-classes="top" + /> - <icon - name="retry" - :size="32" - css-classes="top" - /> +*/ +// only allow classes in images.scss e.g. s12 +const validSizes = [8, 12, 16, 18, 24, 32, 48, 72]; - */ - // only allow classes in images.scss e.g. s12 - const validSizes = [8, 12, 16, 18, 24, 32, 48, 72]; - - export default { - props: { - name: { - type: String, - required: true, - }, +export default { + props: { + name: { + type: String, + required: true, + }, - size: { - type: Number, - required: false, - default: 16, - validator(value) { - return validSizes.includes(value); - }, + size: { + type: Number, + required: false, + default: 16, + validator(value) { + return validSizes.includes(value); }, + }, - cssClasses: { - type: String, - required: false, - default: '', - }, + cssClasses: { + type: String, + required: false, + default: '', + }, - width: { - type: Number, - required: false, - default: null, - }, + width: { + type: Number, + required: false, + default: null, + }, - height: { - type: Number, - required: false, - default: null, - }, + height: { + type: Number, + required: false, + default: null, + }, - y: { - type: Number, - required: false, - default: null, - }, + y: { + type: Number, + required: false, + default: null, + }, - x: { - type: Number, - required: false, - default: null, - }, + x: { + type: Number, + required: false, + default: null, }, + }, - computed: { - spriteHref() { - return `${gon.sprite_icons}#${this.name}`; - }, - iconSizeClass() { - return this.size ? `s${this.size}` : ''; - }, + computed: { + spriteHref() { + return `${gon.sprite_icons}#${this.name}`; + }, + iconSizeClass() { + return this.size ? `s${this.size}` : ''; }, - }; + }, +}; </script> <template> @@ -79,7 +78,8 @@ :width="width" :height="height" :x="x" - :y="y"> + :y="y" + > <use v-bind="{ 'xlink:href':spriteHref }" /> </svg> </template> diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/base.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/base.vue index 5ede53d8d01..70b46a9c2bb 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/base.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/base.vue @@ -1,4 +1,5 @@ <script> +import $ from 'jquery'; import { __ } from '~/locale'; import LabelsSelect from '~/labels_select'; import LoadingIcon from '../../loading_icon.vue'; @@ -98,11 +99,18 @@ export default { this.labelsDropdown = new LabelsSelect(this.$refs.dropdownButton, { handleClick: this.handleClick, }); + $(this.$refs.dropdown).on('hidden.gl.dropdown', this.handleDropdownHidden); }, methods: { handleClick(label) { this.$emit('onLabelClick', label); }, + handleCollapsedValueClick() { + this.$emit('toggleCollapse'); + }, + handleDropdownHidden() { + this.$emit('onDropdownClose'); + }, }, }; </script> @@ -112,6 +120,7 @@ export default { <dropdown-value-collapsed v-if="showCreate" :labels="context.labels" + @onValueClick="handleCollapsedValueClick" /> <dropdown-title :can-edit="canEdit" @@ -133,7 +142,10 @@ export default { :name="hiddenInputName" :label="label" /> - <div class="dropdown"> + <div + class="dropdown" + ref="dropdown" + > <dropdown-button :ability-name="abilityName" :field-name="hiddenInputName" diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue index 5cf728fe050..68fa2ab8d01 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue @@ -26,6 +26,11 @@ export default { return labelsString; }, }, + methods: { + handleClick() { + this.$emit('onValueClick'); + }, + }, }; </script> @@ -36,6 +41,7 @@ export default { data-placement="left" data-container="body" :title="labelsList" + @click="handleClick" > <i aria-hidden="true" diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 855ebf7d86d..3a8ec779c14 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -468,6 +468,14 @@ margin-bottom: 10px; white-space: normal; + .ci-job-dropdown-container { + // override dropdown.scss + .dropdown-menu li button { + padding: 0; + text-align: center; + } + } + // ensure .build-content has hover style when action-icon is hovered .ci-job-dropdown-container:hover .build-content { @extend .build-content:hover; diff --git a/app/finders/users_finder.rb b/app/finders/users_finder.rb index edde8022ec9..65824a51919 100644 --- a/app/finders/users_finder.rb +++ b/app/finders/users_finder.rb @@ -32,6 +32,7 @@ class UsersFinder users = by_active(users) users = by_external_identity(users) users = by_external(users) + users = by_2fa(users) users = by_created_at(users) users = by_custom_attributes(users) @@ -76,4 +77,15 @@ class UsersFinder users.external end + + def by_2fa(users) + case params[:two_factor] + when 'enabled' + users.with_two_factor + when 'disabled' + users.without_two_factor + else + users + end + end end diff --git a/app/models/concerns/routable.rb b/app/models/concerns/routable.rb index dfd7d94450b..915ad6959be 100644 --- a/app/models/concerns/routable.rb +++ b/app/models/concerns/routable.rb @@ -102,7 +102,7 @@ module Routable # the route. Caching this per request ensures that even if we have multiple instances, # we will not have to duplicate work, avoiding N+1 queries in some cases. def full_path - return uncached_full_path unless RequestStore.active? + return uncached_full_path unless RequestStore.active? && persisted? RequestStore[full_path_key] ||= uncached_full_path end @@ -124,6 +124,11 @@ module Routable end end + # Group would override this to check from association + def owned_by?(user) + owner == user + end + private def set_path_errors diff --git a/app/models/group.rb b/app/models/group.rb index 95e2c3a8aab..f21008e5f75 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -130,6 +130,10 @@ class Group < Namespace self[:lfs_enabled] end + def owned_by?(user) + owners.include?(user) + end + def add_users(users, access_level, current_user: nil, expires_at: nil) GroupMember.add_users( self, diff --git a/app/models/project_services/flowdock_service.rb b/app/models/project_services/flowdock_service.rb index 4d23a17a545..da01ac1b7cf 100644 --- a/app/models/project_services/flowdock_service.rb +++ b/app/models/project_services/flowdock_service.rb @@ -1,5 +1,51 @@ require "flowdock-git-hook" +# Flow dock depends on Grit to compute the number of commits between two given +# commits. To make this depend on Gitaly, a monkey patch is applied +module Flowdock + class Git + # pass down a Repository all the way down + def repo + @options[:repo] + end + + def config + {} + end + + def messages + Git::Builder.new(repo: repo, + ref: @ref, + before: @from, + after: @to, + commit_url: @commit_url, + branch_url: @branch_url, + diff_url: @diff_url, + repo_url: @repo_url, + repo_name: @repo_name, + permanent_refs: @permanent_refs, + tags: tags + ).to_hashes + end + + class Builder + def commits + @repo.commits_between(@before, @after).map do |commit| + { + url: @opts[:commit_url] ? @opts[:commit_url] % [commit.sha] : nil, + id: commit.sha, + message: commit.message, + author: { + name: commit.author_name, + email: commit.author_email + } + } + end + end + end + end +end + class FlowdockService < Service prop_accessor :token validates :token, presence: true, if: :activated? @@ -34,7 +80,7 @@ class FlowdockService < Service data[:before], data[:after], token: token, - repo: project.repository.path_to_repo, + repo: project.repository, repo_url: "#{Gitlab.config.gitlab.url}/#{project.full_path}", commit_url: "#{Gitlab.config.gitlab.url}/#{project.full_path}/commit/%s", diff_url: "#{Gitlab.config.gitlab.url}/#{project.full_path}/compare/%s...%s" diff --git a/app/presenters/project_presenter.rb b/app/presenters/project_presenter.rb index 484ac64580d..63ead5538cb 100644 --- a/app/presenters/project_presenter.rb +++ b/app/presenters/project_presenter.rb @@ -260,7 +260,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated if current_user && can?(current_user, :admin_pipeline, project) && repository.gitlab_ci_yml.blank? && !show_auto_devops_callout OpenStruct.new(enabled: auto_devops_enabled?, label: auto_devops_enabled? ? _('Auto DevOps enabled') : _('Enable Auto DevOps'), - link: project_settings_ci_cd_path(project, anchor: 'js-general-pipeline-settings')) + link: project_settings_ci_cd_path(project, anchor: 'autodevops-settings')) elsif auto_devops_enabled? OpenStruct.new(enabled: true, label: _('Auto DevOps enabled'), diff --git a/app/views/admin/application_settings/_repository_storage.html.haml b/app/views/admin/application_settings/_repository_storage.html.haml index ac31977e1a9..4eebb59110a 100644 --- a/app/views/admin/application_settings/_repository_storage.html.haml +++ b/app/views/admin/application_settings/_repository_storage.html.haml @@ -21,7 +21,7 @@ .help-block Manage repository storage paths. Learn more in the = succeed "." do - = link_to "repository storages documentation", help_page_path("administration/repository_storages") + = link_to "repository storages documentation", help_page_path("administration/repository_storage_paths") .sub-section %h4 Circuit breaker .form-group diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml index b15fe514a08..2f69da593cd 100644 --- a/app/views/projects/empty.html.haml +++ b/app/views/projects/empty.html.haml @@ -22,7 +22,7 @@ %hr %p - - link_to_auto_devops_settings = link_to(s_('AutoDevOps|enable Auto DevOps (Beta)'), project_settings_ci_cd_path(@project, anchor: 'js-general-pipeline-settings')) + - link_to_auto_devops_settings = link_to(s_('AutoDevOps|enable Auto DevOps (Beta)'), project_settings_ci_cd_path(@project, anchor: 'autodevops-settings')) - link_to_add_kubernetes_cluster = link_to(s_('AutoDevOps|add a Kubernetes cluster'), new_project_cluster_path(@project)) = s_('AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}.').html_safe % { link_to_auto_devops_settings: link_to_auto_devops_settings, link_to_add_kubernetes_cluster: link_to_add_kubernetes_cluster } diff --git a/app/views/projects/settings/ci_cd/_autodevops_form.html.haml b/app/views/projects/settings/ci_cd/_autodevops_form.html.haml new file mode 100644 index 00000000000..7b410101c05 --- /dev/null +++ b/app/views/projects/settings/ci_cd/_autodevops_form.html.haml @@ -0,0 +1,40 @@ +.row.prepend-top-default + .col-lg-12 + = form_for @project, url: project_settings_ci_cd_path(@project) do |f| + = form_errors(@project) + %fieldset.builds-feature + .form-group + - message = auto_devops_warning_message(@project) + - ci_file_formatted = '<code>.gitlab-ci.yml</code>'.html_safe + - if message + %p.settings-message.text-center + = message.html_safe + = f.fields_for :auto_devops_attributes, @auto_devops do |form| + .radio + = form.label :enabled_true do + = form.radio_button :enabled, 'true' + %strong= s_('CICD|Enable Auto DevOps') + %br + = s_('CICD|The Auto DevOps pipeline configuration will be used when there is no %{ci_file} in the project.').html_safe % { ci_file: ci_file_formatted } + + .radio + = form.label :enabled_false do + = form.radio_button :enabled, 'false' + %strong= s_('CICD|Disable Auto DevOps') + %br + = s_('CICD|An explicit %{ci_file} needs to be specified before you can begin using Continuous Integration and Delivery.').html_safe % { ci_file: ci_file_formatted } + + .radio + = form.label :enabled_ do + = form.radio_button :enabled, '' + %strong= s_('CICD|Instance default (%{state})') % { state: "#{Gitlab::CurrentSettings.auto_devops_enabled? ? _('enabled') : _('disabled')}" } + %br + = s_('CICD|Follow the instance default to either have Auto DevOps enabled or disabled when there is no project specific %{ci_file}.').html_safe % { ci_file: ci_file_formatted } + + = form.label :domain, class:"prepend-top-10" do + = _('Domain') + = form.text_field :domain, class: 'form-control', placeholder: 'domain.com' + .help-block + = s_('CICD|You need to specify a domain if you want to use Auto Review Apps and Auto Deploy stages.') + + = f.submit 'Save changes', class: "btn btn-success prepend-top-15" diff --git a/app/views/projects/settings/ci_cd/_form.html.haml b/app/views/projects/settings/ci_cd/_form.html.haml index 20868f9ba5d..80c226ad273 100644 --- a/app/views/projects/settings/ci_cd/_form.html.haml +++ b/app/views/projects/settings/ci_cd/_form.html.haml @@ -3,44 +3,6 @@ = form_for @project, url: project_settings_ci_cd_path(@project) do |f| = form_errors(@project) %fieldset.builds-feature - .form-group - %h5 Auto DevOps (Beta) - %p - Auto DevOps will automatically build, test, and deploy your application based on a predefined Continuous Integration and Delivery configuration. - = link_to 'Learn more about Auto DevOps', help_page_path('topics/autodevops/index.md') - - message = auto_devops_warning_message(@project) - - if message - %p.settings-message.text-center - = message.html_safe - = f.fields_for :auto_devops_attributes, @auto_devops do |form| - .radio - = form.label :enabled_true do - = form.radio_button :enabled, 'true' - %strong Enable Auto DevOps - %br - %span.descr - The Auto DevOps pipeline configuration will be used when there is no <code>.gitlab-ci.yml</code> in the project. - - .radio - = form.label :enabled_false do - = form.radio_button :enabled, 'false' - %strong Disable Auto DevOps - %br - %span.descr - An explicit <code>.gitlab-ci.yml</code> needs to be specified before you can begin using Continuous Integration and Delivery. - - .radio - = form.label :enabled_ do - = form.radio_button :enabled, '' - %strong Instance default (#{Gitlab::CurrentSettings.auto_devops_enabled? ? 'enabled' : 'disabled'}) - %br - %span.descr - Follow the instance default to either have Auto DevOps enabled or disabled when there is no project specific <code>.gitlab-ci.yml</code>. - %p - You need to specify a domain if you want to use Auto Review Apps and Auto Deploy stages. - = form.text_field :domain, class: 'form-control', placeholder: 'domain.com' - - %hr .form-group.append-bottom-default.js-secret-runner-token = f.label :runners_token, "Runner token", class: 'label-light' .form-control.js-secret-value-placeholder diff --git a/app/views/projects/settings/ci_cd/show.html.haml b/app/views/projects/settings/ci_cd/show.html.haml index 09268c9943b..5f596a019f7 100644 --- a/app/views/projects/settings/ci_cd/show.html.haml +++ b/app/views/projects/settings/ci_cd/show.html.haml @@ -12,10 +12,22 @@ %button.btn.js-settings-toggle{ type: 'button' } = expanded ? 'Collapse' : 'Expand' %p - Update your CI/CD configuration, like job timeout or Auto DevOps. + Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report. .settings-content = render 'form' +%section.settings#autodevops-settings.no-animate{ class: ('expanded' if expanded) } + .settings-header + %h4 + = s_('CICD|Auto DevOps (Beta)') + %button.btn.btn-default.js-settings-toggle{ type: 'button' } + = expanded ? _('Collapse') : _('Expand') + %p + = s_('CICD|Auto DevOps will automatically build, test, and deploy your application based on a predefined Continuous Integration and Delivery configuration.') + = link_to s_('CICD|Learn more about Auto DevOps'), help_page_path('topics/autodevops/index.md') + .settings-content + = render 'autodevops_form' + %section.settings.no-animate{ class: ('expanded' if expanded) } .settings-header %h4 diff --git a/app/views/shared/_auto_devops_callout.html.haml b/app/views/shared/_auto_devops_callout.html.haml index e9ac192f5f7..d3fa324e460 100644 --- a/app/views/shared/_auto_devops_callout.html.haml +++ b/app/views/shared/_auto_devops_callout.html.haml @@ -9,7 +9,7 @@ - link = link_to(s_('AutoDevOps|Auto DevOps documentation'), help_page_path('topics/autodevops/index.md'), target: '_blank', rel: 'noopener noreferrer') = s_('AutoDevOps|Learn more in the %{link_to_documentation}').html_safe % { link_to_documentation: link } .banner-buttons - = link_to s_('AutoDevOps|Enable in settings'), project_settings_ci_cd_path(@project, anchor: 'js-general-pipeline-settings'), class: 'btn js-close-callout' + = link_to s_('AutoDevOps|Enable in settings'), project_settings_ci_cd_path(@project, anchor: 'autodevops-settings'), class: 'btn js-close-callout' %button.btn-transparent.banner-close.close.js-close-callout{ type: 'button', 'aria-label' => 'Dismiss Auto DevOps box' } diff --git a/changelogs/unreleased/33697-remove-ujs-action-big-graph.yml b/changelogs/unreleased/33697-remove-ujs-action-big-graph.yml new file mode 100644 index 00000000000..43ce52c1209 --- /dev/null +++ b/changelogs/unreleased/33697-remove-ujs-action-big-graph.yml @@ -0,0 +1,5 @@ +--- +title: Prevent pipeline actions in dropdown to redirct to a new page +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/42936-improve-ns-factory-avoid-duplicates.yml b/changelogs/unreleased/42936-improve-ns-factory-avoid-duplicates.yml new file mode 100644 index 00000000000..54b523b65a1 --- /dev/null +++ b/changelogs/unreleased/42936-improve-ns-factory-avoid-duplicates.yml @@ -0,0 +1,6 @@ +--- +title: Fix discussions API setting created_at for notable in a group or notable in + a project in a group with owners +merge_request: 18464 +author: +type: fixed diff --git a/changelogs/unreleased/43466-make-auto-devops-settings-first-class.yml b/changelogs/unreleased/43466-make-auto-devops-settings-first-class.yml new file mode 100644 index 00000000000..f5c5415159c --- /dev/null +++ b/changelogs/unreleased/43466-make-auto-devops-settings-first-class.yml @@ -0,0 +1,5 @@ +--- +title: Create settings section for autodevops +merge_request: 18321 +author: +type: changed diff --git a/changelogs/unreleased/45576-fix-create-project-for-user-endpoint.yml b/changelogs/unreleased/45576-fix-create-project-for-user-endpoint.yml new file mode 100644 index 00000000000..12631c75b44 --- /dev/null +++ b/changelogs/unreleased/45576-fix-create-project-for-user-endpoint.yml @@ -0,0 +1,5 @@ +--- +title: Fix project creation for user endpoint when jobs_enabled parameter supplied +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/blackst0ne-add-missing-changelog-type-to-docs.yml b/changelogs/unreleased/blackst0ne-add-missing-changelog-type-to-docs.yml new file mode 100644 index 00000000000..f8790fa45aa --- /dev/null +++ b/changelogs/unreleased/blackst0ne-add-missing-changelog-type-to-docs.yml @@ -0,0 +1,5 @@ +--- +title: Add missing changelog type to docs +merge_request: 18526 +author: "@blackst0ne" +type: other diff --git a/changelogs/unreleased/dz-add-2fa-filter-admin-api.yml b/changelogs/unreleased/dz-add-2fa-filter-admin-api.yml new file mode 100644 index 00000000000..df479e69380 --- /dev/null +++ b/changelogs/unreleased/dz-add-2fa-filter-admin-api.yml @@ -0,0 +1,5 @@ +--- +title: Add 2FA filter to users API for admins only +merge_request: 18503 +author: +type: changed diff --git a/changelogs/unreleased/rd-44635-error-500-when-clicking-ghost-user-or-gitlab-support-bot-in-ui.yml b/changelogs/unreleased/rd-44635-error-500-when-clicking-ghost-user-or-gitlab-support-bot-in-ui.yml new file mode 100644 index 00000000000..a08a75ceda6 --- /dev/null +++ b/changelogs/unreleased/rd-44635-error-500-when-clicking-ghost-user-or-gitlab-support-bot-in-ui.yml @@ -0,0 +1,5 @@ +--- +title: Fix missing namespace for some internal users +merge_request: 18357 +author: +type: fixed diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index dc7999ac556..9b00ae459a3 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -1,131 +1,4 @@ -# rubocop:disable GitlabSecurity/PublicSend - -require_dependency Rails.root.join('lib/gitlab') # Load Gitlab as soon as possible - -class Settings < Settingslogic - source ENV.fetch('GITLAB_CONFIG') { "#{Rails.root}/config/gitlab.yml" } - namespace Rails.env - - class << self - def gitlab_on_standard_port? - on_standard_port?(gitlab) - end - - def host_without_www(url) - host(url).sub('www.', '') - end - - def build_gitlab_ci_url - custom_port = - if on_standard_port?(gitlab) - nil - else - ":#{gitlab.port}" - end - - [ - gitlab.protocol, - "://", - gitlab.host, - custom_port, - gitlab.relative_url_root - ].join('') - end - - def build_pages_url - base_url(pages).join('') - end - - def build_gitlab_shell_ssh_path_prefix - user_host = "#{gitlab_shell.ssh_user}@#{gitlab_shell.ssh_host}" - - if gitlab_shell.ssh_port != 22 - "ssh://#{user_host}:#{gitlab_shell.ssh_port}/" - else - if gitlab_shell.ssh_host.include? ':' - "[#{user_host}]:" - else - "#{user_host}:" - end - end - end - - def build_base_gitlab_url - base_url(gitlab).join('') - end - - def build_gitlab_url - (base_url(gitlab) + [gitlab.relative_url_root]).join('') - end - - # check that values in `current` (string or integer) is a contant in `modul`. - def verify_constant_array(modul, current, default) - values = default || [] - unless current.nil? - values = [] - current.each do |constant| - values.push(verify_constant(modul, constant, nil)) - end - values.delete_if { |value| value.nil? } - end - - values - end - - # check that `current` (string or integer) is a contant in `modul`. - def verify_constant(modul, current, default) - constant = modul.constants.find { |name| modul.const_get(name) == current } - value = constant.nil? ? default : modul.const_get(constant) - if current.is_a? String - value = modul.const_get(current.upcase) rescue default - end - - value - end - - def absolute(path) - File.expand_path(path, Rails.root) - end - - private - - def base_url(config) - custom_port = on_standard_port?(config) ? nil : ":#{config.port}" - - [ - config.protocol, - "://", - config.host, - custom_port - ] - end - - def on_standard_port?(config) - config.port.to_i == (config.https ? 443 : 80) - end - - # Extract the host part of the given +url+. - def host(url) - url = url.downcase - url = "http://#{url}" unless url.start_with?('http') - - # Get rid of the path so that we don't even have to encode it - url_without_path = url.sub(%r{(https?://[^/]+)/?.*}, '\1') - - URI.parse(url_without_path).host - end - - # Runs every minute in a random ten-minute period on Sundays, to balance the - # load on the server receiving these pings. The usage ping is safe to run - # multiple times because of a 24 hour exclusive lock. - def cron_for_usage_ping - hour = rand(24) - minute = rand(6) - - "#{minute}0-#{minute}9 #{hour} * * 0" - end - end -end +require_dependency File.expand_path('../../lib/gitlab', __dir__) # Load Gitlab as soon as possible # Default settings Settings['ldap'] ||= Settingslogic.new({}) diff --git a/config/initializers/2_app.rb b/config/initializers/2_app.rb deleted file mode 100644 index bd74f90e7d2..00000000000 --- a/config/initializers/2_app.rb +++ /dev/null @@ -1,8 +0,0 @@ -module Gitlab - def self.config - Settings - end - - VERSION = File.read(Rails.root.join("VERSION")).strip.freeze - REVISION = Gitlab::Popen.popen(%W(#{config.git.bin_path} log --pretty=format:%h -n 1)).first.chomp.freeze -end diff --git a/db/fixtures/development/17_cycle_analytics.rb b/db/fixtures/development/17_cycle_analytics.rb index d7be6f5950f..7b9a4bad449 100644 --- a/db/fixtures/development/17_cycle_analytics.rb +++ b/db/fixtures/development/17_cycle_analytics.rb @@ -1,5 +1,5 @@ require './spec/support/sidekiq' -require './spec/support/test_env' +require './spec/support/helpers/test_env' class Gitlab::Seeder::CycleAnalytics def initialize(project, perf: false) diff --git a/db/migrate/20180413022611_create_missing_namespace_for_internal_users.rb b/db/migrate/20180413022611_create_missing_namespace_for_internal_users.rb new file mode 100644 index 00000000000..8fc558be733 --- /dev/null +++ b/db/migrate/20180413022611_create_missing_namespace_for_internal_users.rb @@ -0,0 +1,66 @@ +class CreateMissingNamespaceForInternalUsers < ActiveRecord::Migration + DOWNTIME = false + + def up + connection.exec_query(users_query.to_sql).rows.each do |id, username| + create_namespace(id, username) + # When testing locally I've noticed that these internal users are missing + # the notification email, for more details visit the below link: + # https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18357#note_68327560 + set_notification_email(id) + end + end + + def down + # no-op + end + + private + + def users + @users ||= Arel::Table.new(:users) + end + + def namespaces + @namespaces ||= Arel::Table.new(:namespaces) + end + + def users_query + condition = users[:ghost].eq(true) + + if column_exists?(:users, :support_bot) + condition = condition.or(users[:support_bot].eq(true)) + end + + users.join(namespaces, Arel::Nodes::OuterJoin) + .on(namespaces[:type].eq(nil).and(namespaces[:owner_id].eq(users[:id]))) + .where(namespaces[:owner_id].eq(nil)) + .where(condition) + .project(users[:id], users[:username]) + end + + def create_namespace(user_id, username) + path = Uniquify.new.string(username) do |str| + query = "SELECT id FROM namespaces WHERE parent_id IS NULL AND path='#{str}' LIMIT 1" + connection.exec_query(query).present? + end + + insert_query = "INSERT INTO namespaces(owner_id, path, name) VALUES(#{user_id}, '#{path}', '#{path}')" + namespace_id = connection.insert_sql(insert_query) + + create_route(namespace_id) + end + + def create_route(namespace_id) + return unless namespace_id + + row = connection.exec_query("SELECT id, path FROM namespaces WHERE id=#{namespace_id}").first + id, path = row.values_at('id', 'path') + + execute("INSERT INTO routes(source_id, source_type, path, name) VALUES(#{id}, 'Namespace', '#{path}', '#{path}')") + end + + def set_notification_email(user_id) + execute "UPDATE users SET notification_email = email WHERE notification_email IS NULL AND id = #{user_id}" + end +end diff --git a/doc/api/project_import_export.md b/doc/api/project_import_export.md index d8f61852b11..085437c801a 100644 --- a/doc/api/project_import_export.md +++ b/doc/api/project_import_export.md @@ -112,13 +112,13 @@ POST /projects/import | `file` | string | yes | The file to be uploaded | | `path` | string | yes | Name and path for new project | | `overwrite` | boolean | no | If there is a project with the same path the import will overwrite it. Default to false | -| `override_params` | Hash | no | Supports all fields defined in the [Project API](projects.md)] | +| `override_params` | Hash | no | Supports all fields defined in the [Project API](projects.md) | -The override params passed will take precendence over all values defined inside the export file. +The override params passed will take precedence over all values defined inside the export file. -To upload a file from your filesystem, use the `--form` argument. This causes +To upload a file from your file system, use the `--form` argument. This causes cURL to post data using the header `Content-Type: multipart/form-data`. -The `file=` parameter must point to a file on your filesystem and be preceded +The `file=` parameter must point to a file on your file system and be preceded by `@`. For example: ```console diff --git a/doc/api/users.md b/doc/api/users.md index a4447e32908..ca5afa04687 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -55,6 +55,7 @@ GET /users | --------- | ---- | -------- | ----------- | | `order_by` | string | no | Return projects ordered by `id`, `name`, `username`, `created_at`, or `updated_at` fields. Default is `id` | | `sort` | string | no | Return projects sorted in `asc` or `desc` order. Default is `desc` | +| `two_factor` | string | no | Filter users by Two-factor authentication. Filter values are `enabled` or `disabled`. By default it returns all users | ```json [ diff --git a/doc/development/changelog.md b/doc/development/changelog.md index d5a4acff481..a9fa5ae834f 100644 --- a/doc/development/changelog.md +++ b/doc/development/changelog.md @@ -22,7 +22,7 @@ The `merge_request` value is a reference to a merge request that adds this entry, and the `author` key is used to give attribution to community contributors. **Both are optional**. The `type` field maps the category of the change, -valid options are: added, fixed, changed, deprecated, removed, security, other. **Type field is mandatory**. +valid options are: added, fixed, changed, deprecated, removed, security, performance, other. **Type field is mandatory**. Community contributors and core team members are encouraged to add their name to the `author` field. GitLab team members **should not**. diff --git a/doc/development/testing_guide/best_practices.md b/doc/development/testing_guide/best_practices.md index df80cd9f584..b77e9b7ff63 100644 --- a/doc/development/testing_guide/best_practices.md +++ b/doc/development/testing_guide/best_practices.md @@ -90,6 +90,25 @@ Finished in 34.51 seconds (files took 0.76702 seconds to load) Note: `live_debug` only works on javascript enabled specs. +### Fast unit tests + +Some classes are well-isolated from Rails and you should be able to test them +without the overhead added by the Rails environment and Bundler's `:default` +group's gem loading. In these cases, you can `require 'fast_spec_helper'` +instead of `require 'spec_helper'` in your test file, and your test should run +really fast since: + +- Gems loading is skipped +- Rails app boot is skipped +- gitlab-shell and Gitaly setup are skipped +- Test repositories setup are skipped + +Note that in some cases, you might have to add some `require_dependency 'foo'` +in your file under test since Rails autoloading is not available in these cases. + +This shouldn't be a problem since explicitely listing dependencies should be +considered a good practice anyway. + ### `let` variables GitLab's RSpec suite has made extensive use of `let` variables to reduce @@ -281,14 +300,13 @@ All fixtures should be be placed under `spec/fixtures/`. RSpec config files are files that change the RSpec config (i.e. `RSpec.configure do |config|` blocks). They should be placed under -`spec/support/config/`. +`spec/support/`. Each file should be related to a specific domain, e.g. -`spec/support/config/capybara.rb`, `spec/support/config/carrierwave.rb`, etc. +`spec/support/capybara.rb`, `spec/support/carrierwave.rb`, etc. -Helpers can be included in the `spec/support/config/rspec.rb` file. If a -helpers module applies only to a certain kind of specs, it should add modifiers -to the `config.include` call. For instance if +If a helpers module applies only to a certain kind of specs, it should add +modifiers to the `config.include` call. For instance if `spec/support/helpers/cycle_analytics_helpers.rb` applies to `:lib` and `type: :model` specs only, you would write the following: @@ -299,6 +317,14 @@ RSpec.configure do |config| end ``` +If a config file only consists of `config.include`, you can add these +`config.include` directly in `spec/spec_helper.rb`. + +For very generic helpers, consider including them in the `spec/support/rspec.rb` +file which is used by the `spec/fast_spec_helper.rb` file. See +[Fast unit tests](#fast-unit-tests) for more details about the +`spec/fast_spec_helper.rb` file. + --- [Return to Testing documentation](index.md) diff --git a/doc/development/testing_guide/testing_rake_tasks.md b/doc/development/testing_guide/testing_rake_tasks.md index 60163f1a230..db8ca87e9f8 100644 --- a/doc/development/testing_guide/testing_rake_tasks.md +++ b/doc/development/testing_guide/testing_rake_tasks.md @@ -9,7 +9,7 @@ At a minimum, requiring the Rake helper will redirect `stdout`, include the runtime task helpers, and include the `RakeHelpers` Spec support module. The `RakeHelpers` module exposes a `run_rake_task(<task>)` method to make -executing tasks simple. See `spec/support/rake_helpers.rb` for all available +executing tasks simple. See `spec/support/helpers/rake_helpers.rb` for all available methods. Example: diff --git a/doc/user/project/deploy_tokens/index.md b/doc/user/project/deploy_tokens/index.md index 86fc58020e8..34154b69d2d 100644 --- a/doc/user/project/deploy_tokens/index.md +++ b/doc/user/project/deploy_tokens/index.md @@ -23,7 +23,7 @@ You can create as many deploy tokens as you like from the settings of your proje ![Personal access tokens page](img/deploy_tokens.png) -## Revoking a personal access token +## Revoking a deploy token At any time, you can revoke any deploy token by just clicking the respective **Revoke** button under the 'Active deploy tokens' area. diff --git a/features/support/env.rb b/features/support/env.rb index 15211995918..8fa2fcb6e3e 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -12,7 +12,11 @@ end WebMock.enable! -%w(select2_helper test_env repo_helpers wait_for_requests sidekiq project_forks_helper webmock).each do |f| +%w(select2_helper test_env repo_helpers wait_for_requests project_forks_helper).each do |f| + require Rails.root.join('spec', 'support', 'helpers', f) +end + +%w(sidekiq webmock).each do |f| require Rails.root.join('spec', 'support', f) end diff --git a/lib/api/helpers/notes_helpers.rb b/lib/api/helpers/notes_helpers.rb index cd91df1ecd8..b74b8149834 100644 --- a/lib/api/helpers/notes_helpers.rb +++ b/lib/api/helpers/notes_helpers.rb @@ -64,8 +64,10 @@ module API authorize! :create_note, noteable parent = noteable_parent(noteable) + if opts[:created_at] - opts.delete(:created_at) unless current_user.admin? || parent.owner == current_user + opts.delete(:created_at) unless + current_user.admin? || parent.owned_by?(current_user) end project = parent if parent.is_a?(Project) diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 51b3b0459f3..8871792060b 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -74,6 +74,11 @@ module API present options[:with].prepare_relation(projects, options), options end + + def translate_params_for_compatibility(params) + params[:builds_enabled] = params.delete(:jobs_enabled) if params.key?(:jobs_enabled) + params + end end resource :users, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do @@ -123,7 +128,7 @@ module API end post do attrs = declared_params(include_missing: false) - attrs[:builds_enabled] = attrs.delete(:jobs_enabled) if attrs.key?(:jobs_enabled) + attrs = translate_params_for_compatibility(attrs) project = ::Projects::CreateService.new(current_user, attrs).execute if project.saved? @@ -155,6 +160,7 @@ module API not_found!('User') unless user attrs = declared_params(include_missing: false) + attrs = translate_params_for_compatibility(attrs) project = ::Projects::CreateService.new(user, attrs).execute if project.saved? @@ -276,7 +282,7 @@ module API authorize! :rename_project, user_project if attrs[:name].present? authorize! :change_visibility_level, user_project if attrs[:visibility].present? - attrs[:builds_enabled] = attrs.delete(:jobs_enabled) if attrs.key?(:jobs_enabled) + attrs = translate_params_for_compatibility(attrs) result = ::Projects::UpdateService.new(user_project, current_user, attrs).execute diff --git a/lib/api/users.rb b/lib/api/users.rb index 3920171205f..14b8a796c8e 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -77,7 +77,7 @@ module API authenticated_as_admin! if params[:external].present? || (params[:extern_uid].present? && params[:provider].present?) unless current_user&.admin? - params.except!(:created_after, :created_before, :order_by, :sort) + params.except!(:created_after, :created_before, :order_by, :sort, :two_factor) end users = UsersFinder.new(current_user, params).execute diff --git a/lib/gitlab.rb b/lib/gitlab.rb index f6629982512..0a167104bf4 100644 --- a/lib/gitlab.rb +++ b/lib/gitlab.rb @@ -1,9 +1,20 @@ -require_dependency 'gitlab/git' +require_dependency 'settings' +require_dependency 'gitlab/popen' module Gitlab + def self.root + Pathname.new(File.expand_path('..', __dir__)) + end + + def self.config + Settings + end + COM_URL = 'https://gitlab.com'.freeze APP_DIRS_PATTERN = %r{^/?(app|config|ee|lib|spec|\(\w*\))} SUBDOMAIN_REGEX = %r{\Ahttps://[a-z0-9]+\.gitlab\.com\z} + VERSION = File.read(root.join("VERSION")).strip.freeze + REVISION = Gitlab::Popen.popen(%W(#{config.git.bin_path} log --pretty=format:%h -n 1)).first.chomp.freeze def self.com? # Check `gl_subdomain?` as well to keep parity with gitlab.com diff --git a/lib/gitlab/git.rb b/lib/gitlab/git.rb index c9abea90d21..e85e87a54af 100644 --- a/lib/gitlab/git.rb +++ b/lib/gitlab/git.rb @@ -1,3 +1,5 @@ +require_dependency 'gitlab/encoding_helper' + module Gitlab module Git # The ID of empty tree. diff --git a/lib/gitlab/wiki/committer_with_hooks.rb b/lib/gitlab/git/committer_with_hooks.rb index 19f0b3814fd..a8a59f998cd 100644 --- a/lib/gitlab/wiki/committer_with_hooks.rb +++ b/lib/gitlab/git/committer_with_hooks.rb @@ -1,5 +1,5 @@ module Gitlab - module Wiki + module Git class CommitterWithHooks < Gollum::Committer attr_reader :gl_wiki @@ -9,6 +9,9 @@ module Gitlab end def commit + # TODO: Remove after 10.8 + return super unless allowed_to_run_hooks? + result = Gitlab::Git::OperationService.new(git_user, gl_wiki.repository).with_branch( @wiki.ref, start_branch_name: @wiki.ref @@ -24,6 +27,11 @@ module Gitlab private + # TODO: Remove after 10.8 + def allowed_to_run_hooks? + @options[:user_id] != 0 && @options[:username].present? + end + def git_user @git_user ||= Gitlab::Git::User.new(@options[:username], @options[:name], diff --git a/lib/gitlab/git/wiki.rb b/lib/gitlab/git/wiki.rb index 821436911ab..84a26fe4a6f 100644 --- a/lib/gitlab/git/wiki.rb +++ b/lib/gitlab/git/wiki.rb @@ -290,7 +290,7 @@ module Gitlab end def committer_with_hooks(commit_details) - Gitlab::Wiki::CommitterWithHooks.new(self, commit_details.to_h) + Gitlab::Git::CommitterWithHooks.new(self, commit_details.to_h) end def with_committer_with_hooks(commit_details, &block) diff --git a/lib/settings.rb b/lib/settings.rb new file mode 100644 index 00000000000..69d637761ea --- /dev/null +++ b/lib/settings.rb @@ -0,0 +1,126 @@ +require 'settingslogic' + +class Settings < Settingslogic + source ENV.fetch('GITLAB_CONFIG') { Pathname.new(File.expand_path('..', __dir__)).join('config/gitlab.yml') } + namespace ENV.fetch('GITLAB_ENV') { Rails.env } + + class << self + def gitlab_on_standard_port? + on_standard_port?(gitlab) + end + + def host_without_www(url) + host(url).sub('www.', '') + end + + def build_gitlab_ci_url + custom_port = + if on_standard_port?(gitlab) + nil + else + ":#{gitlab.port}" + end + + [ + gitlab.protocol, + "://", + gitlab.host, + custom_port, + gitlab.relative_url_root + ].join('') + end + + def build_pages_url + base_url(pages).join('') + end + + def build_gitlab_shell_ssh_path_prefix + user_host = "#{gitlab_shell.ssh_user}@#{gitlab_shell.ssh_host}" + + if gitlab_shell.ssh_port != 22 + "ssh://#{user_host}:#{gitlab_shell.ssh_port}/" + else + if gitlab_shell.ssh_host.include? ':' + "[#{user_host}]:" + else + "#{user_host}:" + end + end + end + + def build_base_gitlab_url + base_url(gitlab).join('') + end + + def build_gitlab_url + (base_url(gitlab) + [gitlab.relative_url_root]).join('') + end + + # check that values in `current` (string or integer) is a contant in `modul`. + def verify_constant_array(modul, current, default) + values = default || [] + unless current.nil? + values = [] + current.each do |constant| + values.push(verify_constant(modul, constant, nil)) + end + values.delete_if { |value| value.nil? } + end + + values + end + + # check that `current` (string or integer) is a contant in `modul`. + def verify_constant(modul, current, default) + constant = modul.constants.find { |name| modul.const_get(name) == current } + value = constant.nil? ? default : modul.const_get(constant) + if current.is_a? String + value = modul.const_get(current.upcase) rescue default + end + + value + end + + def absolute(path) + File.expand_path(path, Rails.root) + end + + private + + def base_url(config) + custom_port = on_standard_port?(config) ? nil : ":#{config.port}" + + [ + config.protocol, + "://", + config.host, + custom_port + ] + end + + def on_standard_port?(config) + config.port.to_i == (config.https ? 443 : 80) + end + + # Extract the host part of the given +url+. + def host(url) + url = url.downcase + url = "http://#{url}" unless url.start_with?('http') + + # Get rid of the path so that we don't even have to encode it + url_without_path = url.sub(%r{(https?://[^/]+)/?.*}, '\1') + + URI.parse(url_without_path).host + end + + # Runs every minute in a random ten-minute period on Sundays, to balance the + # load on the server receiving these pings. The usage ping is safe to run + # multiple times because of a 24 hour exclusive lock. + def cron_for_usage_ping + hour = rand(24) + minute = rand(6) + + "#{minute}0-#{minute}9 #{hour} * * 0" + end + end +end diff --git a/rubocop/spec_helpers.rb b/rubocop/spec_helpers.rb index 6c0f0193b1a..9bf5f1e3b18 100644 --- a/rubocop/spec_helpers.rb +++ b/rubocop/spec_helpers.rb @@ -1,6 +1,6 @@ module RuboCop module SpecHelpers - SPEC_HELPERS = %w[spec_helper.rb rails_helper.rb].freeze + SPEC_HELPERS = %w[fast_spec_helper.rb rails_helper.rb spec_helper.rb].freeze # Returns true if the given node originated from the spec directory. def in_spec?(node) diff --git a/spec/controllers/import/bitbucket_controller_spec.rb b/spec/controllers/import/bitbucket_controller_spec.rb index 2be46049aab..be49b92d23f 100644 --- a/spec/controllers/import/bitbucket_controller_spec.rb +++ b/spec/controllers/import/bitbucket_controller_spec.rb @@ -223,11 +223,12 @@ describe Import::BitbucketController do end context 'user has chosen an existing nested namespace and name for the project', :postgresql do - let(:parent_namespace) { create(:group, name: 'foo', owner: user) } + let(:parent_namespace) { create(:group, name: 'foo') } let(:nested_namespace) { create(:group, name: 'bar', parent: parent_namespace) } let(:test_name) { 'test_name' } before do + parent_namespace.add_owner(user) nested_namespace.add_owner(user) end @@ -273,7 +274,7 @@ describe Import::BitbucketController do context 'user has chosen existent and non-existent nested namespaces and name for the project', :postgresql do let(:test_name) { 'test_name' } - let!(:parent_namespace) { create(:group, name: 'foo', owner: user) } + let!(:parent_namespace) { create(:group, name: 'foo') } before do parent_namespace.add_owner(user) diff --git a/spec/controllers/import/gitlab_controller_spec.rb b/spec/controllers/import/gitlab_controller_spec.rb index e958be077c2..742f4787126 100644 --- a/spec/controllers/import/gitlab_controller_spec.rb +++ b/spec/controllers/import/gitlab_controller_spec.rb @@ -196,10 +196,11 @@ describe Import::GitlabController do end context 'user has chosen an existing nested namespace for the project', :postgresql do - let(:parent_namespace) { create(:group, name: 'foo', owner: user) } + let(:parent_namespace) { create(:group, name: 'foo') } let(:nested_namespace) { create(:group, name: 'bar', parent: parent_namespace) } before do + parent_namespace.add_owner(user) nested_namespace.add_owner(user) end @@ -245,7 +246,7 @@ describe Import::GitlabController do context 'user has chosen existent and non-existent nested namespaces and name for the project', :postgresql do let(:test_name) { 'test_name' } - let!(:parent_namespace) { create(:group, name: 'foo', owner: user) } + let!(:parent_namespace) { create(:group, name: 'foo') } before do parent_namespace.add_owner(user) diff --git a/spec/controllers/projects/forks_controller_spec.rb b/spec/controllers/projects/forks_controller_spec.rb index c4b32dc3a09..e20623c0ac1 100644 --- a/spec/controllers/projects/forks_controller_spec.rb +++ b/spec/controllers/projects/forks_controller_spec.rb @@ -4,7 +4,11 @@ describe Projects::ForksController do let(:user) { create(:user) } let(:project) { create(:project, :public, :repository) } let(:forked_project) { Projects::ForkService.new(project, user).execute } - let(:group) { create(:group, owner: forked_project.creator) } + let(:group) { create(:group) } + + before do + group.add_owner(user) + end describe 'GET index' do def get_forks diff --git a/spec/factories/commits.rb b/spec/factories/commits.rb index d5d819d862a..818f7b046f6 100644 --- a/spec/factories/commits.rb +++ b/spec/factories/commits.rb @@ -1,4 +1,4 @@ -require_relative '../support/repo_helpers' +require_relative '../support/helpers/repo_helpers' FactoryBot.define do factory :commit do diff --git a/spec/factories/gpg_key_subkeys.rb b/spec/factories/gpg_key_subkeys.rb index 57eaaee345f..6c7db5379a9 100644 --- a/spec/factories/gpg_key_subkeys.rb +++ b/spec/factories/gpg_key_subkeys.rb @@ -1,5 +1,3 @@ -require_relative '../support/gpg_helpers' - FactoryBot.define do factory :gpg_key_subkey do gpg_key diff --git a/spec/factories/gpg_keys.rb b/spec/factories/gpg_keys.rb index b8aabf74221..51b8ddc9934 100644 --- a/spec/factories/gpg_keys.rb +++ b/spec/factories/gpg_keys.rb @@ -1,4 +1,4 @@ -require_relative '../support/gpg_helpers' +require_relative '../support/helpers/gpg_helpers' FactoryBot.define do factory :gpg_key do diff --git a/spec/factories/gpg_signature.rb b/spec/factories/gpg_signature.rb index 4620caff823..b89e6ffc4b3 100644 --- a/spec/factories/gpg_signature.rb +++ b/spec/factories/gpg_signature.rb @@ -1,5 +1,3 @@ -require_relative '../support/gpg_helpers' - FactoryBot.define do factory :gpg_signature do commit_sha { Digest::SHA1.hexdigest(SecureRandom.hex) } diff --git a/spec/factories/groups.rb b/spec/factories/groups.rb index 8c531cf5909..3b354c0d96b 100644 --- a/spec/factories/groups.rb +++ b/spec/factories/groups.rb @@ -5,6 +5,14 @@ FactoryBot.define do type 'Group' owner nil + after(:create) do |group| + if group.owner + # We could remove this after we have proper constraint: + # https://gitlab.com/gitlab-org/gitlab-ce/issues/43292 + raise "Don't set owner for groups, use `group.add_owner(user)` instead" + end + end + trait :public do visibility_level Gitlab::VisibilityLevel::PUBLIC end diff --git a/spec/factories/namespaces.rb b/spec/factories/namespaces.rb index f94b09cff15..6feafa5ece9 100644 --- a/spec/factories/namespaces.rb +++ b/spec/factories/namespaces.rb @@ -2,6 +2,22 @@ FactoryBot.define do factory :namespace do sequence(:name) { |n| "namespace#{n}" } path { name.downcase.gsub(/\s/, '_') } - owner + + # This is a workaround to avoid the user creating another namespace via + # User#ensure_namespace_correct. We should try to remove it and then + # we could remove this workaround + association :owner, factory: :user, strategy: :build + before(:create) do |namespace| + owner = namespace.owner + + if owner + # We're changing the username here because we want to keep our path, + # and User#ensure_namespace_correct would change the path based on + # username, so we're forced to do this otherwise we'll need to change + # a lot of existing tests. + owner.username = namespace.path + owner.namespace = namespace + end + end end end diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb index 857333f222d..40f3fa7d69b 100644 --- a/spec/factories/notes.rb +++ b/spec/factories/notes.rb @@ -1,4 +1,4 @@ -require_relative '../support/repo_helpers' +require_relative '../support/helpers/repo_helpers' include ActionDispatch::TestProcess diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index ad33d09f78a..6b32629eb58 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -1,4 +1,4 @@ -require_relative '../support/test_env' +require_relative '../support/helpers/test_env' FactoryBot.define do # Project without repository diff --git a/spec/fast_spec_helper.rb b/spec/fast_spec_helper.rb new file mode 100644 index 00000000000..f28268b0754 --- /dev/null +++ b/spec/fast_spec_helper.rb @@ -0,0 +1,15 @@ +require 'bundler/setup' + +ENV['GITLAB_ENV'] = 'test' +ENV['IN_MEMORY_APPLICATION_SETTINGS'] = 'true' + +unless Object.respond_to?(:require_dependency) + class Object + alias_method :require_dependency, :require + end +end + +# Defines Gitlab and Gitlab.config which are at the center of the app +require_relative '../lib/gitlab' unless defined?(Gitlab.config) + +require_relative 'support/rspec' diff --git a/spec/features/projects/new_project_spec.rb b/spec/features/projects/new_project_spec.rb index a5954fec54b..fee6287558e 100644 --- a/spec/features/projects/new_project_spec.rb +++ b/spec/features/projects/new_project_spec.rb @@ -64,7 +64,7 @@ feature 'New project' do end context 'with group namespace' do - let(:group) { create(:group, :private, owner: user) } + let(:group) { create(:group, :private) } before do group.add_owner(user) @@ -81,7 +81,7 @@ feature 'New project' do end context 'with subgroup namespace' do - let(:group) { create(:group, owner: user) } + let(:group) { create(:group) } let(:subgroup) { create(:group, parent: group) } before do diff --git a/spec/features/projects/settings/pipelines_settings_spec.rb b/spec/features/projects/settings/pipelines_settings_spec.rb index d9020333f28..e875a88a52b 100644 --- a/spec/features/projects/settings/pipelines_settings_spec.rb +++ b/spec/features/projects/settings/pipelines_settings_spec.rb @@ -8,6 +8,7 @@ describe "Projects > Settings > Pipelines settings" do before do sign_in(user) project.add_role(user, role) + create(:project_auto_devops, project: project) end context 'for developer' do @@ -27,10 +28,17 @@ describe "Projects > Settings > Pipelines settings" do visit project_settings_ci_cd_path(project) fill_in('Test coverage parsing', with: 'coverage_regex') - click_on 'Save changes' + + page.within '#js-general-pipeline-settings' do + click_on 'Save changes' + end expect(page.status_code).to eq(200) - expect(page).to have_button('Save changes', disabled: false) + + page.within '#js-general-pipeline-settings' do + expect(page).to have_button('Save changes', disabled: false) + end + expect(page).to have_field('Test coverage parsing', with: 'coverage_regex') end @@ -38,10 +46,15 @@ describe "Projects > Settings > Pipelines settings" do visit project_settings_ci_cd_path(project) page.check('Auto-cancel redundant, pending pipelines') - click_on 'Save changes' + page.within '#js-general-pipeline-settings' do + click_on 'Save changes' + end expect(page.status_code).to eq(200) - expect(page).to have_button('Save changes', disabled: false) + + page.within '#js-general-pipeline-settings' do + expect(page).to have_button('Save changes', disabled: false) + end checkbox = find_field('project_auto_cancel_pending_pipelines') expect(checkbox).to be_checked @@ -51,13 +64,16 @@ describe "Projects > Settings > Pipelines settings" do it 'update auto devops settings' do visit project_settings_ci_cd_path(project) - fill_in('project_auto_devops_attributes_domain', with: 'test.com') - page.choose('project_auto_devops_attributes_enabled_false') - click_on 'Save changes' + page.within '#autodevops-settings' do + fill_in('project_auto_devops_attributes_domain', with: 'test.com') + page.choose('project_auto_devops_attributes_enabled_false') + click_on 'Save changes' + end expect(page.status_code).to eq(200) expect(project.auto_devops).to be_present expect(project.auto_devops).not_to be_enabled + expect(project.auto_devops.domain).to eq('test.com') end end end diff --git a/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb b/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb index a906fa20233..e44361fbe26 100644 --- a/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb +++ b/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb @@ -65,7 +65,7 @@ describe 'Projects > Show > User sees setup shortcut buttons' do describe 'Auto DevOps button' do it '"Enable Auto DevOps" button linked to settings page' do page.within('.project-stats') do - expect(page).to have_link('Enable Auto DevOps', href: project_settings_ci_cd_path(project, anchor: 'js-general-pipeline-settings')) + expect(page).to have_link('Enable Auto DevOps', href: project_settings_ci_cd_path(project, anchor: 'autodevops-settings')) end end @@ -75,7 +75,7 @@ describe 'Projects > Show > User sees setup shortcut buttons' do visit project_path(project) page.within('.project-stats') do - expect(page).to have_link('Auto DevOps enabled', href: project_settings_ci_cd_path(project, anchor: 'js-general-pipeline-settings')) + expect(page).to have_link('Auto DevOps enabled', href: project_settings_ci_cd_path(project, anchor: 'autodevops-settings')) end end end @@ -212,7 +212,7 @@ describe 'Projects > Show > User sees setup shortcut buttons' do describe 'Auto DevOps button' do it '"Enable Auto DevOps" button linked to settings page' do page.within('.project-stats') do - expect(page).to have_link('Enable Auto DevOps', href: project_settings_ci_cd_path(project, anchor: 'js-general-pipeline-settings')) + expect(page).to have_link('Enable Auto DevOps', href: project_settings_ci_cd_path(project, anchor: 'autodevops-settings')) end end @@ -222,7 +222,7 @@ describe 'Projects > Show > User sees setup shortcut buttons' do visit project_path(project) page.within('.project-stats') do - expect(page).to have_link('Auto DevOps enabled', href: project_settings_ci_cd_path(project, anchor: 'js-general-pipeline-settings')) + expect(page).to have_link('Auto DevOps enabled', href: project_settings_ci_cd_path(project, anchor: 'autodevops-settings')) end end diff --git a/spec/javascripts/pipelines/graph/action_component_spec.js b/spec/javascripts/pipelines/graph/action_component_spec.js index 581209f215d..3de10392472 100644 --- a/spec/javascripts/pipelines/graph/action_component_spec.js +++ b/spec/javascripts/pipelines/graph/action_component_spec.js @@ -6,7 +6,7 @@ import mountComponent from '../../helpers/vue_mount_component_helper'; describe('pipeline graph action component', () => { let component; - beforeEach((done) => { + beforeEach(done => { const ActionComponent = Vue.extend(actionComponent); component = mountComponent(ActionComponent, { tooltipText: 'bar', @@ -22,7 +22,7 @@ describe('pipeline graph action component', () => { }); it('should emit an event with the provided link', () => { - eventHub.$on('graphAction', (link) => { + eventHub.$on('graphAction', link => { expect(link).toEqual('foo'); }); }); @@ -31,7 +31,7 @@ describe('pipeline graph action component', () => { expect(component.$el.getAttribute('data-original-title')).toEqual('bar'); }); - it('should update bootstrap tooltip when title changes', (done) => { + it('should update bootstrap tooltip when title changes', done => { component.tooltipText = 'changed'; setTimeout(() => { @@ -44,4 +44,45 @@ describe('pipeline graph action component', () => { expect(component.$el.querySelector('.ci-action-icon-wrapper')).toBeDefined(); expect(component.$el.querySelector('svg')).toBeDefined(); }); + + it('disables the button when clicked', done => { + component.$el.click(); + + component.$nextTick(() => { + expect(component.$el.getAttribute('disabled')).toEqual('disabled'); + done(); + }); + }); + + it('re-enabled the button when `requestFinishedFor` matches `linkRequested`', done => { + component.$el.click(); + + component + .$nextTick() + .then(() => { + expect(component.$el.getAttribute('disabled')).toEqual('disabled'); + component.requestFinishedFor = 'foo'; + }) + .then(() => { + expect(component.$el.getAttribute('disabled')).toBeNull(); + }) + .then(done) + .catch(done.fail); + }); + + it('does not re-enable the button when `requestFinishedFor` does not matches `linkRequested`', done => { + component.$el.click(); + + component + .$nextTick() + .then(() => { + expect(component.$el.getAttribute('disabled')).toEqual('disabled'); + component.requestFinishedFor = 'bar'; + }) + .then(() => { + expect(component.$el.getAttribute('disabled')).toEqual('disabled'); + }) + .then(done) + .catch(done.fail); + }); }); diff --git a/spec/javascripts/pipelines/graph/dropdown_action_component_spec.js b/spec/javascripts/pipelines/graph/dropdown_action_component_spec.js deleted file mode 100644 index ba721bc53c6..00000000000 --- a/spec/javascripts/pipelines/graph/dropdown_action_component_spec.js +++ /dev/null @@ -1,32 +0,0 @@ -import Vue from 'vue'; -import dropdownActionComponent from '~/pipelines/components/graph/dropdown_action_component.vue'; - -describe('action component', () => { - let component; - - beforeEach((done) => { - const DropdownActionComponent = Vue.extend(dropdownActionComponent); - component = new DropdownActionComponent({ - propsData: { - tooltipText: 'bar', - link: 'foo', - actionMethod: 'post', - actionIcon: 'cancel', - }, - }).$mount(); - - Vue.nextTick(done); - }); - - it('should render a link', () => { - expect(component.$el.getAttribute('href')).toEqual('foo'); - }); - - it('should render the provided title as a bootstrap tooltip', () => { - expect(component.$el.getAttribute('data-original-title')).toEqual('bar'); - }); - - it('should render an svg', () => { - expect(component.$el.querySelector('svg')).toBeDefined(); - }); -}); diff --git a/spec/javascripts/pipelines/graph/job_component_spec.js b/spec/javascripts/pipelines/graph/job_component_spec.js index c9677ae209a..073dae56c25 100644 --- a/spec/javascripts/pipelines/graph/job_component_spec.js +++ b/spec/javascripts/pipelines/graph/job_component_spec.js @@ -93,17 +93,6 @@ describe('pipeline graph job component', () => { }); }); - describe('dropdown', () => { - it('should render the dropdown action icon', () => { - component = mountComponent(JobComponent, { - job: mockJob, - isDropdown: true, - }); - - expect(component.$el.querySelector('a.ci-action-icon-wrapper')).toBeDefined(); - }); - }); - it('should render provided class name', () => { component = mountComponent(JobComponent, { job: mockJob, diff --git a/spec/javascripts/settings_panels_spec.js b/spec/javascripts/settings_panels_spec.js index d433f8c3e07..4fba36bd4de 100644 --- a/spec/javascripts/settings_panels_spec.js +++ b/spec/javascripts/settings_panels_spec.js @@ -13,9 +13,9 @@ describe('Settings Panels', () => { }); it('should expand linked hash fragment panel', () => { - location.hash = '#js-general-pipeline-settings'; + location.hash = '#autodevops-settings'; - const pipelineSettingsPanel = document.querySelector('#js-general-pipeline-settings'); + const pipelineSettingsPanel = document.querySelector('#autodevops-settings'); // Our test environment automatically expands everything so we need to clear that out first pipelineSettingsPanel.classList.remove('expanded'); diff --git a/spec/javascripts/vue_shared/components/ci_icon_spec.js b/spec/javascripts/vue_shared/components/ci_icon_spec.js index d8664408595..423bc746a22 100644 --- a/spec/javascripts/vue_shared/components/ci_icon_spec.js +++ b/spec/javascripts/vue_shared/components/ci_icon_spec.js @@ -1,139 +1,122 @@ import Vue from 'vue'; import ciIcon from '~/vue_shared/components/ci_icon.vue'; +import mountComponent from 'spec/helpers/vue_mount_component_helper'; describe('CI Icon component', () => { - let CiIcon; - beforeEach(() => { - CiIcon = Vue.extend(ciIcon); + const Component = Vue.extend(ciIcon); + let vm; + + afterEach(() => { + vm.$destroy(); }); it('should render a span element with an svg', () => { - const component = new CiIcon({ - propsData: { - status: { - icon: 'icon_status_success', - }, + vm = mountComponent(Component, { + status: { + icon: 'icon_status_success', }, - }).$mount(); + }); - expect(component.$el.tagName).toEqual('SPAN'); - expect(component.$el.querySelector('span > svg')).toBeDefined(); + expect(vm.$el.tagName).toEqual('SPAN'); + expect(vm.$el.querySelector('span > svg')).toBeDefined(); }); it('should render a success status', () => { - const component = new CiIcon({ - propsData: { - status: { - icon: 'icon_status_success', - group: 'success', - }, + vm = mountComponent(Component, { + status: { + icon: 'icon_status_success', + group: 'success', }, - }).$mount(); + }); - expect(component.$el.classList.contains('ci-status-icon-success')).toEqual(true); + expect(vm.$el.classList.contains('ci-status-icon-success')).toEqual(true); }); it('should render a failed status', () => { - const component = new CiIcon({ - propsData: { - status: { - icon: 'icon_status_failed', - group: 'failed', - }, + vm = mountComponent(Component, { + status: { + icon: 'icon_status_failed', + group: 'failed', }, - }).$mount(); + }); - expect(component.$el.classList.contains('ci-status-icon-failed')).toEqual(true); + expect(vm.$el.classList.contains('ci-status-icon-failed')).toEqual(true); }); it('should render success with warnings status', () => { - const component = new CiIcon({ - propsData: { - status: { - icon: 'icon_status_warning', - group: 'warning', - }, + vm = mountComponent(Component, { + status: { + icon: 'icon_status_warning', + group: 'warning', }, - }).$mount(); + }); - expect(component.$el.classList.contains('ci-status-icon-warning')).toEqual(true); + expect(vm.$el.classList.contains('ci-status-icon-warning')).toEqual(true); }); it('should render pending status', () => { - const component = new CiIcon({ - propsData: { - status: { - icon: 'icon_status_pending', - group: 'pending', - }, + vm = mountComponent(Component, { + status: { + icon: 'icon_status_pending', + group: 'pending', }, - }).$mount(); + }); - expect(component.$el.classList.contains('ci-status-icon-pending')).toEqual(true); + expect(vm.$el.classList.contains('ci-status-icon-pending')).toEqual(true); }); it('should render running status', () => { - const component = new CiIcon({ - propsData: { - status: { - icon: 'icon_status_running', - group: 'running', - }, + vm = mountComponent(Component, { + status: { + icon: 'icon_status_running', + group: 'running', }, - }).$mount(); + }); - expect(component.$el.classList.contains('ci-status-icon-running')).toEqual(true); + expect(vm.$el.classList.contains('ci-status-icon-running')).toEqual(true); }); it('should render created status', () => { - const component = new CiIcon({ - propsData: { - status: { - icon: 'icon_status_created', - group: 'created', - }, + vm = mountComponent(Component, { + status: { + icon: 'icon_status_created', + group: 'created', }, - }).$mount(); + }); - expect(component.$el.classList.contains('ci-status-icon-created')).toEqual(true); + expect(vm.$el.classList.contains('ci-status-icon-created')).toEqual(true); }); it('should render skipped status', () => { - const component = new CiIcon({ - propsData: { - status: { - icon: 'icon_status_skipped', - group: 'skipped', - }, + vm = mountComponent(Component, { + status: { + icon: 'icon_status_skipped', + group: 'skipped', }, - }).$mount(); + }); - expect(component.$el.classList.contains('ci-status-icon-skipped')).toEqual(true); + expect(vm.$el.classList.contains('ci-status-icon-skipped')).toEqual(true); }); it('should render canceled status', () => { - const component = new CiIcon({ - propsData: { - status: { - icon: 'icon_status_canceled', - group: 'canceled', - }, + vm = mountComponent(Component, { + status: { + icon: 'icon_status_canceled', + group: 'canceled', }, - }).$mount(); + }); - expect(component.$el.classList.contains('ci-status-icon-canceled')).toEqual(true); + expect(vm.$el.classList.contains('ci-status-icon-canceled')).toEqual(true); }); it('should render status for manual action', () => { - const component = new CiIcon({ - propsData: { - status: { - icon: 'icon_status_manual', - group: 'manual', - }, + vm = mountComponent(Component, { + status: { + icon: 'icon_status_manual', + group: 'manual', }, - }).$mount(); + }); - expect(component.$el.classList.contains('ci-status-icon-manual')).toEqual(true); + expect(vm.$el.classList.contains('ci-status-icon-manual')).toEqual(true); }); }); diff --git a/spec/javascripts/vue_shared/components/clipboard_button_spec.js b/spec/javascripts/vue_shared/components/clipboard_button_spec.js index f598b1afa74..97f0fbb04db 100644 --- a/spec/javascripts/vue_shared/components/clipboard_button_spec.js +++ b/spec/javascripts/vue_shared/components/clipboard_button_spec.js @@ -3,10 +3,10 @@ import clipboardButton from '~/vue_shared/components/clipboard_button.vue'; import mountComponent from 'spec/helpers/vue_mount_component_helper'; describe('clipboard button', () => { + const Component = Vue.extend(clipboardButton); let vm; beforeEach(() => { - const Component = Vue.extend(clipboardButton); vm = mountComponent(Component, { text: 'copy me', title: 'Copy this value into Clipboard!', diff --git a/spec/javascripts/vue_shared/components/commit_spec.js b/spec/javascripts/vue_shared/components/commit_spec.js index ed66361bfc3..7189e8cfcfa 100644 --- a/spec/javascripts/vue_shared/components/commit_spec.js +++ b/spec/javascripts/vue_shared/components/commit_spec.js @@ -55,7 +55,6 @@ describe('Commit component', () => { path: '/jschatz1', username: 'jschatz1', }, - commitIconSvg: '<svg></svg>', }; component = mountComponent(CommitComponent, props); @@ -82,8 +81,10 @@ describe('Commit component', () => { expect(component.$el.querySelector('.commit-sha').textContent).toContain(props.shortSha); }); - it('should render the given commitIconSvg', () => { - expect(component.$el.querySelector('.js-commit-icon').children).toContain('svg'); + it('should render icon for commit', () => { + expect( + component.$el.querySelector('.js-commit-icon use').getAttribute('xlink:href'), + ).toContain('commit'); }); describe('Given commit title and author props', () => { diff --git a/spec/javascripts/vue_shared/components/expand_button_spec.js b/spec/javascripts/vue_shared/components/expand_button_spec.js index f19589d3b75..af9693c48fd 100644 --- a/spec/javascripts/vue_shared/components/expand_button_spec.js +++ b/spec/javascripts/vue_shared/components/expand_button_spec.js @@ -3,10 +3,10 @@ import expandButton from '~/vue_shared/components/expand_button.vue'; import mountComponent from 'spec/helpers/vue_mount_component_helper'; describe('expand button', () => { + const Component = Vue.extend(expandButton); let vm; beforeEach(() => { - const Component = Vue.extend(expandButton); vm = mountComponent(Component, { slots: { expanded: '<p>Expanded!</p>', @@ -22,7 +22,7 @@ describe('expand button', () => { expect(vm.$el.textContent.trim()).toEqual('...'); }); - it('hides expander on click', (done) => { + it('hides expander on click', done => { vm.$el.querySelector('button').click(); vm.$nextTick(() => { expect(vm.$el.querySelector('button').getAttribute('style')).toEqual('display: none;'); diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/base_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/base_spec.js index 6fe95153204..e8685ab48be 100644 --- a/spec/javascripts/vue_shared/components/sidebar/labels_select/base_spec.js +++ b/spec/javascripts/vue_shared/components/sidebar/labels_select/base_spec.js @@ -73,6 +73,22 @@ describe('BaseComponent', () => { expect(vm.$emit).toHaveBeenCalledWith('onLabelClick', mockLabels[0]); }); }); + + describe('handleCollapsedValueClick', () => { + it('emits toggleCollapse event on component', () => { + spyOn(vm, '$emit'); + vm.handleCollapsedValueClick(); + expect(vm.$emit).toHaveBeenCalledWith('toggleCollapse'); + }); + }); + + describe('handleDropdownHidden', () => { + it('emits onDropdownClose event on component', () => { + spyOn(vm, '$emit'); + vm.handleDropdownHidden(); + expect(vm.$emit).toHaveBeenCalledWith('onDropdownClose'); + }); + }); }); describe('mounted', () => { diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js index 39040670a87..da74595bcdc 100644 --- a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js +++ b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js @@ -56,6 +56,16 @@ describe('DropdownValueCollapsedComponent', () => { }); }); + describe('methods', () => { + describe('handleClick', () => { + it('emits onValueClick event on component', () => { + spyOn(vm, '$emit'); + vm.handleClick(); + expect(vm.$emit).toHaveBeenCalledWith('onValueClick'); + }); + }); + }); + describe('template', () => { it('renders component container element with tooltip`', () => { expect(vm.$el.dataset.placement).toBe('left'); diff --git a/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb b/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb index ae5b31dc12d..c3f528dd6fc 100644 --- a/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb +++ b/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb @@ -15,7 +15,7 @@ describe Gitlab::BitbucketImport::ProjectCreator do has_wiki?: false) end - let(:namespace) { create(:group, owner: user) } + let(:namespace) { create(:group) } let(:token) { "asdasd12345" } let(:secret) { "sekrettt" } let(:access_params) { { bitbucket_access_token: token, bitbucket_access_token_secret: secret } } diff --git a/spec/lib/gitlab/wiki/committer_with_hooks_spec.rb b/spec/lib/gitlab/git/committer_with_hooks_spec.rb index 830fb8a8598..267056b96e6 100644 --- a/spec/lib/gitlab/wiki/committer_with_hooks_spec.rb +++ b/spec/lib/gitlab/git/committer_with_hooks_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::Wiki::CommitterWithHooks, seed_helper: true do +describe Gitlab::Git::CommitterWithHooks, seed_helper: true do shared_examples 'calling wiki hooks' do let(:project) { create(:project) } let(:user) { project.owner } diff --git a/spec/lib/gitlab/gitlab_import/project_creator_spec.rb b/spec/lib/gitlab/gitlab_import/project_creator_spec.rb index 82548c7fd31..5ea086e4abd 100644 --- a/spec/lib/gitlab/gitlab_import/project_creator_spec.rb +++ b/spec/lib/gitlab/gitlab_import/project_creator_spec.rb @@ -12,7 +12,7 @@ describe Gitlab::GitlabImport::ProjectCreator do owner: { name: "john" } }.with_indifferent_access end - let(:namespace) { create(:group, owner: user) } + let(:namespace) { create(:group) } let(:token) { "asdffg" } let(:access_params) { { gitlab_access_token: token } } diff --git a/spec/lib/gitlab/google_code_import/project_creator_spec.rb b/spec/lib/gitlab/google_code_import/project_creator_spec.rb index 8d5b60d50de..24cd518c77b 100644 --- a/spec/lib/gitlab/google_code_import/project_creator_spec.rb +++ b/spec/lib/gitlab/google_code_import/project_creator_spec.rb @@ -9,7 +9,7 @@ describe Gitlab::GoogleCodeImport::ProjectCreator do "repositoryUrls" => ["https://vim.googlecode.com/git/"] ) end - let(:namespace) { create(:group, owner: user) } + let(:namespace) { create(:group) } before do namespace.add_owner(user) diff --git a/spec/lib/gitlab/legacy_github_import/project_creator_spec.rb b/spec/lib/gitlab/legacy_github_import/project_creator_spec.rb index 737c9a624e0..eb1b13704ea 100644 --- a/spec/lib/gitlab/legacy_github_import/project_creator_spec.rb +++ b/spec/lib/gitlab/legacy_github_import/project_creator_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Gitlab::LegacyGithubImport::ProjectCreator do let(:user) { create(:user) } - let(:namespace) { create(:group, owner: user) } + let(:namespace) { create(:group) } let(:repo) do OpenStruct.new( diff --git a/spec/lib/gitlab_spec.rb b/spec/lib/gitlab_spec.rb index bd443a5d9e7..da146e24893 100644 --- a/spec/lib/gitlab_spec.rb +++ b/spec/lib/gitlab_spec.rb @@ -1,6 +1,14 @@ -require 'rails_helper' +require 'fast_spec_helper' + +require_dependency 'gitlab' describe Gitlab do + describe '.root' do + it 'returns the root path of the app' do + expect(described_class.root).to eq(Pathname.new(File.expand_path('../..', __dir__))) + end + end + describe '.com?' do it 'is true when on GitLab.com' do stub_config_setting(url: 'https://gitlab.com') diff --git a/spec/migrations/create_missing_namespace_for_internal_users_spec.rb b/spec/migrations/create_missing_namespace_for_internal_users_spec.rb new file mode 100644 index 00000000000..ac3a4b1f68f --- /dev/null +++ b/spec/migrations/create_missing_namespace_for_internal_users_spec.rb @@ -0,0 +1,42 @@ +require 'spec_helper' +require Rails.root.join('db', 'migrate', '20180413022611_create_missing_namespace_for_internal_users.rb') + +describe CreateMissingNamespaceForInternalUsers, :migration do + let(:users) { table(:users) } + let(:namespaces) { table(:namespaces) } + let(:routes) { table(:routes) } + + internal_user_types = [:ghost] + internal_user_types << :support_bot if ActiveRecord::Base.connection.column_exists?(:users, :support_bot) + + internal_user_types.each do |attr| + context "for #{attr} user" do + let(:internal_user) do + users.create!(email: 'test@example.com', projects_limit: 100, username: 'test', attr => true) + end + + it 'creates the missing namespace' do + expect(namespaces.find_by(owner_id: internal_user.id)).to be_nil + + migrate! + + namespace = Namespace.find_by(type: nil, owner_id: internal_user.id) + route = namespace.route + + expect(namespace.path).to eq(route.path) + expect(namespace.name).to eq(route.name) + end + + it 'sets notification email' do + users.update(internal_user.id, notification_email: nil) + + expect(users.find(internal_user.id).notification_email).to be_nil + + migrate! + + user = users.find(internal_user.id) + expect(user.notification_email).to eq(user.email) + end + end + end +end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 9c78f7b8bd6..9ef4dbd7e39 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -344,7 +344,7 @@ describe Project do let(:owner) { create(:user, name: 'Gitlab') } let(:namespace) { create(:namespace, path: 'sample-namespace', owner: owner) } let(:project) { create(:project, path: 'sample-project', namespace: namespace) } - let(:group) { create(:group, name: 'Group', path: 'sample-group', owner: owner) } + let(:group) { create(:group, name: 'Group', path: 'sample-group') } context 'when nil argument' do it 'returns nil' do diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index f384b688889..79447a65e94 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -1164,8 +1164,12 @@ describe User do end context 'with a group route matching the given path' do + let!(:group) { create(:group, path: 'group_path') } + context 'when the group namespace has an owner_id (legacy data)' do - let!(:group) { create(:group, path: 'group_path', owner: user) } + before do + group.update!(owner_id: user.id) + end it 'returns nil' do expect(described_class.find_by_full_path('group_path')).to eq(nil) @@ -1173,8 +1177,6 @@ describe User do end context 'when the group namespace does not have an owner_id' do - let!(:group) { create(:group, path: 'group_path') } - it 'returns nil' do expect(described_class.find_by_full_path('group_path')).to eq(nil) end diff --git a/spec/presenters/project_presenter_spec.rb b/spec/presenters/project_presenter_spec.rb index 55962f345d4..0a130c59037 100644 --- a/spec/presenters/project_presenter_spec.rb +++ b/spec/presenters/project_presenter_spec.rb @@ -321,7 +321,7 @@ describe ProjectPresenter do expect(presenter.autodevops_anchor_data).to eq(OpenStruct.new(enabled: false, label: 'Enable Auto DevOps', - link: presenter.project_settings_ci_cd_path(project, anchor: 'js-general-pipeline-settings'))) + link: presenter.project_settings_ci_cd_path(project, anchor: 'autodevops-settings'))) end end end diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 17272cb00e5..85a571b8f0e 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -685,7 +685,8 @@ describe API::Projects do issues_enabled: false, merge_requests_enabled: false, wiki_enabled: false, - request_access_enabled: true + request_access_enabled: true, + jobs_enabled: true }) post api("/projects/user/#{user.id}", admin), project diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index f406d2ffb22..e8196980a8c 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -212,6 +212,18 @@ describe API::Users do expect(json_response.last['id']).to eq(user.id) end + it 'returns users with 2fa enabled' do + admin + user + user_with_2fa = create(:user, :two_factor_via_otp) + + get api('/users', admin), { two_factor: 'enabled' } + + expect(response).to match_response_schema('public_api/v4/user/admins') + expect(json_response.size).to eq(1) + expect(json_response.first['id']).to eq(user_with_2fa.id) + end + it 'returns 400 when provided incorrect sort params' do get api('/users', admin), { order_by: 'magic', sort: 'asc' } diff --git a/spec/services/groups/nested_create_service_spec.rb b/spec/services/groups/nested_create_service_spec.rb index 6491fb34777..86fdd43c1e5 100644 --- a/spec/services/groups/nested_create_service_spec.rb +++ b/spec/services/groups/nested_create_service_spec.rb @@ -59,8 +59,11 @@ describe Groups::NestedCreateService do describe "#execute" do it 'returns the group if it already existed' do - parent = create(:group, path: 'a-group', owner: user) - child = create(:group, path: 'a-sub-group', parent: parent, owner: user) + parent = create(:group, path: 'a-group') + child = create(:group, path: 'a-sub-group', parent: parent) + + parent.add_owner(user) + child.add_owner(user) expect(service.execute).to eq(child) end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 83664bae046..53045815a6a 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -32,42 +32,19 @@ require 'rainbow/ext/string' # Requires supporting ruby files with custom matchers and macros, etc, # in spec/support/ and its subdirectories. +# Requires helpers, and shared contexts/examples first since they're used in other support files +Dir[Rails.root.join("spec/support/helpers/*.rb")].each { |f| require f } +Dir[Rails.root.join("spec/support/shared_contexts/*.rb")].each { |f| require f } +Dir[Rails.root.join("spec/support/shared_examples/*.rb")].each { |f| require f } Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f } RSpec.configure do |config| config.use_transactional_fixtures = false config.use_instantiated_fixtures = false - config.mock_with :rspec config.verbose_retry = true config.display_try_failure_messages = true - config.include Devise::Test::ControllerHelpers, type: :controller - config.include Devise::Test::ControllerHelpers, type: :view - config.include Devise::Test::IntegrationHelpers, type: :feature - config.include Warden::Test::Helpers, type: :request - config.include LoginHelpers, type: :feature - config.include SearchHelpers, type: :feature - config.include CookieHelper, :js - config.include InputHelper, :js - config.include SelectionHelper, :js - config.include InspectRequests, :js - config.include WaitForRequests, :js - config.include LiveDebugger, :js - config.include StubConfiguration - config.include EmailHelpers, :mailer, type: :mailer - config.include TestEnv - config.include ActiveJob::TestHelper - config.include ActiveSupport::Testing::TimeHelpers - config.include StubGitlabCalls - config.include StubGitlabData - config.include ApiHelpers, :api - config.include Gitlab::Routing, type: :routing - config.include MigrationsHelpers, :migration - config.include StubFeatureFlags - config.include StubENV - config.include ExpectOffense - config.infer_spec_type_from_file_location! config.define_derived_metadata(file_path: %r{/spec/}) do |metadata| @@ -82,7 +59,33 @@ RSpec.configure do |config| metadata[:type] = match[1].singularize.to_sym if match end - config.raise_errors_for_deprecations! + config.include ActiveJob::TestHelper + config.include ActiveSupport::Testing::TimeHelpers + config.include CycleAnalyticsHelpers + config.include ExpectOffense + config.include FactoryBot::Syntax::Methods + config.include FixtureHelpers + config.include GitlabRoutingHelper + config.include StubFeatureFlags + config.include StubGitlabCalls + config.include StubGitlabData + config.include TestEnv + config.include Devise::Test::ControllerHelpers, type: :controller + config.include Devise::Test::IntegrationHelpers, type: :feature + config.include LoginHelpers, type: :feature + config.include SearchHelpers, type: :feature + config.include EmailHelpers, :mailer, type: :mailer + config.include Warden::Test::Helpers, type: :request + config.include Gitlab::Routing, type: :routing + config.include Devise::Test::ControllerHelpers, type: :view + config.include ApiHelpers, :api + config.include CookieHelper, :js + config.include InputHelper, :js + config.include SelectionHelper, :js + config.include InspectRequests, :js + config.include WaitForRequests, :js + config.include LiveDebugger, :js + config.include MigrationsHelpers, :migration if ENV['CI'] # This includes the first try, i.e. tests will be run 4 times before failing. diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb index 9ddcc5f2fbf..c0ceb0f6605 100644 --- a/spec/support/capybara.rb +++ b/spec/support/capybara.rb @@ -60,6 +60,8 @@ Capybara::Screenshot.register_driver(:chrome) do |driver, path| end RSpec.configure do |config| + config.include CapybaraHelpers, type: :feature + config.before(:context, :js) do next if $capybara_server_already_started diff --git a/spec/support/controllers/githubish_import_controller_shared_examples.rb b/spec/support/controllers/githubish_import_controller_shared_examples.rb index 3321f920666..368439aa5b0 100644 --- a/spec/support/controllers/githubish_import_controller_shared_examples.rb +++ b/spec/support/controllers/githubish_import_controller_shared_examples.rb @@ -56,7 +56,7 @@ shared_examples 'a GitHub-ish import controller: GET status' do end it "assigns variables" do - project = create(:project, import_type: provider, creator_id: user.id) + project = create(:project, import_type: provider, namespace: user.namespace) stub_client(repos: [repo, org_repo], orgs: [org], org_repos: [org_repo]) get :status @@ -69,7 +69,7 @@ shared_examples 'a GitHub-ish import controller: GET status' do end it "does not show already added project" do - project = create(:project, import_type: provider, creator_id: user.id, import_source: 'asd/vim') + project = create(:project, import_type: provider, namespace: user.namespace, import_source: 'asd/vim') stub_client(repos: [repo], orgs: []) get :status @@ -257,11 +257,12 @@ shared_examples 'a GitHub-ish import controller: POST create' do end context 'user has chosen an existing nested namespace and name for the project', :postgresql do - let(:parent_namespace) { create(:group, name: 'foo', owner: user) } + let(:parent_namespace) { create(:group, name: 'foo') } let(:nested_namespace) { create(:group, name: 'bar', parent: parent_namespace) } let(:test_name) { 'test_name' } before do + parent_namespace.add_owner(user) nested_namespace.add_owner(user) end @@ -307,7 +308,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do context 'user has chosen existent and non-existent nested namespaces and name for the project', :postgresql do let(:test_name) { 'test_name' } - let!(:parent_namespace) { create(:group, name: 'foo', owner: user) } + let!(:parent_namespace) { create(:group, name: 'foo') } before do parent_namespace.add_owner(user) diff --git a/spec/support/factory_bot.rb b/spec/support/factory_bot.rb deleted file mode 100644 index c7890e49c66..00000000000 --- a/spec/support/factory_bot.rb +++ /dev/null @@ -1,3 +0,0 @@ -RSpec.configure do |config| - config.include FactoryBot::Syntax::Methods -end diff --git a/spec/support/generate-seed-repo-rb b/spec/support/generate-seed-repo-rb index 876b3b8242d..44b3de23b99 100755 --- a/spec/support/generate-seed-repo-rb +++ b/spec/support/generate-seed-repo-rb @@ -8,7 +8,7 @@ # # Usage: # -# ./spec/support/generate-seed-repo-rb > spec/support/seed_repo.rb +# ./spec/support/generate-seed-repo-rb > spec/support/helpers/seed_repo.rb # # diff --git a/spec/support/gitlab-git-test.git/README.md b/spec/support/gitlab-git-test.git/README.md index f072cd421be..f757e613ee6 100644 --- a/spec/support/gitlab-git-test.git/README.md +++ b/spec/support/gitlab-git-test.git/README.md @@ -12,5 +12,5 @@ inflate the size of the gitlab-ce repository. - make changes in your local clone of gitlab-git-test - run `git push` which will push to your local source `gitlab-ce/spec/support/gitlab-git-test.git` - in gitlab-ce: run `spec/support/prepare-gitlab-git-test-for-commit` -- in gitlab-ce: `git add spec/support/seed_repo.rb spec/support/gitlab-git-test.git` +- in gitlab-ce: `git add spec/support/helpers/seed_repo.rb spec/support/gitlab-git-test.git` - commit your changes in gitlab-ce diff --git a/spec/support/api_helpers.rb b/spec/support/helpers/api_helpers.rb index ac0c7a9b493..ac0c7a9b493 100644 --- a/spec/support/api_helpers.rb +++ b/spec/support/helpers/api_helpers.rb diff --git a/spec/support/bare_repo_operations.rb b/spec/support/helpers/bare_repo_operations.rb index 3f4a4243cb6..3f4a4243cb6 100644 --- a/spec/support/bare_repo_operations.rb +++ b/spec/support/helpers/bare_repo_operations.rb diff --git a/spec/support/board_helpers.rb b/spec/support/helpers/board_helpers.rb index 507d0432d7f..507d0432d7f 100644 --- a/spec/support/board_helpers.rb +++ b/spec/support/helpers/board_helpers.rb diff --git a/spec/support/capybara_helpers.rb b/spec/support/helpers/capybara_helpers.rb index 868233416bf..bcc2df44708 100644 --- a/spec/support/capybara_helpers.rb +++ b/spec/support/helpers/capybara_helpers.rb @@ -41,7 +41,3 @@ module CapybaraHelpers page.driver.browser.manage.delete_cookie('_gitlab_session') end end - -RSpec.configure do |config| - config.include CapybaraHelpers, type: :feature -end diff --git a/spec/support/cookie_helper.rb b/spec/support/helpers/cookie_helper.rb index 5ff7b0b68c9..5ff7b0b68c9 100644 --- a/spec/support/cookie_helper.rb +++ b/spec/support/helpers/cookie_helper.rb diff --git a/spec/support/cycle_analytics_helpers.rb b/spec/support/helpers/cycle_analytics_helpers.rb index 73cc64c0b74..55359d36597 100644 --- a/spec/support/cycle_analytics_helpers.rb +++ b/spec/support/helpers/cycle_analytics_helpers.rb @@ -135,7 +135,3 @@ module CycleAnalyticsHelpers end end end - -RSpec.configure do |config| - config.include CycleAnalyticsHelpers -end diff --git a/spec/support/database_connection_helpers.rb b/spec/support/helpers/database_connection_helpers.rb index 763329499f0..763329499f0 100644 --- a/spec/support/database_connection_helpers.rb +++ b/spec/support/helpers/database_connection_helpers.rb diff --git a/spec/support/devise_helpers.rb b/spec/support/helpers/devise_helpers.rb index 66874e10f38..66874e10f38 100644 --- a/spec/support/devise_helpers.rb +++ b/spec/support/helpers/devise_helpers.rb diff --git a/spec/support/drag_to_helper.rb b/spec/support/helpers/drag_to_helper.rb index ae149631ed9..ae149631ed9 100644 --- a/spec/support/drag_to_helper.rb +++ b/spec/support/helpers/drag_to_helper.rb diff --git a/spec/support/dropzone_helper.rb b/spec/support/helpers/dropzone_helper.rb index fe72d320fcf..fe72d320fcf 100644 --- a/spec/support/dropzone_helper.rb +++ b/spec/support/helpers/dropzone_helper.rb diff --git a/spec/support/email_helpers.rb b/spec/support/helpers/email_helpers.rb index 1fb8252459f..1fb8252459f 100644 --- a/spec/support/email_helpers.rb +++ b/spec/support/helpers/email_helpers.rb diff --git a/spec/support/fake_migration_classes.rb b/spec/support/helpers/fake_migration_classes.rb index b0fc8422857..b0fc8422857 100644 --- a/spec/support/fake_migration_classes.rb +++ b/spec/support/helpers/fake_migration_classes.rb diff --git a/spec/support/fake_u2f_device.rb b/spec/support/helpers/fake_u2f_device.rb index a7605cd483a..a7605cd483a 100644 --- a/spec/support/fake_u2f_device.rb +++ b/spec/support/helpers/fake_u2f_device.rb diff --git a/spec/support/filter_item_select_helper.rb b/spec/support/helpers/filter_item_select_helper.rb index 519e84d359e..519e84d359e 100644 --- a/spec/support/filter_item_select_helper.rb +++ b/spec/support/helpers/filter_item_select_helper.rb diff --git a/spec/support/filter_spec_helper.rb b/spec/support/helpers/filter_spec_helper.rb index 721d359c2ee..721d359c2ee 100644 --- a/spec/support/filter_spec_helper.rb +++ b/spec/support/helpers/filter_spec_helper.rb diff --git a/spec/support/filtered_search_helpers.rb b/spec/support/helpers/filtered_search_helpers.rb index 5f42ff77fb2..5f42ff77fb2 100644 --- a/spec/support/filtered_search_helpers.rb +++ b/spec/support/helpers/filtered_search_helpers.rb diff --git a/spec/support/fixture_helpers.rb b/spec/support/helpers/fixture_helpers.rb index 8854382dc6b..611d19f36a0 100644 --- a/spec/support/fixture_helpers.rb +++ b/spec/support/helpers/fixture_helpers.rb @@ -9,7 +9,3 @@ module FixtureHelpers File.expand_path(Rails.root.join(dir, 'spec', 'fixtures', filename)) end end - -RSpec.configure do |config| - config.include FixtureHelpers -end diff --git a/spec/support/git_http_helpers.rb b/spec/support/helpers/git_http_helpers.rb index b8289e6c5f1..b8289e6c5f1 100644 --- a/spec/support/git_http_helpers.rb +++ b/spec/support/helpers/git_http_helpers.rb diff --git a/spec/support/helpers/gitlab_verify_helpers.rb b/spec/support/helpers/gitlab_verify_helpers.rb new file mode 100644 index 00000000000..5df4bf24ec2 --- /dev/null +++ b/spec/support/helpers/gitlab_verify_helpers.rb @@ -0,0 +1,25 @@ +module GitlabVerifyHelpers + def collect_ranges(args = {}) + verifier = described_class.new(args.merge(batch_size: 1)) + + collect_results(verifier).map { |range, _| range } + end + + def collect_failures + verifier = described_class.new(batch_size: 1) + + out = {} + + collect_results(verifier).map { |_, failures| out.merge!(failures) } + + out + end + + def collect_results(verifier) + out = [] + + verifier.run_batches { |*args| out << args } + + out + end +end diff --git a/spec/support/gpg_helpers.rb b/spec/support/helpers/gpg_helpers.rb index 3f7279a50e0..3f7279a50e0 100644 --- a/spec/support/gpg_helpers.rb +++ b/spec/support/helpers/gpg_helpers.rb diff --git a/spec/support/import_spec_helper.rb b/spec/support/helpers/import_spec_helper.rb index d4eced724fa..d4eced724fa 100644 --- a/spec/support/import_spec_helper.rb +++ b/spec/support/helpers/import_spec_helper.rb diff --git a/spec/support/input_helper.rb b/spec/support/helpers/input_helper.rb index acbb42274ec..acbb42274ec 100644 --- a/spec/support/input_helper.rb +++ b/spec/support/helpers/input_helper.rb diff --git a/spec/support/inspect_requests.rb b/spec/support/helpers/inspect_requests.rb index 88ddc5c7f6c..88ddc5c7f6c 100644 --- a/spec/support/inspect_requests.rb +++ b/spec/support/helpers/inspect_requests.rb diff --git a/spec/support/issue_helpers.rb b/spec/support/helpers/issue_helpers.rb index ffd72515f37..ffd72515f37 100644 --- a/spec/support/issue_helpers.rb +++ b/spec/support/helpers/issue_helpers.rb diff --git a/spec/support/javascript_fixtures_helpers.rb b/spec/support/helpers/javascript_fixtures_helpers.rb index 2197bc9d853..2197bc9d853 100644 --- a/spec/support/javascript_fixtures_helpers.rb +++ b/spec/support/helpers/javascript_fixtures_helpers.rb diff --git a/spec/support/jira_service_helper.rb b/spec/support/helpers/jira_service_helper.rb index 88a7aeba461..88a7aeba461 100644 --- a/spec/support/jira_service_helper.rb +++ b/spec/support/helpers/jira_service_helper.rb diff --git a/spec/support/kubernetes_helpers.rb b/spec/support/helpers/kubernetes_helpers.rb index e46b61b6461..e46b61b6461 100644 --- a/spec/support/kubernetes_helpers.rb +++ b/spec/support/helpers/kubernetes_helpers.rb diff --git a/spec/support/ldap_helpers.rb b/spec/support/helpers/ldap_helpers.rb index 0e87b3d359d..0e87b3d359d 100644 --- a/spec/support/ldap_helpers.rb +++ b/spec/support/helpers/ldap_helpers.rb diff --git a/spec/support/live_debugger.rb b/spec/support/helpers/live_debugger.rb index 911eb48a8ca..911eb48a8ca 100644 --- a/spec/support/live_debugger.rb +++ b/spec/support/helpers/live_debugger.rb diff --git a/spec/support/login_helpers.rb b/spec/support/helpers/login_helpers.rb index db34090e971..db34090e971 100644 --- a/spec/support/login_helpers.rb +++ b/spec/support/helpers/login_helpers.rb diff --git a/spec/support/markdown_feature.rb b/spec/support/helpers/markdown_feature.rb index 39e94ad53de..39e94ad53de 100644 --- a/spec/support/markdown_feature.rb +++ b/spec/support/helpers/markdown_feature.rb diff --git a/spec/support/merge_request_helpers.rb b/spec/support/helpers/merge_request_helpers.rb index 772adff4626..772adff4626 100644 --- a/spec/support/merge_request_helpers.rb +++ b/spec/support/helpers/merge_request_helpers.rb diff --git a/spec/support/migrations_helpers.rb b/spec/support/helpers/migrations_helpers.rb index 5d6f662e8fe..5d6f662e8fe 100644 --- a/spec/support/migrations_helpers.rb +++ b/spec/support/helpers/migrations_helpers.rb diff --git a/spec/support/mobile_helpers.rb b/spec/support/helpers/mobile_helpers.rb index 3b9eb84e824..3b9eb84e824 100644 --- a/spec/support/mobile_helpers.rb +++ b/spec/support/helpers/mobile_helpers.rb diff --git a/spec/support/project_forks_helper.rb b/spec/support/helpers/project_forks_helper.rb index 2c501a2a27c..2c501a2a27c 100644 --- a/spec/support/project_forks_helper.rb +++ b/spec/support/helpers/project_forks_helper.rb diff --git a/spec/support/prometheus_helpers.rb b/spec/support/helpers/prometheus_helpers.rb index 4212be2cc88..4212be2cc88 100644 --- a/spec/support/prometheus_helpers.rb +++ b/spec/support/helpers/prometheus_helpers.rb diff --git a/spec/support/helpers/query_recorder.rb b/spec/support/helpers/query_recorder.rb new file mode 100644 index 00000000000..28536bbef5e --- /dev/null +++ b/spec/support/helpers/query_recorder.rb @@ -0,0 +1,38 @@ +module ActiveRecord + class QueryRecorder + attr_reader :log, :cached + + def initialize(&block) + @log = [] + @cached = [] + ActiveSupport::Notifications.subscribed(method(:callback), 'sql.active_record', &block) + end + + def show_backtrace(values) + Rails.logger.debug("QueryRecorder SQL: #{values[:sql]}") + caller.each { |line| Rails.logger.debug(" --> #{line}") } + end + + def callback(name, start, finish, message_id, values) + show_backtrace(values) if ENV['QUERY_RECORDER_DEBUG'] + + if values[:name]&.include?("CACHE") + @cached << values[:sql] + elsif !values[:name]&.include?("SCHEMA") + @log << values[:sql] + end + end + + def count + @log.count + end + + def cached_count + @cached.count + end + + def log_message + @log.join("\n\n") + end + end +end diff --git a/spec/support/helpers/quick_actions_helpers.rb b/spec/support/helpers/quick_actions_helpers.rb new file mode 100644 index 00000000000..361190aa352 --- /dev/null +++ b/spec/support/helpers/quick_actions_helpers.rb @@ -0,0 +1,10 @@ +module QuickActionsHelpers + def write_note(text) + Sidekiq::Testing.fake! do + page.within('.js-main-target-form') do + fill_in 'note[note]', with: text + find('.js-comment-submit-button').click + end + end + end +end diff --git a/spec/support/rake_helpers.rb b/spec/support/helpers/rake_helpers.rb index 86bfeed107c..86bfeed107c 100644 --- a/spec/support/rake_helpers.rb +++ b/spec/support/helpers/rake_helpers.rb diff --git a/spec/support/reactive_caching_helpers.rb b/spec/support/helpers/reactive_caching_helpers.rb index e22dd974c6a..e22dd974c6a 100644 --- a/spec/support/reactive_caching_helpers.rb +++ b/spec/support/helpers/reactive_caching_helpers.rb diff --git a/spec/support/redis_without_keys.rb b/spec/support/helpers/redis_without_keys.rb index 6220167dee6..6220167dee6 100644 --- a/spec/support/redis_without_keys.rb +++ b/spec/support/helpers/redis_without_keys.rb diff --git a/spec/support/reference_parser_helpers.rb b/spec/support/helpers/reference_parser_helpers.rb index c01897ed1a1..c01897ed1a1 100644 --- a/spec/support/reference_parser_helpers.rb +++ b/spec/support/helpers/reference_parser_helpers.rb diff --git a/spec/support/repo_helpers.rb b/spec/support/helpers/repo_helpers.rb index 3c6956cf5e0..3c6956cf5e0 100644 --- a/spec/support/repo_helpers.rb +++ b/spec/support/helpers/repo_helpers.rb diff --git a/spec/support/search_helpers.rb b/spec/support/helpers/search_helpers.rb index abbbb636d66..abbbb636d66 100644 --- a/spec/support/search_helpers.rb +++ b/spec/support/helpers/search_helpers.rb diff --git a/spec/support/seed_helper.rb b/spec/support/helpers/seed_helper.rb index 11ef1fc477f..8fd107260cc 100644 --- a/spec/support/seed_helper.rb +++ b/spec/support/helpers/seed_helper.rb @@ -9,7 +9,7 @@ TEST_MUTABLE_REPO_PATH = 'mutable-repo.git'.freeze TEST_BROKEN_REPO_PATH = 'broken-repo.git'.freeze module SeedHelper - GITLAB_GIT_TEST_REPO_URL = File.expand_path('../gitlab-git-test.git', __FILE__).freeze + GITLAB_GIT_TEST_REPO_URL = File.expand_path('../gitlab-git-test.git', __dir__).freeze def ensure_seeds if File.exist?(SEED_STORAGE_PATH) @@ -108,11 +108,3 @@ bla/bla.txt { 'GIT_TEMPLATE_DIR' => '' } end end - -RSpec.configure do |config| - config.include SeedHelper, :seed_helper - - config.before(:all, :seed_helper) do - ensure_seeds - end -end diff --git a/spec/support/seed_repo.rb b/spec/support/helpers/seed_repo.rb index b4868e82cd7..b4868e82cd7 100644 --- a/spec/support/seed_repo.rb +++ b/spec/support/helpers/seed_repo.rb diff --git a/spec/support/select2_helper.rb b/spec/support/helpers/select2_helper.rb index 90618ba5b19..90618ba5b19 100644 --- a/spec/support/select2_helper.rb +++ b/spec/support/helpers/select2_helper.rb diff --git a/spec/support/selection_helper.rb b/spec/support/helpers/selection_helper.rb index b4725b137b2..b4725b137b2 100644 --- a/spec/support/selection_helper.rb +++ b/spec/support/helpers/selection_helper.rb diff --git a/spec/support/helpers/sorting_helper.rb b/spec/support/helpers/sorting_helper.rb new file mode 100644 index 00000000000..577518d726c --- /dev/null +++ b/spec/support/helpers/sorting_helper.rb @@ -0,0 +1,18 @@ +# Helper allows you to sort items +# +# Params +# value - value for sorting +# +# Usage: +# include SortingHelper +# +# sorting_by('Oldest updated') +# +module SortingHelper + def sorting_by(value) + find('button.dropdown-toggle').click + page.within('.content ul.dropdown-menu.dropdown-menu-align-right li') do + click_link value + end + end +end diff --git a/spec/support/stub_configuration.rb b/spec/support/helpers/stub_configuration.rb index a75a3eaefcb..1823099dd9c 100644 --- a/spec/support/stub_configuration.rb +++ b/spec/support/helpers/stub_configuration.rb @@ -1,3 +1,6 @@ +require 'active_support/core_ext/hash/transform_values' +require 'active_support/hash_with_indifferent_access' + module StubConfiguration def stub_application_setting(messages) add_predicates(messages) diff --git a/spec/support/stub_env.rb b/spec/support/helpers/stub_env.rb index 36b90fc68d6..36b90fc68d6 100644 --- a/spec/support/stub_env.rb +++ b/spec/support/helpers/stub_env.rb diff --git a/spec/support/stub_feature_flags.rb b/spec/support/helpers/stub_feature_flags.rb index b96338bf548..b96338bf548 100644 --- a/spec/support/stub_feature_flags.rb +++ b/spec/support/helpers/stub_feature_flags.rb diff --git a/spec/support/stub_gitlab_calls.rb b/spec/support/helpers/stub_gitlab_calls.rb index c1618f5086c..c1618f5086c 100644 --- a/spec/support/stub_gitlab_calls.rb +++ b/spec/support/helpers/stub_gitlab_calls.rb diff --git a/spec/support/stub_gitlab_data.rb b/spec/support/helpers/stub_gitlab_data.rb index fa402f35b95..fa402f35b95 100644 --- a/spec/support/stub_gitlab_data.rb +++ b/spec/support/helpers/stub_gitlab_data.rb diff --git a/spec/support/stub_object_storage.rb b/spec/support/helpers/stub_object_storage.rb index 6e88641da42..19d744b959a 100644 --- a/spec/support/stub_object_storage.rb +++ b/spec/support/helpers/stub_object_storage.rb @@ -1,4 +1,4 @@ -module StubConfiguration +module StubObjectStorage def stub_object_storage_uploader( config:, uploader:, diff --git a/spec/support/test_env.rb b/spec/support/helpers/test_env.rb index d87f265cdf0..d87f265cdf0 100644 --- a/spec/support/test_env.rb +++ b/spec/support/helpers/test_env.rb diff --git a/spec/support/upload_helpers.rb b/spec/support/helpers/upload_helpers.rb index 5eead80c935..5eead80c935 100644 --- a/spec/support/upload_helpers.rb +++ b/spec/support/helpers/upload_helpers.rb diff --git a/spec/support/user_activities_helpers.rb b/spec/support/helpers/user_activities_helpers.rb index 44feb104644..44feb104644 100644 --- a/spec/support/user_activities_helpers.rb +++ b/spec/support/helpers/user_activities_helpers.rb diff --git a/spec/support/wait_for_requests.rb b/spec/support/helpers/wait_for_requests.rb index fda0e29f983..fda0e29f983 100644 --- a/spec/support/wait_for_requests.rb +++ b/spec/support/helpers/wait_for_requests.rb diff --git a/spec/support/workhorse_helpers.rb b/spec/support/helpers/workhorse_helpers.rb index ef1f9f68671..ef1f9f68671 100644 --- a/spec/support/workhorse_helpers.rb +++ b/spec/support/helpers/workhorse_helpers.rb diff --git a/spec/support/json_response_helpers.rb b/spec/support/json_response.rb index aa235529c56..210b0e6d867 100644 --- a/spec/support/json_response_helpers.rb +++ b/spec/support/json_response.rb @@ -1,7 +1,3 @@ -shared_context 'JSON response' do - let(:json_response) { JSON.parse(response.body) } -end - RSpec.configure do |config| config.include_context 'JSON response' config.include_context 'JSON response', type: :request diff --git a/spec/support/background_migrations_matchers.rb b/spec/support/matchers/background_migrations_matchers.rb index f4127efc6ae..f4127efc6ae 100644 --- a/spec/support/background_migrations_matchers.rb +++ b/spec/support/matchers/background_migrations_matchers.rb diff --git a/spec/support/query_recorder.rb b/spec/support/matchers/exceed_query_limit.rb index 8cf8f45a8b2..88d22a3ddd9 100644 --- a/spec/support/query_recorder.rb +++ b/spec/support/matchers/exceed_query_limit.rb @@ -1,42 +1,3 @@ -module ActiveRecord - class QueryRecorder - attr_reader :log, :cached - - def initialize(&block) - @log = [] - @cached = [] - ActiveSupport::Notifications.subscribed(method(:callback), 'sql.active_record', &block) - end - - def show_backtrace(values) - Rails.logger.debug("QueryRecorder SQL: #{values[:sql]}") - caller.each { |line| Rails.logger.debug(" --> #{line}") } - end - - def callback(name, start, finish, message_id, values) - show_backtrace(values) if ENV['QUERY_RECORDER_DEBUG'] - - if values[:name]&.include?("CACHE") - @cached << values[:sql] - elsif !values[:name]&.include?("SCHEMA") - @log << values[:sql] - end - end - - def count - @log.count - end - - def cached_count - @cached.count - end - - def log_message - @log.join("\n\n") - end - end -end - RSpec::Matchers.define :exceed_query_limit do |expected| supports_block_expectations diff --git a/spec/support/prepare-gitlab-git-test-for-commit b/spec/support/prepare-gitlab-git-test-for-commit index 3047786a599..d08e3ba5481 100755 --- a/spec/support/prepare-gitlab-git-test-for-commit +++ b/spec/support/prepare-gitlab-git-test-for-commit @@ -1,7 +1,7 @@ #!/usr/bin/env ruby abort unless [ - system('spec/support/generate-seed-repo-rb', out: 'spec/support/seed_repo.rb'), + system('spec/support/generate-seed-repo-rb', out: 'spec/support/helpers/seed_repo.rb'), system('spec/support/unpack-gitlab-git-test') ].all? diff --git a/spec/support/routing_helpers.rb b/spec/support/routing_helpers.rb deleted file mode 100644 index af1f4760804..00000000000 --- a/spec/support/routing_helpers.rb +++ /dev/null @@ -1,3 +0,0 @@ -RSpec.configure do |config| - config.include GitlabRoutingHelper -end diff --git a/spec/support/rspec.rb b/spec/support/rspec.rb new file mode 100644 index 00000000000..dffab22d8b5 --- /dev/null +++ b/spec/support/rspec.rb @@ -0,0 +1,12 @@ +require_relative "helpers/stub_configuration" +require_relative "helpers/stub_object_storage" +require_relative "helpers/stub_env" + +RSpec.configure do |config| + config.mock_with :rspec + config.raise_errors_for_deprecations! + + config.include StubConfiguration + config.include StubObjectStorage + config.include StubENV +end diff --git a/spec/support/seed.rb b/spec/support/seed.rb new file mode 100644 index 00000000000..bea2e9c3044 --- /dev/null +++ b/spec/support/seed.rb @@ -0,0 +1,7 @@ +RSpec.configure do |config| + config.include SeedHelper, :seed_helper + + config.before(:all, :seed_helper) do + ensure_seeds + end +end diff --git a/spec/support/shared_contexts/json_response_shared_context.rb b/spec/support/shared_contexts/json_response_shared_context.rb new file mode 100644 index 00000000000..df5fc288089 --- /dev/null +++ b/spec/support/shared_contexts/json_response_shared_context.rb @@ -0,0 +1,3 @@ +shared_context 'JSON response' do + let(:json_response) { JSON.parse(response.body) } +end diff --git a/spec/support/services_shared_context.rb b/spec/support/shared_contexts/services_shared_context.rb index 23f9b46ae0c..23f9b46ae0c 100644 --- a/spec/support/services_shared_context.rb +++ b/spec/support/shared_contexts/services_shared_context.rb diff --git a/spec/support/chat_slash_commands_shared_examples.rb b/spec/support/shared_examples/chat_slash_commands_shared_examples.rb index dc97a39f051..dc97a39f051 100644 --- a/spec/support/chat_slash_commands_shared_examples.rb +++ b/spec/support/shared_examples/chat_slash_commands_shared_examples.rb diff --git a/spec/support/email_format_shared_examples.rb b/spec/support/shared_examples/email_format_shared_examples.rb index b924a208e71..b924a208e71 100644 --- a/spec/support/email_format_shared_examples.rb +++ b/spec/support/shared_examples/email_format_shared_examples.rb diff --git a/spec/support/gitlab_verify.rb b/spec/support/shared_examples/gitlab_verify.rb index 13e2e37624d..560913ca92f 100644 --- a/spec/support/gitlab_verify.rb +++ b/spec/support/shared_examples/gitlab_verify.rb @@ -17,29 +17,3 @@ RSpec.shared_examples 'Gitlab::Verify::BatchVerifier subclass' do end end end - -module GitlabVerifyHelpers - def collect_ranges(args = {}) - verifier = described_class.new(args.merge(batch_size: 1)) - - collect_results(verifier).map { |range, _| range } - end - - def collect_failures - verifier = described_class.new(batch_size: 1) - - out = {} - - collect_results(verifier).map { |_, failures| out.merge!(failures) } - - out - end - - def collect_results(verifier) - out = [] - - verifier.run_batches { |*args| out << args } - - out - end -end diff --git a/spec/support/group_members_shared_example.rb b/spec/support/shared_examples/group_members_shared_example.rb index 547c83c7955..547c83c7955 100644 --- a/spec/support/group_members_shared_example.rb +++ b/spec/support/shared_examples/group_members_shared_example.rb diff --git a/spec/support/issuable_shared_examples.rb b/spec/support/shared_examples/issuable_shared_examples.rb index 42f3b4db23c..42f3b4db23c 100644 --- a/spec/support/issuable_shared_examples.rb +++ b/spec/support/shared_examples/issuable_shared_examples.rb diff --git a/spec/support/issuables_list_metadata_shared_examples.rb b/spec/support/shared_examples/issuables_list_metadata_shared_examples.rb index e61983c60b4..e61983c60b4 100644 --- a/spec/support/issuables_list_metadata_shared_examples.rb +++ b/spec/support/shared_examples/issuables_list_metadata_shared_examples.rb diff --git a/spec/support/issue_tracker_service_shared_example.rb b/spec/support/shared_examples/issue_tracker_service_shared_example.rb index a6ab03cb808..a6ab03cb808 100644 --- a/spec/support/issue_tracker_service_shared_example.rb +++ b/spec/support/shared_examples/issue_tracker_service_shared_example.rb diff --git a/spec/support/ldap_shared_examples.rb b/spec/support/shared_examples/ldap_shared_examples.rb index 52c34e78965..52c34e78965 100644 --- a/spec/support/ldap_shared_examples.rb +++ b/spec/support/shared_examples/ldap_shared_examples.rb diff --git a/spec/support/legacy_path_redirect_shared_examples.rb b/spec/support/shared_examples/legacy_path_redirect_shared_examples.rb index f300bdd48b1..f300bdd48b1 100644 --- a/spec/support/legacy_path_redirect_shared_examples.rb +++ b/spec/support/shared_examples/legacy_path_redirect_shared_examples.rb diff --git a/spec/support/malicious_regexp_shared_examples.rb b/spec/support/shared_examples/malicious_regexp_shared_examples.rb index ac5d22298bb..ac5d22298bb 100644 --- a/spec/support/malicious_regexp_shared_examples.rb +++ b/spec/support/shared_examples/malicious_regexp_shared_examples.rb diff --git a/spec/support/mentionable_shared_examples.rb b/spec/support/shared_examples/mentionable_shared_examples.rb index 1685decbe94..1685decbe94 100644 --- a/spec/support/mentionable_shared_examples.rb +++ b/spec/support/shared_examples/mentionable_shared_examples.rb diff --git a/spec/support/milestone_tabs_examples.rb b/spec/support/shared_examples/milestone_tabs_examples.rb index 70b499198bf..70b499198bf 100644 --- a/spec/support/milestone_tabs_examples.rb +++ b/spec/support/shared_examples/milestone_tabs_examples.rb diff --git a/spec/support/notify_shared_examples.rb b/spec/support/shared_examples/notify_shared_examples.rb index e2c23607406..e2c23607406 100644 --- a/spec/support/notify_shared_examples.rb +++ b/spec/support/shared_examples/notify_shared_examples.rb diff --git a/spec/support/reference_parser_shared_examples.rb b/spec/support/shared_examples/reference_parser_shared_examples.rb index baf8bcc04b8..baf8bcc04b8 100644 --- a/spec/support/reference_parser_shared_examples.rb +++ b/spec/support/shared_examples/reference_parser_shared_examples.rb diff --git a/spec/support/slack_mattermost_notifications_shared_examples.rb b/spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb index 07bc3a51fd8..07bc3a51fd8 100644 --- a/spec/support/slack_mattermost_notifications_shared_examples.rb +++ b/spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb diff --git a/spec/support/snippet_visibility.rb b/spec/support/shared_examples/snippet_visibility.rb index 3a7c69b7877..3a7c69b7877 100644 --- a/spec/support/snippet_visibility.rb +++ b/spec/support/shared_examples/snippet_visibility.rb diff --git a/spec/support/snippets_shared_examples.rb b/spec/support/shared_examples/snippets_shared_examples.rb index 85f0facd5c3..85f0facd5c3 100644 --- a/spec/support/snippets_shared_examples.rb +++ b/spec/support/shared_examples/snippets_shared_examples.rb diff --git a/spec/support/taskable_shared_examples.rb b/spec/support/shared_examples/taskable_shared_examples.rb index 4056ff06b84..4056ff06b84 100644 --- a/spec/support/taskable_shared_examples.rb +++ b/spec/support/shared_examples/taskable_shared_examples.rb diff --git a/spec/support/time_tracking_shared_examples.rb b/spec/support/shared_examples/time_tracking_shared_examples.rb index 909d4e2ee8d..909d4e2ee8d 100644 --- a/spec/support/time_tracking_shared_examples.rb +++ b/spec/support/shared_examples/time_tracking_shared_examples.rb diff --git a/spec/support/unique_ip_check_shared_examples.rb b/spec/support/shared_examples/unique_ip_check_shared_examples.rb index e5c8ac6a004..e5c8ac6a004 100644 --- a/spec/support/unique_ip_check_shared_examples.rb +++ b/spec/support/shared_examples/unique_ip_check_shared_examples.rb diff --git a/spec/support/update_invalid_issuable.rb b/spec/support/shared_examples/update_invalid_issuable.rb index 1490287681b..1490287681b 100644 --- a/spec/support/update_invalid_issuable.rb +++ b/spec/support/shared_examples/update_invalid_issuable.rb diff --git a/spec/support/updating_mentions_shared_examples.rb b/spec/support/shared_examples/updating_mentions_shared_examples.rb index 5e3f19ba19e..5e3f19ba19e 100644 --- a/spec/support/updating_mentions_shared_examples.rb +++ b/spec/support/shared_examples/updating_mentions_shared_examples.rb diff --git a/spec/views/projects/settings/ci_cd/_form.html.haml_spec.rb b/spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb index be9a4d9c57c..d15391911c1 100644 --- a/spec/views/projects/settings/ci_cd/_form.html.haml_spec.rb +++ b/spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe 'projects/settings/ci_cd/_form' do +describe 'projects/settings/ci_cd/_autodevops_form' do let(:project) { create(:project, :repository) } before do diff --git a/spec/workers/issue_due_scheduler_worker_spec.rb b/spec/workers/issue_due_scheduler_worker_spec.rb index 7b60835fd26..2710267d384 100644 --- a/spec/workers/issue_due_scheduler_worker_spec.rb +++ b/spec/workers/issue_due_scheduler_worker_spec.rb @@ -14,7 +14,9 @@ describe IssueDueSchedulerWorker do create(:issue, :closed, project: project_closed_issue, due_date: Date.tomorrow) create(:issue, :opened, project: project_issue_due_another_day, due_date: Date.today) - expect(MailScheduler::IssueDueWorker).to receive(:bulk_perform_async).with([[project1.id], [project2.id]]) + expect(MailScheduler::IssueDueWorker).to receive(:bulk_perform_async) do |args| + expect(args).to match_array([[project1.id], [project2.id]]) + end described_class.new.perform end |