diff options
69 files changed, 365 insertions, 1932 deletions
diff --git a/.rubocop_todo/security/open.yml b/.rubocop_todo/security/open.yml deleted file mode 100644 index 9626b442a92..00000000000 --- a/.rubocop_todo/security/open.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -Security/Open: - Exclude: - - 'scripts/lib/glfm/update_specification.rb' diff --git a/.rubocop_todo/style/single_argument_dig.yml b/.rubocop_todo/style/single_argument_dig.yml index 860183426e9..3ffd27d26ae 100644 --- a/.rubocop_todo/style/single_argument_dig.yml +++ b/.rubocop_todo/style/single_argument_dig.yml @@ -1,9 +1,7 @@ --- # Cop supports --auto-correct. Style/SingleArgumentDig: - # Offense count: 150 - # Temporarily disabled due to too many offenses - Enabled: false + Details: grace period Exclude: - 'app/graphql/resolvers/namespace_projects_resolver.rb' - 'app/models/ci/build.rb' @@ -22,6 +20,7 @@ Style/SingleArgumentDig: - 'ee/app/workers/concerns/elastic/migration_helper.rb' - 'ee/lib/gitlab/ci/parsers/security/dependency_list.rb' - 'ee/lib/gitlab/subscription_portal/clients/graphql.rb' + - 'ee/spec/elastic/migrate/20220119120500_populate_commit_permissions_in_main_index_spec.rb' - 'ee/spec/graphql/mutations/vulnerabilities/create_spec.rb' - 'ee/spec/lib/ee/gitlab/ci/parsers/security/common_spec.rb' - 'ee/spec/lib/ee/gitlab/ci/pipeline/chain/validate/external_spec.rb' @@ -35,10 +34,11 @@ Style/SingleArgumentDig: - 'ee/spec/requests/api/graphql/project/dast_site_profiles_spec.rb' - 'ee/spec/requests/api/graphql/project/requirements_management/requirements_spec.rb' - 'ee/spec/requests/api/internal/upcoming_reconciliations_spec.rb' + - 'ee/spec/services/vulnerabilities/findings/find_or_create_from_security_finding_service_spec.rb' - 'ee/spec/services/vulnerabilities/manually_create_service_spec.rb' + - 'lib/gitlab/auth/o_auth/auth_hash.rb' - 'lib/gitlab/ci/badge/coverage/template.rb' - - 'lib/gitlab/ci/badge/pipeline/template.rb' - - 'lib/gitlab/ci/badge/release/template.rb' + - 'lib/gitlab/ci/badge/template.rb' - 'lib/gitlab/ci/lint.rb' - 'lib/gitlab/ci/parsers/accessibility/pa11y.rb' - 'lib/gitlab/ci/parsers/security/common.rb' @@ -51,11 +51,13 @@ Style/SingleArgumentDig: - 'lib/gitlab/database/transaction/observer.rb' - 'lib/gitlab/serverless/service.rb' - 'qa/qa/specs/features/browser_ui/2_plan/email/trigger_email_notification_spec.rb' + - 'qa/qa/vendor/mail_hog/api.rb' - 'spec/controllers/graphql_controller_spec.rb' - 'spec/graphql/types/release_links_type_spec.rb' - 'spec/helpers/projects_helper_spec.rb' - 'spec/lib/gitlab/ci/yaml_processor_spec.rb' - 'spec/requests/api/ci/runner/jobs_request_post_spec.rb' + - 'spec/requests/api/graphql/ci/instance_variables_spec.rb' - 'spec/requests/api/graphql/container_repository/container_repository_details_spec.rb' - 'spec/requests/api/graphql/project/container_repositories_spec.rb' - 'spec/requests/api/graphql/project/jira_import_spec.rb' diff --git a/app/assets/javascripts/diffs/components/commit_item.vue b/app/assets/javascripts/diffs/components/commit_item.vue index 0e5acd0928b..c970f0f8942 100644 --- a/app/assets/javascripts/diffs/components/commit_item.vue +++ b/app/assets/javascripts/diffs/components/commit_item.vue @@ -128,7 +128,7 @@ export default { :img-src="authorAvatar" :img-alt="authorName" :img-size="32" - class="avatar-cell d-none d-sm-block" + class="avatar-cell d-none d-sm-block gl-my-2 gl-mr-4" /> </div> <div diff --git a/app/assets/javascripts/issues/show/components/incidents/incident_tabs.vue b/app/assets/javascripts/issues/show/components/incidents/incident_tabs.vue index dd84a1d7d67..5725d0f8d6a 100644 --- a/app/assets/javascripts/issues/show/components/incidents/incident_tabs.vue +++ b/app/assets/javascripts/issues/show/components/incidents/incident_tabs.vue @@ -52,9 +52,6 @@ export default { loading() { return this.$apollo.queries.alert.loading; }, - incidentTabEnabled() { - return this.glFeatures.incidentTimeline; - }, }, mounted() { this.trackPageViews(); @@ -112,7 +109,7 @@ export default { > <alert-details-table :alert="alert" :loading="loading" /> </gl-tab> - <timeline-tab v-if="incidentTabEnabled" /> + <timeline-tab /> </gl-tabs> </div> </template> diff --git a/app/assets/javascripts/reports/accessibility_report/components/accessibility_issue_body.vue b/app/assets/javascripts/reports/accessibility_report/components/accessibility_issue_body.vue deleted file mode 100644 index 05ab5c2cc90..00000000000 --- a/app/assets/javascripts/reports/accessibility_report/components/accessibility_issue_body.vue +++ /dev/null @@ -1,61 +0,0 @@ -<script> -import { GlBadge, GlLink } from '@gitlab/ui'; - -export default { - name: 'AccessibilityIssueBody', - components: { - GlBadge, - GlLink, - }, - props: { - issue: { - type: Object, - required: true, - }, - isNew: { - type: Boolean, - required: false, - default: false, - }, - }, - computed: { - parsedTECHSCode() { - /* - * In issue code looks like "WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail" - * or "WCAG2AA.Principle4.Guideline4_1.4_1_2.H91.A.NoContent" - * - * The TECHS code is the "G18", "G168", "H91", etc. from the code which is used for the documentation. - * Here we simply split the string on `.` and get the code in the 5th position - */ - return this.issue.code?.split('.')[4]; - }, - learnMoreUrl() { - // eslint-disable-next-line @gitlab/require-i18n-strings - return `https://www.w3.org/TR/WCAG20-TECHS/${this.parsedTECHSCode || 'Overview'}.html`; - }, - }, -}; -</script> -<template> - <div class="report-block-list-issue-description gl-mt-2 gl-mb-2"> - <div ref="accessibility-issue-description" class="report-block-list-issue-description-text"> - <gl-badge v-if="isNew" class="gl-mr-2" variant="danger">{{ - s__('AccessibilityReport|New') - }}</gl-badge> - <div> - {{ - sprintf( - s__( - 'AccessibilityReport|The accessibility scanning found an error of the following type: %{code}', - ), - { code: issue.code }, - ) - }} - <gl-link ref="accessibility-issue-learn-more" :href="learnMoreUrl" target="_blank">{{ - s__('AccessibilityReport|Learn more') - }}</gl-link> - </div> - {{ sprintf(s__('AccessibilityReport|Message: %{message}'), { message: issue.message }) }} - </div> - </div> -</template> diff --git a/app/assets/javascripts/reports/accessibility_report/grouped_accessibility_reports_app.vue b/app/assets/javascripts/reports/accessibility_report/grouped_accessibility_reports_app.vue deleted file mode 100644 index 99cdeae545e..00000000000 --- a/app/assets/javascripts/reports/accessibility_report/grouped_accessibility_reports_app.vue +++ /dev/null @@ -1,65 +0,0 @@ -<script> -import { mapActions, mapGetters } from 'vuex'; -import { componentNames } from '~/reports/components/issue_body'; -import IssuesList from '~/reports/components/issues_list.vue'; -import ReportSection from '~/reports/components/report_section.vue'; -import createStore from './store'; - -export default { - name: 'GroupedAccessibilityReportsApp', - store: createStore(), - components: { - ReportSection, - IssuesList, - }, - props: { - endpoint: { - type: String, - required: true, - }, - }, - componentNames, - computed: { - ...mapGetters([ - 'summaryStatus', - 'groupedSummaryText', - 'shouldRenderIssuesList', - 'unresolvedIssues', - 'resolvedIssues', - 'newIssues', - ]), - }, - created() { - this.setEndpoint(this.endpoint); - - this.fetchReport(); - }, - methods: { - ...mapActions(['fetchReport', 'setEndpoint']), - }, -}; -</script> -<template> - <report-section - :status="summaryStatus" - :success-text="groupedSummaryText" - :loading-text="groupedSummaryText" - :error-text="groupedSummaryText" - :has-issues="shouldRenderIssuesList" - track-action="users_expanding_testing_accessibility_report" - class="mr-widget-section grouped-security-reports mr-report" - > - <template #body> - <div class="mr-widget-grouped-section report-block"> - <issues-list - v-if="shouldRenderIssuesList" - :unresolved-issues="unresolvedIssues" - :new-issues="newIssues" - :resolved-issues="resolvedIssues" - :component="$options.componentNames.AccessibilityIssueBody" - class="report-block-group-list" - /> - </div> - </template> - </report-section> -</template> diff --git a/app/assets/javascripts/reports/accessibility_report/store/actions.js b/app/assets/javascripts/reports/accessibility_report/store/actions.js deleted file mode 100644 index e0142a35291..00000000000 --- a/app/assets/javascripts/reports/accessibility_report/store/actions.js +++ /dev/null @@ -1,76 +0,0 @@ -import Visibility from 'visibilityjs'; -import axios from '~/lib/utils/axios_utils'; -import httpStatusCodes from '~/lib/utils/http_status'; -import Poll from '~/lib/utils/poll'; -import * as types from './mutation_types'; - -let eTagPoll; - -export const clearEtagPoll = () => { - eTagPoll = null; -}; - -export const stopPolling = () => { - if (eTagPoll) eTagPoll.stop(); -}; - -export const restartPolling = () => { - if (eTagPoll) eTagPoll.restart(); -}; - -export const setEndpoint = ({ commit }, endpoint) => commit(types.SET_ENDPOINT, endpoint); - -/** - * We need to poll the report endpoint while they are being parsed in the Backend. - * This can take up to one minute. - * - * Poll.js will handle etag response. - * While http status code is 204, it means it's parsing, and we'll keep polling - * When http status code is 200, it means parsing is done, we can show the results & stop polling - * When http status code is 500, it means parsing went wrong and we stop polling - */ -export const fetchReport = ({ state, dispatch, commit }) => { - commit(types.REQUEST_REPORT); - - eTagPoll = new Poll({ - resource: { - getReport(endpoint) { - return axios.get(endpoint); - }, - }, - data: state.endpoint, - method: 'getReport', - successCallback: ({ status, data }) => dispatch('receiveReportSuccess', { status, data }), - errorCallback: () => dispatch('receiveReportError'), - }); - - if (!Visibility.hidden()) { - eTagPoll.makeRequest(); - } else { - axios - .get(state.endpoint) - .then(({ status, data }) => dispatch('receiveReportSuccess', { status, data })) - .catch(() => dispatch('receiveReportError')); - } - - Visibility.change(() => { - if (!Visibility.hidden() && state.isLoading) { - dispatch('restartPolling'); - } else { - dispatch('stopPolling'); - } - }); -}; - -export const receiveReportSuccess = ({ commit, dispatch }, { status, data }) => { - if (status === httpStatusCodes.OK) { - commit(types.RECEIVE_REPORT_SUCCESS, data); - // Stop polling since we have the information already parsed and it won't be changing - dispatch('stopPolling'); - } -}; - -export const receiveReportError = ({ commit, dispatch }) => { - commit(types.RECEIVE_REPORT_ERROR); - dispatch('stopPolling'); -}; diff --git a/app/assets/javascripts/reports/accessibility_report/store/getters.js b/app/assets/javascripts/reports/accessibility_report/store/getters.js deleted file mode 100644 index 20506b1bfd1..00000000000 --- a/app/assets/javascripts/reports/accessibility_report/store/getters.js +++ /dev/null @@ -1,45 +0,0 @@ -import { s__, n__ } from '~/locale'; -import { LOADING, ERROR, SUCCESS, STATUS_FAILED } from '../../constants'; - -export const groupedSummaryText = (state) => { - if (state.isLoading) { - return s__('Reports|Accessibility scanning results are being parsed'); - } - - if (state.hasError) { - return s__('Reports|Accessibility scanning failed loading results'); - } - - const numberOfResults = state.report?.summary?.errored || 0; - if (numberOfResults === 0) { - return s__('Reports|Accessibility scanning detected no issues for the source branch only'); - } - - return n__( - 'Reports|Accessibility scanning detected %d issue for the source branch only', - 'Reports|Accessibility scanning detected %d issues for the source branch only', - numberOfResults, - ); -}; - -export const summaryStatus = (state) => { - if (state.isLoading) { - return LOADING; - } - - if (state.hasError || state.status === STATUS_FAILED) { - return ERROR; - } - - return SUCCESS; -}; - -export const shouldRenderIssuesList = (state) => - Object.values(state.report).some((x) => Array.isArray(x) && x.length > 0); - -// We could just map state, but we're going to iterate in the future -// to add notes and warnings to these issue lists, so I'm going to -// keep these as getters -export const unresolvedIssues = (state) => state.report.existing_errors; -export const resolvedIssues = (state) => state.report.resolved_errors; -export const newIssues = (state) => state.report.new_errors; diff --git a/app/assets/javascripts/reports/accessibility_report/store/index.js b/app/assets/javascripts/reports/accessibility_report/store/index.js deleted file mode 100644 index 5bfcd69edec..00000000000 --- a/app/assets/javascripts/reports/accessibility_report/store/index.js +++ /dev/null @@ -1,17 +0,0 @@ -import Vue from 'vue'; -import Vuex from 'vuex'; -import * as actions from './actions'; -import * as getters from './getters'; -import mutations from './mutations'; -import state from './state'; - -Vue.use(Vuex); - -export const getStoreConfig = (initialState) => ({ - actions, - getters, - mutations, - state: state(initialState), -}); - -export default (initialState) => new Vuex.Store(getStoreConfig(initialState)); diff --git a/app/assets/javascripts/reports/accessibility_report/store/mutation_types.js b/app/assets/javascripts/reports/accessibility_report/store/mutation_types.js deleted file mode 100644 index 22e2330e1ea..00000000000 --- a/app/assets/javascripts/reports/accessibility_report/store/mutation_types.js +++ /dev/null @@ -1,5 +0,0 @@ -export const SET_ENDPOINT = 'SET_ENDPOINT'; - -export const REQUEST_REPORT = 'REQUEST_REPORT'; -export const RECEIVE_REPORT_SUCCESS = 'RECEIVE_REPORT_SUCCESS'; -export const RECEIVE_REPORT_ERROR = 'RECEIVE_REPORT_ERROR'; diff --git a/app/assets/javascripts/reports/accessibility_report/store/mutations.js b/app/assets/javascripts/reports/accessibility_report/store/mutations.js deleted file mode 100644 index 20d3e5be9a3..00000000000 --- a/app/assets/javascripts/reports/accessibility_report/store/mutations.js +++ /dev/null @@ -1,20 +0,0 @@ -import * as types from './mutation_types'; - -export default { - [types.SET_ENDPOINT](state, endpoint) { - state.endpoint = endpoint; - }, - [types.REQUEST_REPORT](state) { - state.isLoading = true; - }, - [types.RECEIVE_REPORT_SUCCESS](state, report) { - state.hasError = false; - state.isLoading = false; - state.report = report; - }, - [types.RECEIVE_REPORT_ERROR](state) { - state.isLoading = false; - state.hasError = true; - state.report = {}; - }, -}; diff --git a/app/assets/javascripts/reports/accessibility_report/store/state.js b/app/assets/javascripts/reports/accessibility_report/store/state.js deleted file mode 100644 index 2a4cefea5e6..00000000000 --- a/app/assets/javascripts/reports/accessibility_report/store/state.js +++ /dev/null @@ -1,28 +0,0 @@ -export default (initialState = {}) => ({ - endpoint: initialState.endpoint || '', - - isLoading: initialState.isLoading || false, - hasError: initialState.hasError || false, - - /** - * Report will have the following format: - * { - * status: {String}, - * summary: { - * total: {Number}, - * resolved: {Number}, - * errored: {Number}, - * }, - * existing_errors: {Array.<Object>}, - * existing_notes: {Array.<Object>}, - * existing_warnings: {Array.<Object>}, - * new_errors: {Array.<Object>}, - * new_notes: {Array.<Object>}, - * new_warnings: {Array.<Object>}, - * resolved_errors: {Array.<Object>}, - * resolved_notes: {Array.<Object>}, - * resolved_warnings: {Array.<Object>}, - * } - */ - report: initialState.report || {}, -}); diff --git a/app/assets/javascripts/reports/components/issue_body.js b/app/assets/javascripts/reports/components/issue_body.js index 04e72809e62..a76a6f45c07 100644 --- a/app/assets/javascripts/reports/components/issue_body.js +++ b/app/assets/javascripts/reports/components/issue_body.js @@ -1,14 +1,11 @@ import IssueStatusIcon from '~/reports/components/issue_status_icon.vue'; export const components = { - AccessibilityIssueBody: () => - import('../accessibility_report/components/accessibility_issue_body.vue'), CodequalityIssueBody: () => import('../codequality_report/components/codequality_issue_body.vue'), TestIssueBody: () => import('../grouped_test_report/components/test_issue_body.vue'), }; export const componentNames = { - AccessibilityIssueBody: 'AccessibilityIssueBody', CodequalityIssueBody: 'CodequalityIssueBody', TestIssueBody: 'TestIssueBody', }; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/action_buttons.vue b/app/assets/javascripts/vue_merge_request_widget/components/action_buttons.vue index f3186723a49..0e858fb30e6 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/action_buttons.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/action_buttons.vue @@ -77,6 +77,8 @@ export default { <div class="gl-display-flex"> <gl-dropdown v-if="tertiaryButtons.length" + v-gl-tooltip + :title="__('Options')" :text="dropdownLabel" icon="ellipsis_v" no-caret diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue index 5e89d28ee50..c5ea336bd96 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue @@ -537,130 +537,148 @@ export default { <div class="mr-widget-body mr-widget-body-ready-merge media mr-widget-body-line-height-1"> <div class="media-body"> <div class="mr-widget-body-controls gl-display-flex gl-align-items-center gl-flex-wrap"> - <gl-button-group v-if="shouldShowMergeControls" class="gl-align-self-start"> - <gl-button - size="medium" - category="primary" - class="accept-merge-request" - data-testid="merge-button" - variant="confirm" - :disabled="isMergeButtonDisabled" - :loading="isMakingRequest" - data-qa-selector="merge_button" - @click="handleMergeButtonClick(isAutoMergeAvailable)" - >{{ mergeButtonText }}</gl-button - > - <gl-dropdown - v-if="shouldShowMergeImmediatelyDropdown" - v-gl-tooltip.hover.focus="__('Select merge moment')" - :disabled="isMergeButtonDisabled" - variant="confirm" - data-qa-selector="merge_moment_dropdown" - toggle-class="btn-icon js-merge-moment" - > - <template #button-content> - <gl-icon name="chevron-down" class="mr-0" /> - <span class="sr-only">{{ __('Select merge moment') }}</span> - </template> - <gl-dropdown-item - icon-name="warning" - button-class="accept-merge-request js-merge-immediately-button" - data-qa-selector="merge_immediately_menu_item" - @click="handleMergeImmediatelyButtonClick" + <template v-if="shouldShowMergeControls"> + <div class="gl-display-flex gl-align-items-center gl-flex-wrap gl-w-full gl-mb-5"> + <gl-form-checkbox + v-if="canRemoveSourceBranch" + id="remove-source-branch-input" + v-model="removeSourceBranch" + :disabled="isRemoveSourceBranchButtonDisabled" + class="js-remove-source-branch-checkbox gl-display-flex gl-align-items-center gl-mr-5" > - {{ __('Merge immediately') }} - </gl-dropdown-item> - <merge-immediately-confirmation-dialog - ref="confirmationDialog" - :docs-url="mr.mergeImmediatelyDocsPath" - @mergeImmediately="onMergeImmediatelyConfirmation" + {{ __('Delete source branch') }} + </gl-form-checkbox> + + <!-- Placeholder for EE extension of this component --> + <squash-before-merge + v-if="shouldShowSquashBeforeMerge" + v-model="squashBeforeMerge" + :help-path="mr.squashBeforeMergeHelpPath" + :is-disabled="isSquashReadOnly" + class="gl-mr-5" /> - </gl-dropdown> - <merge-train-failed-pipeline-confirmation-dialog - :visible="isPipelineFailedModalVisibleMergeTrain" - @startMergeTrain="onStartMergeTrainConfirmation" - @cancel="isPipelineFailedModalVisibleMergeTrain = false" - /> - <merge-failed-pipeline-confirmation-dialog - :visible="isPipelineFailedModalVisibleNormalMerge" - @mergeWithFailedPipeline="onMergeWithFailedPipelineConfirmation" - @cancel="isPipelineFailedModalVisibleNormalMerge = false" - /> - </gl-button-group> - <merge-train-helper-icon v-if="shouldRenderMergeTrainHelperIcon" class="gl-mx-3" /> - <div - v-if="shouldShowMergeControls" - class="gl-display-flex gl-align-items-center gl-flex-wrap gl-w-full gl-order-n1 gl-mb-5" - > - <gl-form-checkbox - v-if="canRemoveSourceBranch" - id="remove-source-branch-input" - v-model="removeSourceBranch" - :disabled="isRemoveSourceBranchButtonDisabled" - class="js-remove-source-branch-checkbox gl-display-flex gl-align-items-center gl-mr-5" - > - {{ __('Delete source branch') }} - </gl-form-checkbox> - - <!-- Placeholder for EE extension of this component --> - <squash-before-merge - v-if="shouldShowSquashBeforeMerge" - v-model="squashBeforeMerge" - :help-path="mr.squashBeforeMergeHelpPath" - :is-disabled="isSquashReadOnly" - class="gl-mr-5" - /> - - <gl-form-checkbox - v-if="shouldShowSquashEdit || shouldShowMergeEdit" - v-model="editCommitMessage" - data-testid="widget_edit_commit_message" - class="gl-display-flex gl-align-items-center" - > - {{ __('Edit commit message') }} - </gl-form-checkbox> - </div> - <div - v-if="editCommitMessage" - class="gl-w-full gl-order-n1" - data-testid="edit_commit_message" - > - <ul class="border-top commits-list flex-list gl-list-style-none gl-p-0 gl-pt-4"> - <commit-edit - v-if="shouldShowSquashEdit" - :value="squashCommitMessage" - :label="__('Squash commit message')" - input-id="squash-message-edit" - class="gl-m-0! gl-p-0!" - @input="setSquashCommitMessage" + + <gl-form-checkbox + v-if="shouldShowSquashEdit || shouldShowMergeEdit" + v-model="editCommitMessage" + data-testid="widget_edit_commit_message" + class="gl-display-flex gl-align-items-center" > - <template #header> - <commit-message-dropdown :commits="commits" @input="setSquashCommitMessage" /> + {{ __('Edit commit message') }} + </gl-form-checkbox> + </div> + <div class="gl-w-full gl-text-gray-500 gl-mb-5"> + <added-commit-message + :is-squash-enabled="squashBeforeMerge" + :is-fast-forward-enabled="!shouldShowMergeEdit" + :commits-count="commitsCount" + :target-branch="stateData.targetBranch" + /> + <template v-if="mr.relatedLinks"> + · + <related-links + :state="mr.state" + :related-links="mr.relatedLinks" + :show-assign-to-me="false" + :diverged-commits-count="mr.divergedCommitsCount" + :target-branch-path="mr.targetBranchPath" + class="mr-ready-merge-related-links gl-display-inline" + /> + </template> + </div> + <div v-if="editCommitMessage" class="gl-w-full" data-testid="edit_commit_message"> + <ul class="border-top commits-list flex-list gl-list-style-none gl-p-0 gl-pt-4"> + <commit-edit + v-if="shouldShowSquashEdit" + :value="squashCommitMessage" + :label="__('Squash commit message')" + input-id="squash-message-edit" + class="gl-m-0! gl-p-0!" + @input="setSquashCommitMessage" + > + <template #header> + <commit-message-dropdown :commits="commits" @input="setSquashCommitMessage" /> + </template> + </commit-edit> + <commit-edit + v-if="shouldShowMergeEdit" + :value="commitMessage" + :label="__('Merge commit message')" + input-id="merge-message-edit" + class="gl-m-0! gl-p-0!" + @input="setCommitMessage" + /> + <li class="gl-m-0! gl-p-0!"> + <p class="form-text text-muted"> + <gl-sprintf :message="commitTemplateHintText"> + <template #link="{ content }"> + <gl-link + :href="commitTemplateHelpPage" + class="inline-link" + target="_blank" + > + {{ content }} + </gl-link> + </template> + </gl-sprintf> + </p> + </li> + </ul> + </div> + <gl-button-group class="gl-align-self-start"> + <gl-button + size="medium" + category="primary" + class="accept-merge-request" + data-testid="merge-button" + variant="confirm" + :disabled="isMergeButtonDisabled" + :loading="isMakingRequest" + data-qa-selector="merge_button" + @click="handleMergeButtonClick(isAutoMergeAvailable)" + >{{ mergeButtonText }}</gl-button + > + <gl-dropdown + v-if="shouldShowMergeImmediatelyDropdown" + v-gl-tooltip.hover.focus="__('Select merge moment')" + :disabled="isMergeButtonDisabled" + variant="confirm" + data-qa-selector="merge_moment_dropdown" + toggle-class="btn-icon js-merge-moment" + > + <template #button-content> + <gl-icon name="chevron-down" class="mr-0" /> + <span class="sr-only">{{ __('Select merge moment') }}</span> </template> - </commit-edit> - <commit-edit - v-if="shouldShowMergeEdit" - :value="commitMessage" - :label="__('Merge commit message')" - input-id="merge-message-edit" - class="gl-m-0! gl-p-0!" - @input="setCommitMessage" + <gl-dropdown-item + icon-name="warning" + button-class="accept-merge-request js-merge-immediately-button" + data-qa-selector="merge_immediately_menu_item" + @click="handleMergeImmediatelyButtonClick" + > + {{ __('Merge immediately') }} + </gl-dropdown-item> + <merge-immediately-confirmation-dialog + ref="confirmationDialog" + :docs-url="mr.mergeImmediatelyDocsPath" + @mergeImmediately="onMergeImmediatelyConfirmation" + /> + </gl-dropdown> + <merge-train-failed-pipeline-confirmation-dialog + :visible="isPipelineFailedModalVisibleMergeTrain" + @startMergeTrain="onStartMergeTrainConfirmation" + @cancel="isPipelineFailedModalVisibleMergeTrain = false" /> - <li class="gl-m-0! gl-p-0!"> - <p class="form-text text-muted"> - <gl-sprintf :message="commitTemplateHintText"> - <template #link="{ content }"> - <gl-link :href="commitTemplateHelpPage" class="inline-link" target="_blank"> - {{ content }} - </gl-link> - </template> - </gl-sprintf> - </p> - </li> - </ul> - </div> + <merge-failed-pipeline-confirmation-dialog + :visible="isPipelineFailedModalVisibleNormalMerge" + @mergeWithFailedPipeline="onMergeWithFailedPipelineConfirmation" + @cancel="isPipelineFailedModalVisibleNormalMerge = false" + /> + </gl-button-group> + <merge-train-helper-icon v-if="shouldRenderMergeTrainHelperIcon" class="gl-mx-3" /> + </template> <div - v-if="!shouldShowMergeControls" + v-else class="gl-w-full gl-order-n1 mr-widget-merge-details" data-qa-selector="merged_status_content" > @@ -669,12 +687,10 @@ export default { </p> <ul class="gl-pl-4 gl-mb-0 gl-ml-3 gl-text-gray-600"> <li v-if="sourceHasDivergedFromTarget" class="gl-line-height-normal"> - <gl-sprintf - :message="s__('mrWidget|The source branch is %{link} the target branch')" - > + <gl-sprintf :message="$options.i18n.sourceDivergedFromTargetText"> <template #link> <gl-link :href="mr.targetBranchPath">{{ - n__('%d commit behind', '%d commits behind', mr.divergedCommitsCount) + $options.i18n.divergedCommits(mr.divergedCommitsCount) }}</gl-link> </template> </gl-sprintf> @@ -703,37 +719,6 @@ export default { </li> </ul> </div> - <div - v-else - :class="{ 'gl-mb-5': shouldShowMergeControls }" - class="gl-w-full gl-order-n1 gl-text-gray-500" - > - <p v-if="sourceHasDivergedFromTarget" class="gl-display-inline gl-m-0"> - <gl-sprintf :message="$options.i18n.sourceDivergedFromTargetText"> - <template #link> - <gl-link :href="mr.targetBranchPath">{{ - $options.i18n.divergedCommits(mr.divergedCommitsCount) - }}</gl-link> - </template> - </gl-sprintf> - </p> - <template v-if="sourceHasDivergedFromTarget"> · </template> - <added-commit-message - :is-squash-enabled="squashBeforeMerge" - :is-fast-forward-enabled="!shouldShowMergeEdit" - :commits-count="commitsCount" - :target-branch="stateData.targetBranch" - /> - <template v-if="mr.relatedLinks"> - · - <related-links - :state="mr.state" - :related-links="mr.relatedLinks" - :show-assign-to-me="false" - class="mr-ready-merge-related-links gl-display-inline" - /> - </template> - </div> </div> </div> </div> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/terraform/mr_widget_terraform_container.vue b/app/assets/javascripts/vue_merge_request_widget/components/terraform/mr_widget_terraform_container.vue deleted file mode 100644 index 18fdb29ba54..00000000000 --- a/app/assets/javascripts/vue_merge_request_widget/components/terraform/mr_widget_terraform_container.vue +++ /dev/null @@ -1,137 +0,0 @@ -<script> -import { GlSkeletonLoader, GlSprintf } from '@gitlab/ui'; -import axios from '~/lib/utils/axios_utils'; -import Poll from '~/lib/utils/poll'; -import { n__ } from '~/locale'; -import MrWidgetExpanableSection from '../mr_widget_expandable_section.vue'; -import TerraformPlan from './terraform_plan.vue'; - -export default { - name: 'MRWidgetTerraformContainer', - components: { - GlSkeletonLoader, - GlSprintf, - MrWidgetExpanableSection, - TerraformPlan, - }, - props: { - endpoint: { - type: String, - required: true, - }, - }, - data() { - return { - loading: true, - plansObject: {}, - poll: null, - }; - }, - computed: { - inValidPlanCountText() { - if (this.numberOfInvalidPlans === 0) { - return null; - } - - return n__( - 'Terraform|%{number} Terraform report failed to generate', - 'Terraform|%{number} Terraform reports failed to generate', - this.numberOfInvalidPlans, - ); - }, - numberOfInvalidPlans() { - return Object.values(this.plansObject).filter((plan) => plan.tf_report_error).length; - }, - numberOfPlans() { - return Object.keys(this.plansObject).length; - }, - numberOfValidPlans() { - return this.numberOfPlans - this.numberOfInvalidPlans; - }, - validPlanCountText() { - if (this.numberOfValidPlans === 0) { - return null; - } - - return n__( - 'Terraform|%{number} Terraform report was generated in your pipelines', - 'Terraform|%{number} Terraform reports were generated in your pipelines', - this.numberOfValidPlans, - ); - }, - }, - created() { - this.fetchPlans(); - }, - beforeDestroy() { - this.poll.stop(); - }, - methods: { - fetchPlans() { - this.loading = true; - - this.poll = new Poll({ - resource: { - fetchPlans: () => axios.get(this.endpoint), - }, - data: this.endpoint, - method: 'fetchPlans', - successCallback: ({ data }) => { - this.plansObject = data; - - if (this.numberOfPlans > 0) { - this.loading = false; - this.poll.stop(); - } - }, - errorCallback: () => { - this.plansObject = { bad_plan: { tf_report_error: 'api_error' } }; - this.loading = false; - this.poll.stop(); - }, - }); - - this.poll.makeRequest(); - }, - }, -}; -</script> - -<template> - <section class="mr-widget-section"> - <div v-if="loading" class="mr-widget-body"> - <gl-skeleton-loader /> - </div> - - <mr-widget-expanable-section v-else> - <template #header> - <div - data-testid="terraform-header-text" - class="gl-flex-grow-1 gl-display-flex gl-flex-direction-column" - > - <p v-if="validPlanCountText" class="gl-m-0"> - <gl-sprintf :message="validPlanCountText"> - <template #number> - <strong>{{ numberOfValidPlans }}</strong> - </template> - </gl-sprintf> - </p> - - <p v-if="inValidPlanCountText" class="gl-m-0"> - <gl-sprintf :message="inValidPlanCountText"> - <template #number> - <strong>{{ numberOfInvalidPlans }}</strong> - </template> - </gl-sprintf> - </p> - </div> - </template> - - <template #content> - <div class="mr-widget-body gl-pb-1"> - <terraform-plan v-for="(plan, key) in plansObject" :key="key" :plan="plan" /> - </div> - </template> - </mr-widget-expanable-section> - </section> -</template> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/terraform/terraform_plan.vue b/app/assets/javascripts/vue_merge_request_widget/components/terraform/terraform_plan.vue deleted file mode 100644 index 1e5f7361966..00000000000 --- a/app/assets/javascripts/vue_merge_request_widget/components/terraform/terraform_plan.vue +++ /dev/null @@ -1,119 +0,0 @@ -<script> -import { GlIcon, GlLink, GlSprintf } from '@gitlab/ui'; -import { s__ } from '~/locale'; - -export default { - name: 'TerraformPlan', - components: { - GlIcon, - GlLink, - GlSprintf, - }, - props: { - plan: { - required: true, - type: Object, - }, - }, - i18n: { - changes: s__( - 'Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete', - ), - generationErrored: s__('Terraform|Generating the report caused an error.'), - namedReportFailed: s__('Terraform|The job %{name} failed to generate a report.'), - namedReportGenerated: s__('Terraform|The job %{name} generated a report.'), - reportFailed: s__('Terraform|A report failed to generate.'), - reportGenerated: s__('Terraform|A report was generated in your pipelines.'), - }, - computed: { - addNum() { - return Number(this.plan.create); - }, - changeNum() { - return Number(this.plan.update); - }, - deleteNum() { - return Number(this.plan.delete); - }, - iconType() { - return this.validPlanValues ? 'doc-changes' : 'warning'; - }, - reportChangeText() { - if (this.validPlanValues) { - return this.$options.i18n.changes; - } - - return this.$options.i18n.generationErrored; - }, - reportHeaderText() { - if (this.validPlanValues) { - return this.plan.job_name - ? this.$options.i18n.namedReportGenerated - : this.$options.i18n.reportGenerated; - } - - return this.plan.job_name - ? this.$options.i18n.namedReportFailed - : this.$options.i18n.reportFailed; - }, - validPlanValues() { - return this.addNum + this.changeNum + this.deleteNum >= 0; - }, - }, -}; -</script> - -<template> - <div class="gl-display-flex gl-pb-3"> - <span - class="gl-display-flex gl-align-items-center gl-justify-content-center gl-align-self-start gl-px-2" - > - <gl-icon :name="iconType" :size="16" data-testid="change-type-icon" /> - </span> - - <div class="gl-display-flex gl-flex-grow-1 gl-flex-direction-column flex-md-row gl-pl-3"> - <div class="gl-flex-grow-1 gl-display-flex gl-flex-direction-column gl-pr-3"> - <p class="gl-mb-3 gl-line-height-normal"> - <gl-sprintf :message="reportHeaderText"> - <template #name> - <strong>{{ plan.job_name }}</strong> - </template> - </gl-sprintf> - </p> - - <p class="gl-mb-3 gl-line-height-normal"> - <gl-sprintf :message="reportChangeText"> - <template #addNum> - <strong>{{ addNum }}</strong> - </template> - - <template #changeNum> - <strong>{{ changeNum }}</strong> - </template> - - <template #deleteNum> - <strong>{{ deleteNum }}</strong> - </template> - </gl-sprintf> - </p> - </div> - - <div> - <gl-link - v-if="plan.job_path" - :href="plan.job_path" - target="_blank" - data-testid="terraform-report-link" - data-track-action="click_terraform_mr_plan_button" - data-track-label="mr_widget_terraform_mr_plan_button" - data-track-property="terraform_mr_plan_button" - class="btn btn-sm" - rel="noopener" - > - {{ __('View full log') }} - <gl-icon name="external-link" /> - </gl-link> - </div> - </div> - </div> -</template> diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue index 4ff2643057f..2d9549fdada 100644 --- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue +++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue @@ -86,9 +86,6 @@ export default { import('../reports/codequality_report/grouped_codequality_reports_app.vue'), GroupedTestReportsApp: () => import('../reports/grouped_test_report/grouped_test_reports_app.vue'), - TerraformPlan: () => import('./components/terraform/mr_widget_terraform_container.vue'), - GroupedAccessibilityReportsApp: () => - import('../reports/accessibility_report/grouped_accessibility_reports_app.vue'), MrWidgetApprovals, SecurityReportsApp: () => import('~/vue_shared/security_reports/security_reports_app.vue'), MergeChecksFailed: () => import('./components/states/merge_checks_failed.vue'), @@ -218,12 +215,6 @@ export default { hasAlerts() { return this.hasMergeError || this.showMergePipelineForkWarning; }, - shouldShowExtension() { - return ( - window.gon?.features?.refactorMrWidgetsExtensions || - window.gon?.features?.refactorMrWidgetsExtensionsUser - ); - }, shouldShowSecurityExtension() { return window.gon?.features?.refactorSecurityExtension; }, @@ -518,12 +509,12 @@ export default { this.mr.isDismissedSuggestPipeline = true; }, registerTerraformPlans() { - if (this.shouldRenderTerraformPlans && this.shouldShowExtension) { + if (this.shouldRenderTerraformPlans) { registerExtension(terraformExtension); } }, registerAccessibilityExtension() { - if (this.shouldShowAccessibilityReport && this.shouldShowExtension) { + if (this.shouldShowAccessibilityReport) { registerExtension(accessibilityExtension); } }, @@ -627,16 +618,6 @@ export default { :pipeline-path="mr.pipeline.path" /> - <terraform-plan - v-if="mr.terraformReportsPath && !shouldShowExtension" - :endpoint="mr.terraformReportsPath" - /> - - <grouped-accessibility-reports-app - v-if="shouldShowAccessibilityReport && !shouldShowExtension" - :endpoint="mr.accessibilityReportPath" - /> - <div class="mr-widget-section" data-qa-selector="mr_widget_content"> <component :is="componentName" :mr="mr" :service="service" /> <ready-to-merge diff --git a/app/assets/javascripts/vue_shared/components/url_sync.vue b/app/assets/javascripts/vue_shared/components/url_sync.vue index 925c6008836..bd5b7b77017 100644 --- a/app/assets/javascripts/vue_shared/components/url_sync.vue +++ b/app/assets/javascripts/vue_shared/components/url_sync.vue @@ -1,6 +1,9 @@ <script> import { historyPushState } from '~/lib/utils/common_utils'; -import { mergeUrlParams } from '~/lib/utils/url_utility'; +import { mergeUrlParams, setUrlParams } from '~/lib/utils/url_utility'; + +export const URL_SET_PARAMS_STRATEGY = 'set'; +export const URL_MERGE_PARAMS_STRATEGY = 'merge'; /** * Renderless component to update the query string, @@ -15,6 +18,12 @@ export default { required: false, default: null, }, + urlParamsUpdateStrategy: { + type: String, + required: false, + default: URL_MERGE_PARAMS_STRATEGY, + validator: (value) => [URL_MERGE_PARAMS_STRATEGY, URL_SET_PARAMS_STRATEGY].includes(value), + }, }, watch: { query: { @@ -29,7 +38,11 @@ export default { }, methods: { updateQuery(newQuery) { - historyPushState(mergeUrlParams(newQuery, window.location.href, { spreadArrays: true })); + const url = + this.urlParamsUpdateStrategy === URL_SET_PARAMS_STRATEGY + ? setUrlParams(this.query, window.location.href, true) + : mergeUrlParams(newQuery, window.location.href, { spreadArrays: true }); + historyPushState(url); }, }, render() { diff --git a/app/assets/stylesheets/page_bundles/merge_requests.scss b/app/assets/stylesheets/page_bundles/merge_requests.scss index 050cc2a9fa8..b2fbce7cb4b 100644 --- a/app/assets/stylesheets/page_bundles/merge_requests.scss +++ b/app/assets/stylesheets/page_bundles/merge_requests.scss @@ -720,10 +720,6 @@ $tabs-holder-z-index: 250; } @include media-breakpoint-down(xs) { - p { - font-size: 13px; - } - .btn-grouped { float: none; margin-right: 0; diff --git a/app/controllers/projects/incidents_controller.rb b/app/controllers/projects/incidents_controller.rb index 57892a45ff2..089ee860ea6 100644 --- a/app/controllers/projects/incidents_controller.rb +++ b/app/controllers/projects/incidents_controller.rb @@ -7,7 +7,6 @@ class Projects::IncidentsController < Projects::ApplicationController before_action :authorize_read_issue! before_action :load_incident, only: [:show] before_action do - push_frontend_feature_flag(:incident_timeline, @project) push_force_frontend_feature_flag(:work_items, @project&.work_items_feature_flag_enabled?) push_force_frontend_feature_flag(:work_items_mvc_2, @project&.work_items_mvc_2_feature_flag_enabled?) push_frontend_feature_flag(:work_items_hierarchy, @project) diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 65c23d52271..5b1117c0224 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -41,7 +41,6 @@ class Projects::IssuesController < Projects::ApplicationController before_action :authorize_download_code!, only: [:related_branches] before_action do - push_frontend_feature_flag(:incident_timeline, project) push_frontend_feature_flag(:preserve_unchanged_markdown, project) push_frontend_feature_flag(:content_editor_on_issues, project) end diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 3e226131d75..d7319b0726f 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -34,7 +34,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo before_action only: [:show] do push_frontend_feature_flag(:merge_request_widget_graphql, project) push_frontend_feature_flag(:core_security_mr_widget_counts, project) - push_frontend_feature_flag(:refactor_mr_widgets_extensions, project) push_frontend_feature_flag(:refactor_code_quality_extension, project) push_frontend_feature_flag(:refactor_mr_widget_test_summary, project) push_frontend_feature_flag(:issue_assignees_widget, @project) diff --git a/app/services/bulk_imports/create_pipeline_trackers_service.rb b/app/services/bulk_imports/create_pipeline_trackers_service.rb index af97aec09b5..ca297ef3774 100644 --- a/app/services/bulk_imports/create_pipeline_trackers_service.rb +++ b/app/services/bulk_imports/create_pipeline_trackers_service.rb @@ -53,7 +53,7 @@ module BulkImports def log_skipped_pipeline(pipeline, minimum_version, maximum_version) logger.info( message: 'Pipeline skipped as source instance version not compatible with pipeline', - entity_id: entity.id, + bulk_import_entity_id: entity.id, pipeline_name: pipeline[:pipeline], minimum_source_version: minimum_version, maximum_source_version: maximum_version, diff --git a/app/services/incident_management/timeline_events/create_service.rb b/app/services/incident_management/timeline_events/create_service.rb index 40ce9097c88..5422b4ad6d2 100644 --- a/app/services/incident_management/timeline_events/create_service.rb +++ b/app/services/incident_management/timeline_events/create_service.rb @@ -109,7 +109,6 @@ module IncidentManagement def add_system_note(timeline_event) return if auto_created - return unless Feature.enabled?(:incident_timeline, project) SystemNoteService.add_timeline_event(timeline_event) end diff --git a/app/services/incident_management/timeline_events/destroy_service.rb b/app/services/incident_management/timeline_events/destroy_service.rb index 90e95ae8869..e1c6bbbdb85 100644 --- a/app/services/incident_management/timeline_events/destroy_service.rb +++ b/app/services/incident_management/timeline_events/destroy_service.rb @@ -30,8 +30,6 @@ module IncidentManagement attr_reader :project, :timeline_event, :user, :incident def add_system_note(incident, user) - return unless Feature.enabled?(:incident_timeline, project) - SystemNoteService.delete_timeline_event(incident, user) end end diff --git a/app/services/incident_management/timeline_events/update_service.rb b/app/services/incident_management/timeline_events/update_service.rb index 5c5de4717bc..012e2f0e260 100644 --- a/app/services/incident_management/timeline_events/update_service.rb +++ b/app/services/incident_management/timeline_events/update_service.rb @@ -38,8 +38,6 @@ module IncidentManagement end def add_system_note(timeline_event) - return unless Feature.enabled?(:incident_timeline, incident.project) - changes = was_changed(timeline_event) return if changes == :none diff --git a/app/views/admin/application_settings/_package_registry.html.haml b/app/views/admin/application_settings/_package_registry.html.haml index 4bdfa5bfe83..3506038ca68 100644 --- a/app/views/admin/application_settings/_package_registry.html.haml +++ b/app/views/admin/application_settings/_package_registry.html.haml @@ -20,12 +20,12 @@ %ul.nav-links.scrolling-tabs.mobile-separator.nav.nav-tabs.mb-3 - @plans.each_with_index do |plan, index| %li - = link_to admin_plan_limits_path(anchor: 'js-package-settings'), data: { target: "div#plan#{index}", action: "plan#{index}", toggle: 'tab'}, class: index == 0 ? 'active': '' do + = link_to admin_plan_limits_path(anchor: 'js-package-settings'), data: { target: "div#plan-package#{index}", action: "plan#{index}", toggle: 'tab'}, class: index == 0 ? 'active': '' do = plan.name.capitalize .tab-content - @plans.each_with_index do |plan, index| - .tab-pane{ :id => "plan#{index}", class: index == 0 ? 'active': '' } - = form_for plan.actual_limits, url: admin_plan_limits_path(anchor: 'js-package-settings'), html: { class: 'fieldset-form' }, method: :post do |f| + .tab-pane{ :id => "plan-package#{index}", class: index == 0 ? 'active': '' } + = gitlab_ui_form_for plan.actual_limits, url: admin_plan_limits_path(anchor: 'js-package-settings'), html: { class: 'fieldset-form' }, method: :post do |f| = form_errors(plan) %fieldset = f.hidden_field(:plan_id, value: plan.id) @@ -53,4 +53,4 @@ .form-group = f.label :generic_packages_max_file_size, _('Generic package file size in bytes'), class: 'label-bold' = f.number_field :generic_packages_max_file_size, class: 'form-control gl-form-input' - = f.submit _('Save %{name} size limits').html_safe % { name: plan.name.capitalize }, class: 'btn gl-button btn-confirm' + = f.submit _('Save %{name} size limits').html_safe % { name: plan.name.capitalize }, pajamas_button: true diff --git a/app/views/admin/application_settings/_terms.html.haml b/app/views/admin/application_settings/_terms.html.haml index a4b6e061c43..8da441d5245 100644 --- a/app/views/admin/application_settings/_terms.html.haml +++ b/app/views/admin/application_settings/_terms.html.haml @@ -11,4 +11,4 @@ .form-text.text-muted = _("Markdown supported.") = link_to _('What is Markdown?'), help_page_path('user/markdown.md'), target: '_blank', rel: 'noopener noreferrer' - = f.submit _("Save changes"), class: "gl-button btn btn-confirm" + = f.submit _("Save changes"), pajamas_button: true diff --git a/app/views/admin/deploy_keys/edit.html.haml b/app/views/admin/deploy_keys/edit.html.haml index 12a1c0c3de2..acdf503727d 100644 --- a/app/views/admin/deploy_keys/edit.html.haml +++ b/app/views/admin/deploy_keys/edit.html.haml @@ -3,8 +3,8 @@ %hr %div - = form_for [:admin, @deploy_key], html: { class: 'deploy-key-form' } do |f| + = gitlab_ui_form_for [:admin, @deploy_key], html: { class: 'deploy-key-form' } do |f| = render partial: 'shared/deploy_keys/form', locals: { form: f, deploy_key: @deploy_key } .form-actions - = f.submit _('Save changes'), class: 'btn gl-button btn-confirm' + = f.submit _('Save changes'), pajamas_button: true = link_to _('Cancel'), admin_deploy_keys_path, class: 'btn gl-button btn-default btn-cancel' diff --git a/app/views/admin/groups/_form.html.haml b/app/views/admin/groups/_form.html.haml index 69e9e4260b4..7adba0d023b 100644 --- a/app/views/admin/groups/_form.html.haml +++ b/app/views/admin/groups/_form.html.haml @@ -35,10 +35,10 @@ = c.body do = render 'shared/group_tips' .gl-mt-5 - = f.submit _('Create group'), class: "gl-button btn btn-confirm" + = f.submit _('Create group'), pajamas_button: true = link_to _('Cancel'), admin_groups_path, class: "gl-button btn btn-default btn-cancel" - else .gl-mt-5 - = f.submit _('Save changes'), class: "gl-button btn btn-confirm", data: { qa_selector: 'save_changes_button' } + = f.submit _('Save changes'), data: { qa_selector: 'save_changes_button' }, pajamas_button: true = link_to _('Cancel'), admin_group_path(@group), class: "gl-button btn btn-cancel" diff --git a/app/views/projects/hooks/index.html.haml b/app/views/projects/hooks/index.html.haml index 393f627aa99..0476193c2cb 100644 --- a/app/views/projects/hooks/index.html.haml +++ b/app/views/projects/hooks/index.html.haml @@ -9,6 +9,6 @@ .col-lg-8.gl-mb-3 = gitlab_ui_form_for @hook, as: :hook, url: polymorphic_path([@project, :hooks]) do |f| = render partial: 'shared/web_hooks/form', locals: { form: f, hook: @hook } - = f.submit 'Add webhook', class: 'gl-button btn btn-confirm' + = f.submit 'Add webhook', pajamas_button: true = render 'shared/web_hooks/index', hooks: @hooks, hook_class: @hook.class diff --git a/app/workers/bulk_imports/entity_worker.rb b/app/workers/bulk_imports/entity_worker.rb index f6b1c693fe4..5f94b58f4c6 100644 --- a/app/workers/bulk_imports/entity_worker.rb +++ b/app/workers/bulk_imports/entity_worker.rb @@ -15,7 +15,7 @@ module BulkImports if stage_running?(entity_id, current_stage) logger.info( structured_payload( - entity_id: entity_id, + bulk_import_entity_id: entity_id, current_stage: current_stage, message: 'Stage running' ) @@ -26,7 +26,7 @@ module BulkImports logger.info( structured_payload( - entity_id: entity_id, + bulk_import_entity_id: entity_id, current_stage: current_stage, message: 'Stage starting' ) @@ -42,13 +42,13 @@ module BulkImports rescue StandardError => e logger.error( structured_payload( - entity_id: entity_id, + bulk_import_entity_id: entity_id, current_stage: current_stage, message: e.message ) ) - Gitlab::ErrorTracking.track_exception(e, entity_id: entity_id) + Gitlab::ErrorTracking.track_exception(e, bulk_import_entity_id: entity_id) end private diff --git a/app/workers/bulk_imports/pipeline_worker.rb b/app/workers/bulk_imports/pipeline_worker.rb index 435e1095667..6e2387b3fb5 100644 --- a/app/workers/bulk_imports/pipeline_worker.rb +++ b/app/workers/bulk_imports/pipeline_worker.rb @@ -19,7 +19,7 @@ module BulkImports if pipeline_tracker.present? logger.info( structured_payload( - entity_id: pipeline_tracker.entity.id, + bulk_import_entity_id: pipeline_tracker.entity.id, pipeline_name: pipeline_tracker.pipeline_name ) ) @@ -28,7 +28,7 @@ module BulkImports else logger.error( structured_payload( - entity_id: entity_id, + bulk_import_entity_id: entity_id, pipeline_tracker_id: pipeline_tracker_id, message: 'Unstarted pipeline not found' ) @@ -65,7 +65,7 @@ module BulkImports logger.error( structured_payload( - entity_id: pipeline_tracker.entity.id, + bulk_import_entity_id: pipeline_tracker.entity.id, pipeline_name: pipeline_tracker.pipeline_name, message: exception.message ) @@ -73,7 +73,7 @@ module BulkImports Gitlab::ErrorTracking.track_exception( exception, - entity_id: pipeline_tracker.entity.id, + bulk_import_entity_id: pipeline_tracker.entity.id, pipeline_name: pipeline_tracker.pipeline_name ) @@ -139,7 +139,7 @@ module BulkImports def retry_tracker(exception) logger.error( structured_payload( - entity_id: pipeline_tracker.entity.id, + bulk_import_entity_id: pipeline_tracker.entity.id, pipeline_name: pipeline_tracker.pipeline_name, message: "Retrying error: #{exception.message}" ) @@ -153,7 +153,7 @@ module BulkImports def skip_tracker logger.info( structured_payload( - entity_id: pipeline_tracker.entity.id, + bulk_import_entity_id: pipeline_tracker.entity.id, pipeline_name: pipeline_tracker.pipeline_name, message: 'Skipping pipeline due to failed entity' ) diff --git a/config/feature_flags/development/incident_timeline.yml b/config/feature_flags/development/incident_timeline.yml deleted file mode 100644 index 587ef8b55e8..00000000000 --- a/config/feature_flags/development/incident_timeline.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: incident_timeline -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/80802 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/353426 -milestone: '14.9' -type: development -group: group::respond -default_enabled: true diff --git a/config/feature_flags/development/refactor_mr_widgets_extensions.yml b/config/feature_flags/development/refactor_mr_widgets_extensions.yml deleted file mode 100644 index 3f71e786f99..00000000000 --- a/config/feature_flags/development/refactor_mr_widgets_extensions.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: refactor_mr_widgets_extensions -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/70993 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/341759 -milestone: '14.4' -type: development -group: group::code review -default_enabled: true diff --git a/config/feature_flags/development/refactor_mr_widgets_extensions_user.yml b/config/feature_flags/development/refactor_mr_widgets_extensions_user.yml deleted file mode 100644 index aa3c2799100..00000000000 --- a/config/feature_flags/development/refactor_mr_widgets_extensions_user.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: refactor_mr_widgets_extensions_user -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/70993 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/341759 -milestone: '14.4' -type: development -group: group::code review -default_enabled: false diff --git a/doc/administration/job_artifacts.md b/doc/administration/job_artifacts.md index 0294e469bad..1e9f20ded55 100644 --- a/doc/administration/job_artifacts.md +++ b/doc/administration/job_artifacts.md @@ -233,8 +233,6 @@ by the `gitlab:artifacts:migrate` Rake task. To migrate back to local storage: -1. Set both `direct_upload` and `background_upload` to `false` in `gitlab.rb`, under the artifacts object storage settings. -1. [Reconfigure GitLab](restart_gitlab.md#omnibus-gitlab-reconfigure). 1. Run `gitlab-rake gitlab:artifacts:migrate_to_local`. 1. Disable object_storage for artifacts in `gitlab.rb`: - Set `gitlab_rails['artifacts_object_store_enabled'] = false`. diff --git a/doc/administration/lfs/index.md b/doc/administration/lfs/index.md index bb3f3a48f17..1b01c665ab8 100644 --- a/doc/administration/lfs/index.md +++ b/doc/administration/lfs/index.md @@ -220,7 +220,6 @@ end To migrate back to local storage: -1. Set both `direct_upload` and `background_upload` to `false` under the LFS object storage settings. Don't forget to restart GitLab. 1. Run `rake gitlab:lfs:migrate_to_local` on your console. 1. Disable `object_storage` for LFS objects in `gitlab.rb`. Remember to restart GitLab afterwards. diff --git a/doc/administration/raketasks/uploads/migrate.md b/doc/administration/raketasks/uploads/migrate.md index a44ee9b240e..d4ab1012c2a 100644 --- a/doc/administration/raketasks/uploads/migrate.md +++ b/doc/administration/raketasks/uploads/migrate.md @@ -171,15 +171,6 @@ keeping in mind the task name in this case is `gitlab:uploads:migrate_to_local`. To migrate uploads from object storage to local storage: -1. Disable both `direct_upload` and `background_upload` under `uploads` settings in `gitlab.rb`: - - ```ruby - gitlab_rails['uploads_object_store_direct_upload'] = false - gitlab_rails['uploads_object_store_background_upload'] = false - ``` - - Save the file and [reconfigure GitLab](../../restart_gitlab.md#omnibus-gitlab-reconfigure). - 1. Run the Rake task: **Omnibus Installation** diff --git a/doc/operations/incident_management/incident_timeline_events.md b/doc/operations/incident_management/incident_timeline_events.md index 750f7919b7c..5f98335d7aa 100644 --- a/doc/operations/incident_management/incident_timeline_events.md +++ b/doc/operations/incident_management/incident_timeline_events.md @@ -6,11 +6,9 @@ info: To determine the technical writer assigned to the Stage/Group associated w # Timeline events -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/344059) in GitLab 15.2 [with a flag](../../administration/feature_flags.md) named `incident_timeline`. Enabled by default. - -FLAG: -On self-managed GitLab, by default this feature is available. To hide the feature, ask an administrator to [disable the feature flag](../../administration/feature_flags.md) named `incident_timeline`. -On GitLab.com, this feature is available. +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/344059) in GitLab 15.2 [with a flag](../../administration/feature_flags.md) named `incident_timeline`. Enabled by default. +> - [Enabled on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/353426) in GitLab 15.3. +> - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/353426) in GitLab 15.5. [Feature flag 'incident_timeline'](https://gitlab.com/gitlab-org/gitlab/-/issues/343386) removed. Incident timelines are an important part of record keeping for incidents. Timelines can show executives and external viewers what happened during an incident, diff --git a/lib/tasks/gitlab/seed.rake b/lib/tasks/gitlab/seed.rake index 36761165af5..7b9c57b1876 100644 --- a/lib/tasks/gitlab/seed.rake +++ b/lib/tasks/gitlab/seed.rake @@ -35,5 +35,40 @@ namespace :gitlab do puts "\n#{issues_created} issues created!" end end + + task :epics, [:group_full_path, :backfill_weeks, :average_issues_per_week] => :environment do |t, args| + args.with_defaults(backfill_weeks: 5, average_issues_per_week: 2) + + groups = + if args.group_full_path + group = Group.find_by_full_path(args.group_full_path) + + unless group + error_message = "Group '#{args.group_full_path}' does not exist!" + potential_groups = Group.search(args.group_full_path) + + if potential_groups.present? + error_message += " Did you mean '#{potential_groups.first.full_path}'?" + end + + puts error_message.color(:red) + exit 1 + end + + [group] + else + Group.not_mass_generated.find_each + end + + groups.each do |group| + puts "\nSeeding epics for the '#{group.full_path}' group" + seeder = Quality::Seeders::Epics.new(group: group) + epics = seeder.seed( + backfill_weeks: args.backfill_weeks.to_i, + average_issues_per_week: args.average_issues_per_week.to_i + ) + puts "\n#{epics} epics created!" + end + end end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 54ef5345a5c..7f84d6012d4 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -2030,15 +2030,9 @@ msgstr "" msgid "AccessTokens|Your static object token authenticates you when repository static objects (such as archives or blobs) are served from an external storage." msgstr "" -msgid "AccessibilityReport|Learn more" -msgstr "" - msgid "AccessibilityReport|Message: %{message}" msgstr "" -msgid "AccessibilityReport|New" -msgstr "" - msgid "AccessibilityReport|The accessibility scanning found an error of the following type: %{code}" msgstr "" @@ -33726,11 +33720,6 @@ msgid_plural "Reports|%{recentlyFailed} out of %{failed} failed tests have faile msgstr[0] "" msgstr[1] "" -msgid "Reports|Accessibility scanning detected %d issue for the source branch only" -msgid_plural "Reports|Accessibility scanning detected %d issues for the source branch only" -msgstr[0] "" -msgstr[1] "" - msgid "Reports|Accessibility scanning detected %{strong_start}%{number}%{strong_end} issue for the source branch only" msgid_plural "Reports|Accessibility scanning detected %{strong_start}%{number}%{strong_end} issues for the source branch only" msgstr[0] "" @@ -38440,9 +38429,6 @@ msgstr "" msgid "Status: %{title}" msgstr "" -msgid "StatusCheck| %{failed} failed, and %{pending} pending" -msgstr "" - msgid "StatusCheck|%{failed} failed" msgstr "" @@ -38455,9 +38441,6 @@ msgstr "" msgid "StatusCheck|Add status check" msgstr "" -msgid "StatusCheck|All passed" -msgstr "" - msgid "StatusCheck|An error occurred deleting the %{name} status check." msgstr "" @@ -38479,9 +38462,6 @@ msgstr "" msgid "StatusCheck|Failed to load status checks" msgstr "" -msgid "StatusCheck|Failed to load status checks." -msgstr "" - msgid "StatusCheck|Invoke an external API as part of the pipeline process." msgstr "" @@ -39618,16 +39598,6 @@ msgstr "" msgid "Terraform|%{name} successfully removed" msgstr "" -msgid "Terraform|%{number} Terraform report failed to generate" -msgid_plural "Terraform|%{number} Terraform reports failed to generate" -msgstr[0] "" -msgstr[1] "" - -msgid "Terraform|%{number} Terraform report was generated in your pipelines" -msgid_plural "Terraform|%{number} Terraform reports were generated in your pipelines" -msgstr[0] "" -msgstr[1] "" - msgid "Terraform|%{strong_start}%{number}%{strong_end} Terraform report failed to generate" msgid_plural "Terraform|%{strong_start}%{number}%{strong_end} Terraform reports failed to generate" msgstr[0] "" @@ -39647,12 +39617,6 @@ msgstr "" msgid "Terraform|A Terraform report was generated in your pipelines." msgstr "" -msgid "Terraform|A report failed to generate." -msgstr "" - -msgid "Terraform|A report was generated in your pipelines." -msgstr "" - msgid "Terraform|Actions" msgstr "" @@ -39740,12 +39704,6 @@ msgstr "" msgid "Terraform|Terraform reports" msgstr "" -msgid "Terraform|The job %{name} failed to generate a report." -msgstr "" - -msgid "Terraform|The job %{name} generated a report." -msgstr "" - msgid "Terraform|The job %{strong_start}%{name}%{strong_end} failed to generate a report." msgstr "" @@ -44229,9 +44187,6 @@ msgstr "" msgid "View full dashboard" msgstr "" -msgid "View full log" -msgstr "" - msgid "View group in admin area" msgstr "" diff --git a/scripts/lib/glfm/update_specification.rb b/scripts/lib/glfm/update_specification.rb index 7a89797123b..51ac48a5842 100644 --- a/scripts/lib/glfm/update_specification.rb +++ b/scripts/lib/glfm/update_specification.rb @@ -43,7 +43,7 @@ module Glfm def update_ghfm_spec_md output("Downloading #{GHFM_SPEC_TXT_URI}...") - ghfm_spec_txt_uri_io = URI.open(GHFM_SPEC_TXT_URI) + ghfm_spec_txt_uri_io = URI.parse(GHFM_SPEC_TXT_URI).open # Read IO stream into an array of lines for easy processing later ghfm_spec_lines = ghfm_spec_txt_uri_io.readlines diff --git a/spec/features/incidents/incident_timeline_events_spec.rb b/spec/features/incidents/incident_timeline_events_spec.rb index 6db9f87d6f2..ef0eb27d310 100644 --- a/spec/features/incidents/incident_timeline_events_spec.rb +++ b/spec/features/incidents/incident_timeline_events_spec.rb @@ -12,7 +12,6 @@ RSpec.describe 'Incident timeline events', :js do end before do - stub_feature_flags(incident_timeline: true) sign_in(developer) visit project_issues_incident_path(project, incident) diff --git a/spec/features/issues/incident_issue_spec.rb b/spec/features/issues/incident_issue_spec.rb index 509ff7d4023..d6cde466d1b 100644 --- a/spec/features/issues/incident_issue_spec.rb +++ b/spec/features/issues/incident_issue_spec.rb @@ -26,7 +26,6 @@ RSpec.describe 'Incident Detail', :js do context 'when user displays the incident' do before do - stub_feature_flags(incident_timeline: project) project.add_developer(user) sign_in(user) @@ -97,20 +96,5 @@ RSpec.describe 'Incident Detail', :js do end end end - - context 'when incident_timeline feature flag is disabled' do - before do - stub_feature_flags(incident_timeline: false) - - visit project_issues_incident_path(project, incident) - wait_for_requests - end - - it 'does not show Timeline tab' do - tabs = find('[data-testid="incident-tabs"]') - - expect(tabs).not_to have_content('Timeline') - end - end end end diff --git a/spec/features/merge_request/user_sees_merge_widget_spec.rb b/spec/features/merge_request/user_sees_merge_widget_spec.rb index 5a0af17dc15..77ac6fac22f 100644 --- a/spec/features/merge_request/user_sees_merge_widget_spec.rb +++ b/spec/features/merge_request/user_sees_merge_widget_spec.rb @@ -22,8 +22,6 @@ RSpec.describe 'Merge request > User sees merge widget', :js do project_only_mwps.add_maintainer(user) sign_in(user) - stub_feature_flags(refactor_mr_widgets_extensions: false) - stub_feature_flags(refactor_mr_widgets_extensions_user: false) stub_feature_flags(refactor_mr_widget_test_summary: false) end diff --git a/spec/frontend/issues/show/components/incidents/incident_tabs_spec.js b/spec/frontend/issues/show/components/incidents/incident_tabs_spec.js index d92aeabba0f..458c1c3f858 100644 --- a/spec/frontend/issues/show/components/incidents/incident_tabs_spec.js +++ b/spec/frontend/issues/show/components/incidents/incident_tabs_spec.js @@ -5,7 +5,6 @@ import { trackIncidentDetailsViewsOptions } from '~/incidents/constants'; import DescriptionComponent from '~/issues/show/components/description.vue'; import HighlightBar from '~/issues/show/components/incidents/highlight_bar.vue'; import IncidentTabs from '~/issues/show/components/incidents/incident_tabs.vue'; -import TimelineTab from '~/issues/show/components/incidents/timeline_events_tab.vue'; import INVALID_URL from '~/lib/utils/invalid_url'; import Tracking from '~/tracking'; import AlertDetailsTable from '~/vue_shared/components/alert_details_table.vue'; @@ -38,7 +37,6 @@ describe('Incident Tabs component', () => { projectId: '', issuableId: '', uploadMetricsFeatureAvailable: true, - glFeatures: { incidentTimeline: true }, }, data() { return { alert: mockAlert, ...data }; @@ -67,7 +65,6 @@ describe('Incident Tabs component', () => { const findAlertDetailsComponent = () => wrapper.findComponent(AlertDetailsTable); const findDescriptionComponent = () => wrapper.findComponent(DescriptionComponent); const findHighlightBarComponent = () => wrapper.findComponent(HighlightBar); - const findTimelineTab = () => wrapper.findComponent(TimelineTab); describe('empty state', () => { beforeEach(() => { @@ -128,20 +125,4 @@ describe('Incident Tabs component', () => { expect(Tracking.event).toHaveBeenCalledWith(category, action); }); }); - - describe('incident timeline tab', () => { - beforeEach(() => { - mountComponent(); - }); - - it('renders the timeline tab when feature flag is enabled', () => { - expect(findTimelineTab().exists()).toBe(true); - }); - - it('does not render timeline tab when feature flag is disabled', () => { - mountComponent({}, { provide: { glFeatures: { incidentTimeline: false } } }); - - expect(findTimelineTab().exists()).toBe(false); - }); - }); }); diff --git a/spec/frontend/reports/accessibility_report/components/accessibility_issue_body_spec.js b/spec/frontend/reports/accessibility_report/components/accessibility_issue_body_spec.js deleted file mode 100644 index d835ca4c733..00000000000 --- a/spec/frontend/reports/accessibility_report/components/accessibility_issue_body_spec.js +++ /dev/null @@ -1,112 +0,0 @@ -import { GlBadge } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; -import AccessibilityIssueBody from '~/reports/accessibility_report/components/accessibility_issue_body.vue'; - -const issue = { - name: - 'The accessibility scanning found 2 errors of the following type: WCAG2AA.Principle4.Guideline4_1.4_1_2.H91.A.NoContent', - code: 'WCAG2AA.Principle4.Guideline4_1.4_1_2.H91.A.NoContent', - message: 'This element has insufficient contrast at this conformance level.', - status: 'failed', - className: 'spec.test_spec', - learnMoreUrl: 'https://www.w3.org/TR/WCAG20-TECHS/H91.html', -}; - -describe('CustomMetricsForm', () => { - let wrapper; - - const mountComponent = ({ name, code, message, status, className }, isNew = false) => { - wrapper = shallowMount(AccessibilityIssueBody, { - propsData: { - issue: { - name, - code, - message, - status, - className, - }, - isNew, - }, - }); - }; - - const findIsNewBadge = () => wrapper.findComponent(GlBadge); - - beforeEach(() => { - mountComponent(issue); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - it('Displays the issue message', () => { - const description = wrapper.findComponent({ ref: 'accessibility-issue-description' }).text(); - - expect(description).toContain(`Message: ${issue.message}`); - }); - - describe('When an issue code is present', () => { - it('Creates the correct URL for learning more about the issue code', () => { - const learnMoreUrl = wrapper - .findComponent({ ref: 'accessibility-issue-learn-more' }) - .attributes('href'); - - expect(learnMoreUrl).toBe(issue.learnMoreUrl); - }); - }); - - describe('When an issue code is not present', () => { - beforeEach(() => { - mountComponent({ - ...issue, - code: undefined, - }); - }); - - it('Creates a URL leading to the overview documentation page', () => { - const learnMoreUrl = wrapper - .findComponent({ ref: 'accessibility-issue-learn-more' }) - .attributes('href'); - - expect(learnMoreUrl).toBe('https://www.w3.org/TR/WCAG20-TECHS/Overview.html'); - }); - }); - - describe('When an issue code does not contain the TECHS code', () => { - beforeEach(() => { - mountComponent({ - ...issue, - code: 'WCAG2AA.Principle4.Guideline4_1.4_1_2', - }); - }); - - it('Creates a URL leading to the overview documentation page', () => { - const learnMoreUrl = wrapper - .findComponent({ ref: 'accessibility-issue-learn-more' }) - .attributes('href'); - - expect(learnMoreUrl).toBe('https://www.w3.org/TR/WCAG20-TECHS/Overview.html'); - }); - }); - - describe('When issue is new', () => { - beforeEach(() => { - mountComponent(issue, true); - }); - - it('Renders the new badge', () => { - expect(findIsNewBadge().exists()).toBe(true); - }); - }); - - describe('When issue is not new', () => { - beforeEach(() => { - mountComponent(issue, false); - }); - - it('Does not render the new badge', () => { - expect(findIsNewBadge().exists()).toBe(false); - }); - }); -}); diff --git a/spec/frontend/reports/accessibility_report/grouped_accessibility_reports_app_spec.js b/spec/frontend/reports/accessibility_report/grouped_accessibility_reports_app_spec.js deleted file mode 100644 index 9d3535291eb..00000000000 --- a/spec/frontend/reports/accessibility_report/grouped_accessibility_reports_app_spec.js +++ /dev/null @@ -1,125 +0,0 @@ -import { mount } from '@vue/test-utils'; -import Vue from 'vue'; -import Vuex from 'vuex'; -import AccessibilityIssueBody from '~/reports/accessibility_report/components/accessibility_issue_body.vue'; -import GroupedAccessibilityReportsApp from '~/reports/accessibility_report/grouped_accessibility_reports_app.vue'; -import { getStoreConfig } from '~/reports/accessibility_report/store'; -import { mockReport } from './mock_data'; - -Vue.use(Vuex); - -describe('Grouped accessibility reports app', () => { - let wrapper; - let mockStore; - - const mountComponent = () => { - wrapper = mount(GroupedAccessibilityReportsApp, { - store: mockStore, - propsData: { - endpoint: 'endpoint.json', - }, - }); - }; - - const findHeader = () => wrapper.find('[data-testid="report-section-code-text"]'); - - beforeEach(() => { - mockStore = new Vuex.Store({ - ...getStoreConfig(), - actions: { fetchReport: () => {}, setEndpoint: () => {} }, - }); - - mountComponent(); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - describe('while loading', () => { - beforeEach(() => { - mockStore.state.isLoading = true; - mountComponent(); - }); - - it('renders loading state', () => { - expect(findHeader().text()).toEqual('Accessibility scanning results are being parsed'); - }); - }); - - describe('with error', () => { - beforeEach(() => { - mockStore.state.isLoading = false; - mockStore.state.hasError = true; - mountComponent(); - }); - - it('renders error state', () => { - expect(findHeader().text()).toEqual('Accessibility scanning failed loading results'); - }); - }); - - describe('with a report', () => { - describe('with no issues', () => { - beforeEach(() => { - mockStore.state.report = { - summary: { - errored: 0, - }, - }; - }); - - it('renders no issues header', () => { - expect(findHeader().text()).toContain( - 'Accessibility scanning detected no issues for the source branch only', - ); - }); - }); - - describe('with one issue', () => { - beforeEach(() => { - mockStore.state.report = { - summary: { - errored: 1, - }, - }; - }); - - it('renders one issue header', () => { - expect(findHeader().text()).toContain( - 'Accessibility scanning detected 1 issue for the source branch only', - ); - }); - }); - - describe('with multiple issues', () => { - beforeEach(() => { - mockStore.state.report = { - summary: { - errored: 2, - }, - }; - }); - - it('renders multiple issues header', () => { - expect(findHeader().text()).toContain( - 'Accessibility scanning detected 2 issues for the source branch only', - ); - }); - }); - - describe('with issues to show', () => { - beforeEach(() => { - mockStore.state.report = mockReport; - }); - - it('renders custom accessibility issue body', () => { - const issueBody = wrapper.findComponent(AccessibilityIssueBody); - - expect(issueBody.props('issue').code).toBe(mockReport.new_errors[0].code); - expect(issueBody.props('issue').message).toBe(mockReport.new_errors[0].message); - expect(issueBody.props('isNew')).toBe(true); - }); - }); - }); -}); diff --git a/spec/frontend/reports/accessibility_report/mock_data.js b/spec/frontend/reports/accessibility_report/mock_data.js deleted file mode 100644 index 9dace1e7c54..00000000000 --- a/spec/frontend/reports/accessibility_report/mock_data.js +++ /dev/null @@ -1,53 +0,0 @@ -export const mockReport = { - status: 'failed', - summary: { - total: 2, - resolved: 0, - errored: 2, - }, - new_errors: [ - { - code: 'WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail', - type: 'error', - typeCode: 1, - message: - 'This element has insufficient contrast at this conformance level. Expected a contrast ratio of at least 4.5:1, but text in this element has a contrast ratio of 3.84:1. Recommendation: change text colour to #767676.', - context: '<a href="/stages-devops-lifecycle/" class="main-nav-link">Product</a>', - selector: '#main-nav > div:nth-child(2) > ul > li:nth-child(1) > a', - runner: 'htmlcs', - runnerExtras: {}, - }, - ], - new_notes: [], - new_warnings: [], - resolved_errors: [ - { - code: 'WCAG2AA.Principle4.Guideline4_1.4_1_2.H91.A.NoContent', - type: 'error', - typeCode: 1, - message: - 'Anchor element found with a valid href attribute, but no link content has been supplied.', - context: '<a href="/" class="navbar-brand animated"><svg height="36" viewBox="0 0 1...</a>', - selector: '#main-nav > div:nth-child(1) > a', - runner: 'htmlcs', - runnerExtras: {}, - }, - ], - resolved_notes: [], - resolved_warnings: [], - existing_errors: [ - { - code: 'WCAG2AA.Principle4.Guideline4_1.4_1_2.H91.A.NoContent', - type: 'error', - typeCode: 1, - message: - 'Anchor element found with a valid href attribute, but no link content has been supplied.', - context: '<a href="/" class="navbar-brand animated"><svg height="36" viewBox="0 0 1...</a>', - selector: '#main-nav > div:nth-child(1) > a', - runner: 'htmlcs', - runnerExtras: {}, - }, - ], - existing_notes: [], - existing_warnings: [], -}; diff --git a/spec/frontend/reports/accessibility_report/store/actions_spec.js b/spec/frontend/reports/accessibility_report/store/actions_spec.js deleted file mode 100644 index bab6c4905a7..00000000000 --- a/spec/frontend/reports/accessibility_report/store/actions_spec.js +++ /dev/null @@ -1,115 +0,0 @@ -import MockAdapter from 'axios-mock-adapter'; -import testAction from 'helpers/vuex_action_helper'; -import { TEST_HOST } from 'spec/test_constants'; -import axios from '~/lib/utils/axios_utils'; -import createStore from '~/reports/accessibility_report/store'; -import * as actions from '~/reports/accessibility_report/store/actions'; -import * as types from '~/reports/accessibility_report/store/mutation_types'; -import { mockReport } from '../mock_data'; - -describe('Accessibility Reports actions', () => { - let localState; - let localStore; - - beforeEach(() => { - localStore = createStore(); - localState = localStore.state; - }); - - describe('setEndpoints', () => { - it('should commit SET_ENDPOINTS mutation', () => { - const endpoint = 'endpoint.json'; - - return testAction( - actions.setEndpoint, - endpoint, - localState, - [{ type: types.SET_ENDPOINT, payload: endpoint }], - [], - ); - }); - }); - - describe('fetchReport', () => { - let mock; - - beforeEach(() => { - localState.endpoint = `${TEST_HOST}/endpoint.json`; - mock = new MockAdapter(axios); - }); - - afterEach(() => { - mock.restore(); - actions.stopPolling(); - actions.clearEtagPoll(); - }); - - describe('success', () => { - it('should commit REQUEST_REPORT mutation and dispatch receiveReportSuccess', () => { - const data = { report: { summary: {} } }; - mock.onGet(`${TEST_HOST}/endpoint.json`).reply(200, data); - - return testAction( - actions.fetchReport, - null, - localState, - [{ type: types.REQUEST_REPORT }], - [ - { - payload: { status: 200, data }, - type: 'receiveReportSuccess', - }, - ], - ); - }); - }); - - describe('error', () => { - it('should commit REQUEST_REPORT and RECEIVE_REPORT_ERROR mutations', () => { - mock.onGet(`${TEST_HOST}/endpoint.json`).reply(500); - - return testAction( - actions.fetchReport, - null, - localState, - [{ type: types.REQUEST_REPORT }], - [{ type: 'receiveReportError' }], - ); - }); - }); - }); - - describe('receiveReportSuccess', () => { - it('should commit RECEIVE_REPORT_SUCCESS mutation with 200', () => { - return testAction( - actions.receiveReportSuccess, - { status: 200, data: mockReport }, - localState, - [{ type: types.RECEIVE_REPORT_SUCCESS, payload: mockReport }], - [{ type: 'stopPolling' }], - ); - }); - - it('should not commit RECEIVE_REPORTS_SUCCESS mutation with 204', () => { - return testAction( - actions.receiveReportSuccess, - { status: 204, data: mockReport }, - localState, - [], - [], - ); - }); - }); - - describe('receiveReportError', () => { - it('should commit RECEIVE_REPORT_ERROR mutation', () => { - return testAction( - actions.receiveReportError, - null, - localState, - [{ type: types.RECEIVE_REPORT_ERROR }], - [{ type: 'stopPolling' }], - ); - }); - }); -}); diff --git a/spec/frontend/reports/accessibility_report/store/getters_spec.js b/spec/frontend/reports/accessibility_report/store/getters_spec.js deleted file mode 100644 index 96344596003..00000000000 --- a/spec/frontend/reports/accessibility_report/store/getters_spec.js +++ /dev/null @@ -1,149 +0,0 @@ -import createStore from '~/reports/accessibility_report/store'; -import * as getters from '~/reports/accessibility_report/store/getters'; -import { LOADING, ERROR, SUCCESS, STATUS_FAILED } from '~/reports/constants'; - -describe('Accessibility reports store getters', () => { - let localState; - let localStore; - - beforeEach(() => { - localStore = createStore(); - localState = localStore.state; - }); - - describe('summaryStatus', () => { - describe('when summary is loading', () => { - it('returns loading status', () => { - localState.isLoading = true; - - expect(getters.summaryStatus(localState)).toEqual(LOADING); - }); - }); - - describe('when summary has error', () => { - it('returns error status', () => { - localState.hasError = true; - - expect(getters.summaryStatus(localState)).toEqual(ERROR); - }); - }); - - describe('when summary has failed status', () => { - it('returns loading status', () => { - localState.status = STATUS_FAILED; - - expect(getters.summaryStatus(localState)).toEqual(ERROR); - }); - }); - - describe('when summary has successfully loaded', () => { - it('returns loading status', () => { - expect(getters.summaryStatus(localState)).toEqual(SUCCESS); - }); - }); - }); - - describe('groupedSummaryText', () => { - describe('when state is loading', () => { - it('returns the loading summary message', () => { - localState.isLoading = true; - const result = 'Accessibility scanning results are being parsed'; - - expect(getters.groupedSummaryText(localState)).toEqual(result); - }); - }); - - describe('when state has error', () => { - it('returns the error summary message', () => { - localState.hasError = true; - const result = 'Accessibility scanning failed loading results'; - - expect(getters.groupedSummaryText(localState)).toEqual(result); - }); - }); - - describe('when state has successfully loaded', () => { - describe('when report has errors', () => { - it('returns summary message containing number of errors', () => { - localState.report = { - summary: { - errored: 2, - }, - }; - const result = 'Accessibility scanning detected 2 issues for the source branch only'; - - expect(getters.groupedSummaryText(localState)).toEqual(result); - }); - }); - - describe('when report has no errors', () => { - it('returns summary message containing no errors', () => { - localState.report = { - summary: { - errored: 0, - }, - }; - const result = 'Accessibility scanning detected no issues for the source branch only'; - - expect(getters.groupedSummaryText(localState)).toEqual(result); - }); - }); - }); - }); - - describe('shouldRenderIssuesList', () => { - describe('when has issues to render', () => { - it('returns true', () => { - localState.report = { - existing_errors: [{ name: 'Issue' }], - }; - - expect(getters.shouldRenderIssuesList(localState)).toEqual(true); - }); - }); - - describe('when does not have issues to render', () => { - it('returns false', () => { - localState.report = { - status: 'success', - summary: { errored: 0 }, - }; - - expect(getters.shouldRenderIssuesList(localState)).toEqual(false); - }); - }); - }); - - describe('unresolvedIssues', () => { - it('returns the array unresolved errors', () => { - localState.report = { - existing_errors: [1], - }; - const result = [1]; - - expect(getters.unresolvedIssues(localState)).toEqual(result); - }); - }); - - describe('resolvedIssues', () => { - it('returns array of resolved errors', () => { - localState.report = { - resolved_errors: [1], - }; - const result = [1]; - - expect(getters.resolvedIssues(localState)).toEqual(result); - }); - }); - - describe('newIssues', () => { - it('returns array of new errors', () => { - localState.report = { - new_errors: [1], - }; - const result = [1]; - - expect(getters.newIssues(localState)).toEqual(result); - }); - }); -}); diff --git a/spec/frontend/reports/accessibility_report/store/mutations_spec.js b/spec/frontend/reports/accessibility_report/store/mutations_spec.js deleted file mode 100644 index b336261d804..00000000000 --- a/spec/frontend/reports/accessibility_report/store/mutations_spec.js +++ /dev/null @@ -1,64 +0,0 @@ -import createStore from '~/reports/accessibility_report/store'; -import mutations from '~/reports/accessibility_report/store/mutations'; - -describe('Accessibility Reports mutations', () => { - let localState; - let localStore; - - beforeEach(() => { - localStore = createStore(); - localState = localStore.state; - }); - - describe('SET_ENDPOINT', () => { - it('sets endpoint to given value', () => { - const endpoint = 'endpoint.json'; - mutations.SET_ENDPOINT(localState, endpoint); - - expect(localState.endpoint).toEqual(endpoint); - }); - }); - - describe('REQUEST_REPORT', () => { - it('sets isLoading to true', () => { - mutations.REQUEST_REPORT(localState); - - expect(localState.isLoading).toEqual(true); - }); - }); - - describe('RECEIVE_REPORT_SUCCESS', () => { - it('sets isLoading to false', () => { - mutations.RECEIVE_REPORT_SUCCESS(localState, {}); - - expect(localState.isLoading).toEqual(false); - }); - - it('sets hasError to false', () => { - mutations.RECEIVE_REPORT_SUCCESS(localState, {}); - - expect(localState.hasError).toEqual(false); - }); - - it('sets report to response report', () => { - const report = { data: 'testing' }; - mutations.RECEIVE_REPORT_SUCCESS(localState, report); - - expect(localState.report).toEqual(report); - }); - }); - - describe('RECEIVE_REPORT_ERROR', () => { - it('sets isLoading to false', () => { - mutations.RECEIVE_REPORT_ERROR(localState); - - expect(localState.isLoading).toEqual(false); - }); - - it('sets hasError to true', () => { - mutations.RECEIVE_REPORT_ERROR(localState); - - expect(localState.hasError).toEqual(true); - }); - }); -}); diff --git a/spec/frontend/vue_merge_request_widget/components/states/__snapshots__/mr_widget_auto_merge_enabled_spec.js.snap b/spec/frontend/vue_merge_request_widget/components/states/__snapshots__/mr_widget_auto_merge_enabled_spec.js.snap index 519475f8953..7d9dbcd68ff 100644 --- a/spec/frontend/vue_merge_request_widget/components/states/__snapshots__/mr_widget_auto_merge_enabled_spec.js.snap +++ b/spec/frontend/vue_merge_request_widget/components/states/__snapshots__/mr_widget_auto_merge_enabled_spec.js.snap @@ -79,6 +79,7 @@ exports[`MRWidgetAutoMergeEnabled when graphql is disabled template should have class="dropdown b-dropdown gl-new-dropdown gl-display-block gl-md-display-none! btn-group" lazy="" no-caret="" + title="Options" > <!----> <button @@ -255,6 +256,7 @@ exports[`MRWidgetAutoMergeEnabled when graphql is enabled template should have c class="dropdown b-dropdown gl-new-dropdown gl-display-block gl-md-display-none! btn-group" lazy="" no-caret="" + title="Options" > <!----> <button diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_ready_to_merge_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_ready_to_merge_spec.js index 9844af208d2..48d3f15560b 100644 --- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_ready_to_merge_spec.js +++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_ready_to_merge_spec.js @@ -795,7 +795,7 @@ describe('ReadyToMerge', () => { }); it('shows the diverged commits text when the source branch is behind the target', () => { - createComponent({ mr: { divergedCommitsCount: 9001 } }); + createComponent({ mr: { divergedCommitsCount: 9001, canMerge: false } }); expect(wrapper.text()).toEqual( expect.stringContaining('The source branch is 9001 commits behind the target branch'), diff --git a/spec/frontend/vue_merge_request_widget/components/terraform/mr_widget_terraform_container_spec.js b/spec/frontend/vue_merge_request_widget/components/terraform/mr_widget_terraform_container_spec.js deleted file mode 100644 index db10236e5de..00000000000 --- a/spec/frontend/vue_merge_request_widget/components/terraform/mr_widget_terraform_container_spec.js +++ /dev/null @@ -1,175 +0,0 @@ -import { GlSkeletonLoader, GlSprintf } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; -import MockAdapter from 'axios-mock-adapter'; -import { nextTick } from 'vue'; -import axios from '~/lib/utils/axios_utils'; -import Poll from '~/lib/utils/poll'; -import MrWidgetExpanableSection from '~/vue_merge_request_widget/components/mr_widget_expandable_section.vue'; -import MrWidgetTerraformContainer from '~/vue_merge_request_widget/components/terraform/mr_widget_terraform_container.vue'; -import TerraformPlan from '~/vue_merge_request_widget/components/terraform/terraform_plan.vue'; -import { invalidPlanWithName, plans, validPlanWithName } from './mock_data'; - -describe('MrWidgetTerraformConainer', () => { - let mock; - let wrapper; - - const propsData = { endpoint: '/path/to/terraform/report.json' }; - - const findHeader = () => wrapper.find('[data-testid="terraform-header-text"]'); - const findPlans = () => - wrapper.findAllComponents(TerraformPlan).wrappers.map((x) => x.props('plan')); - - const mockPollingApi = (response, body, header) => { - mock.onGet(propsData.endpoint).reply(response, body, header); - }; - - const mountWrapper = () => { - wrapper = shallowMount(MrWidgetTerraformContainer, { - propsData, - stubs: { MrWidgetExpanableSection, GlSprintf }, - }); - return axios.waitForAll(); - }; - - beforeEach(() => { - mock = new MockAdapter(axios); - }); - - afterEach(() => { - wrapper.destroy(); - mock.restore(); - }); - - describe('when data is loading', () => { - beforeEach(async () => { - mockPollingApi(200, plans, {}); - - await mountWrapper(); - // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details - // eslint-disable-next-line no-restricted-syntax - wrapper.setData({ loading: true }); - await nextTick(); - }); - - it('diplays loading skeleton', () => { - expect(wrapper.findComponent(GlSkeletonLoader).exists()).toBe(true); - expect(wrapper.findComponent(MrWidgetExpanableSection).exists()).toBe(false); - }); - }); - - describe('when data has finished loading', () => { - beforeEach(() => { - mockPollingApi(200, plans, {}); - return mountWrapper(); - }); - - it('displays terraform content', () => { - expect(wrapper.findComponent(GlSkeletonLoader).exists()).toBe(false); - expect(wrapper.findComponent(MrWidgetExpanableSection).exists()).toBe(true); - expect(findPlans()).toEqual(Object.values(plans)); - }); - - describe('when data includes one invalid plan', () => { - beforeEach(() => { - const invalidPlanGroup = { bad_plan: invalidPlanWithName }; - mockPollingApi(200, invalidPlanGroup, {}); - return mountWrapper(); - }); - - it('displays header text for one invalid plan', () => { - expect(findHeader().text()).toBe('1 Terraform report failed to generate'); - }); - }); - - describe('when data includes multiple invalid plans', () => { - beforeEach(() => { - const invalidPlanGroup = { - bad_plan_one: invalidPlanWithName, - bad_plan_two: invalidPlanWithName, - }; - - mockPollingApi(200, invalidPlanGroup, {}); - return mountWrapper(); - }); - - it('displays header text for multiple invalid plans', () => { - expect(findHeader().text()).toBe('2 Terraform reports failed to generate'); - }); - }); - - describe('when data includes one valid plan', () => { - beforeEach(() => { - const validPlanGroup = { valid_plan: validPlanWithName }; - mockPollingApi(200, validPlanGroup, {}); - return mountWrapper(); - }); - - it('displays header text for one valid plans', () => { - expect(findHeader().text()).toBe('1 Terraform report was generated in your pipelines'); - }); - }); - - describe('when data includes multiple valid plans', () => { - beforeEach(() => { - const validPlanGroup = { - valid_plan_one: validPlanWithName, - valid_plan_two: validPlanWithName, - }; - mockPollingApi(200, validPlanGroup, {}); - return mountWrapper(); - }); - - it('displays header text for multiple valid plans', () => { - expect(findHeader().text()).toBe('2 Terraform reports were generated in your pipelines'); - }); - }); - }); - - describe('polling', () => { - let pollRequest; - let pollStop; - - beforeEach(() => { - pollRequest = jest.spyOn(Poll.prototype, 'makeRequest'); - pollStop = jest.spyOn(Poll.prototype, 'stop'); - }); - - afterEach(() => { - pollRequest.mockRestore(); - pollStop.mockRestore(); - }); - - describe('successful poll', () => { - beforeEach(() => { - mockPollingApi(200, plans, {}); - - return mountWrapper(); - }); - - it('does not make additional requests after poll is successful', () => { - expect(pollRequest).toHaveBeenCalledTimes(1); - expect(pollStop).toHaveBeenCalledTimes(1); - }); - }); - - describe('polling fails', () => { - beforeEach(() => { - mockPollingApi(500, null, {}); - return mountWrapper(); - }); - - it('stops loading', () => { - expect(wrapper.findComponent(GlSkeletonLoader).exists()).toBe(false); - }); - - it('generates one broken plan', () => { - expect(findPlans()).toEqual([{ tf_report_error: 'api_error' }]); - }); - - it('does not make additional requests after poll is unsuccessful', () => { - expect(pollRequest).toHaveBeenCalledTimes(1); - expect(pollStop).toHaveBeenCalledTimes(1); - }); - }); - }); -}); diff --git a/spec/frontend/vue_merge_request_widget/components/terraform/terraform_plan_spec.js b/spec/frontend/vue_merge_request_widget/components/terraform/terraform_plan_spec.js deleted file mode 100644 index 3c9f6c2e165..00000000000 --- a/spec/frontend/vue_merge_request_widget/components/terraform/terraform_plan_spec.js +++ /dev/null @@ -1,93 +0,0 @@ -import { GlLink, GlSprintf } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; -import TerraformPlan from '~/vue_merge_request_widget/components/terraform/terraform_plan.vue'; -import { - invalidPlanWithName, - invalidPlanWithoutName, - validPlanWithName, - validPlanWithoutName, -} from './mock_data'; - -describe('TerraformPlan', () => { - let wrapper; - - const findIcon = () => wrapper.find('[data-testid="change-type-icon"]'); - const findLogButton = () => wrapper.find('[data-testid="terraform-report-link"]'); - - const mountWrapper = (propsData) => { - wrapper = shallowMount(TerraformPlan, { stubs: { GlLink, GlSprintf }, propsData }); - }; - - afterEach(() => { - wrapper.destroy(); - }); - - describe('valid plan with job_name', () => { - beforeEach(() => { - mountWrapper({ plan: validPlanWithName }); - }); - - it('displays a document icon', () => { - expect(findIcon().attributes('name')).toBe('doc-changes'); - }); - - it('diplays the header text with a name', () => { - expect(wrapper.text()).toContain(`The job ${validPlanWithName.job_name} generated a report.`); - }); - - it('diplays the reported changes', () => { - expect(wrapper.text()).toContain( - `Reported Resource Changes: ${validPlanWithName.create} to add, ${validPlanWithName.update} to change, ${validPlanWithName.delete} to delete`, - ); - }); - - it('renders button when url is found', () => { - expect(findLogButton().exists()).toBe(true); - expect(findLogButton().text()).toEqual('View full log'); - }); - }); - - describe('valid plan without job_name', () => { - beforeEach(() => { - mountWrapper({ plan: validPlanWithoutName }); - }); - - it('diplays the header text without a name', () => { - expect(wrapper.text()).toContain('A report was generated in your pipelines.'); - }); - }); - - describe('invalid plan with job_name', () => { - beforeEach(() => { - mountWrapper({ plan: invalidPlanWithName }); - }); - - it('displays a warning icon', () => { - expect(findIcon().attributes('name')).toBe('warning'); - }); - - it('diplays the header text with a name', () => { - expect(wrapper.text()).toContain( - `The job ${invalidPlanWithName.job_name} failed to generate a report.`, - ); - }); - - it('diplays generic error since report values are missing', () => { - expect(wrapper.text()).toContain('Generating the report caused an error.'); - }); - }); - - describe('invalid plan with out job_name', () => { - beforeEach(() => { - mountWrapper({ plan: invalidPlanWithoutName }); - }); - - it('diplays the header text without a name', () => { - expect(wrapper.text()).toContain('A report failed to generate.'); - }); - - it('does not render button because url is missing', () => { - expect(findLogButton().exists()).toBe(false); - }); - }); -}); diff --git a/spec/frontend/vue_shared/components/url_sync_spec.js b/spec/frontend/vue_shared/components/url_sync_spec.js index aefe6a5c3e8..acda1a64a75 100644 --- a/spec/frontend/vue_shared/components/url_sync_spec.js +++ b/spec/frontend/vue_shared/components/url_sync_spec.js @@ -1,10 +1,11 @@ import { shallowMount } from '@vue/test-utils'; import { historyPushState } from '~/lib/utils/common_utils'; -import { mergeUrlParams } from '~/lib/utils/url_utility'; -import UrlSyncComponent from '~/vue_shared/components/url_sync.vue'; +import { mergeUrlParams, setUrlParams } from '~/lib/utils/url_utility'; +import UrlSyncComponent, { URL_SET_PARAMS_STRATEGY } from '~/vue_shared/components/url_sync.vue'; jest.mock('~/lib/utils/url_utility', () => ({ - mergeUrlParams: jest.fn((query, url) => `urlParams: ${query} ${url}`), + mergeUrlParams: jest.fn((query, url) => `urlParams: ${JSON.stringify(query)} ${url}`), + setUrlParams: jest.fn((query, url) => `urlParams: ${JSON.stringify(query)} ${url}`), })); jest.mock('~/lib/utils/common_utils', () => ({ @@ -17,9 +18,14 @@ describe('url sync component', () => { const findButton = () => wrapper.find('button'); - const createComponent = ({ query = mockQuery, scopedSlots, slots } = {}) => { + const createComponent = ({ + query = mockQuery, + scopedSlots, + slots, + urlParamsUpdateStrategy, + } = {}) => { wrapper = shallowMount(UrlSyncComponent, { - propsData: { query }, + propsData: { query, ...(urlParamsUpdateStrategy && { urlParamsUpdateStrategy }) }, scopedSlots, slots, }); @@ -29,21 +35,39 @@ describe('url sync component', () => { wrapper.destroy(); }); - const expectUrlSync = (query, times, mergeUrlParamsReturnValue) => { - expect(mergeUrlParams).toHaveBeenCalledTimes(times); - expect(mergeUrlParams).toHaveBeenCalledWith(query, window.location.href, { - spreadArrays: true, - }); + const expectUrlSyncFactory = ( + query, + times, + urlParamsUpdateStrategy, + urlOptions, + urlReturnValue, + ) => { + expect(urlParamsUpdateStrategy).toHaveBeenCalledTimes(times); + expect(urlParamsUpdateStrategy).toHaveBeenCalledWith(query, window.location.href, urlOptions); expect(historyPushState).toHaveBeenCalledTimes(times); - expect(historyPushState).toHaveBeenCalledWith(mergeUrlParamsReturnValue); + expect(historyPushState).toHaveBeenCalledWith(urlReturnValue); + }; + + const expectUrlSyncWithMergeUrlParams = (query, times, mergeUrlParamsReturnValue) => { + expectUrlSyncFactory( + query, + times, + mergeUrlParams, + { spreadArrays: true }, + mergeUrlParamsReturnValue, + ); + }; + + const expectUrlSyncWithSetUrlParams = (query, times, setUrlParamsReturnValue) => { + expectUrlSyncFactory(query, times, setUrlParams, true, setUrlParamsReturnValue); }; describe('with query as a props', () => { it('immediately syncs the query to the URL', () => { createComponent(); - expectUrlSync(mockQuery, 1, mergeUrlParams.mock.results[0].value); + expectUrlSyncWithMergeUrlParams(mockQuery, 1, mergeUrlParams.mock.results[0].value); }); describe('when the query is modified', () => { @@ -54,11 +78,21 @@ describe('url sync component', () => { // using setProps to test the watcher await wrapper.setProps({ query: newQuery }); - expectUrlSync(mockQuery, 2, mergeUrlParams.mock.results[1].value); + expectUrlSyncWithMergeUrlParams(mockQuery, 2, mergeUrlParams.mock.results[1].value); }); }); }); + describe('with url-params-update-strategy equals to URL_SET_PARAMS_STRATEGY', () => { + it('uses setUrlParams to generate URL', () => { + createComponent({ + urlParamsUpdateStrategy: URL_SET_PARAMS_STRATEGY, + }); + + expectUrlSyncWithSetUrlParams(mockQuery, 1, setUrlParams.mock.results[0].value); + }); + }); + describe('with scoped slot', () => { const scopedSlots = { default: ` @@ -77,7 +111,7 @@ describe('url sync component', () => { findButton().trigger('click'); - expectUrlSync({ bar: 'baz' }, 1, mergeUrlParams.mock.results[0].value); + expectUrlSyncWithMergeUrlParams({ bar: 'baz' }, 1, mergeUrlParams.mock.results[0].value); }); }); diff --git a/spec/scripts/lib/glfm/update_specification_spec.rb b/spec/scripts/lib/glfm/update_specification_spec.rb index 1cdc90876de..7daf85b5d25 100644 --- a/spec/scripts/lib/glfm/update_specification_spec.rb +++ b/spec/scripts/lib/glfm/update_specification_spec.rb @@ -1,8 +1,12 @@ # frozen_string_literal: true + require 'fast_spec_helper' require_relative '../../../../scripts/lib/glfm/update_specification' +require_relative '../../../support/helpers/next_instance_of' RSpec.describe Glfm::UpdateSpecification, '#process' do + include NextInstanceOf + subject { described_class.new } let(:ghfm_spec_txt_uri) { described_class::GHFM_SPEC_TXT_URI } @@ -79,7 +83,9 @@ RSpec.describe Glfm::UpdateSpecification, '#process' do # We mock out the URI and local file IO objects with real StringIO, instead of just mock # objects. This gives better and more realistic coverage, while still avoiding # actual network and filesystem I/O during the spec run. - allow(URI).to receive(:open).with(ghfm_spec_txt_uri) { ghfm_spec_txt_uri_io } + allow_next_instance_of(URI::HTTP) do |instance| + allow(instance).to receive(:open).and_return(ghfm_spec_txt_uri_io) + end allow(File).to receive(:open).with(ghfm_spec_md_path) { ghfm_spec_txt_local_io } allow(File).to receive(:open).with(glfm_intro_md_path) { glfm_intro_md_io } allow(File).to receive(:open).with(glfm_examples_txt_path) { glfm_examples_txt_io } @@ -92,7 +98,7 @@ RSpec.describe Glfm::UpdateSpecification, '#process' do describe 'retrieving latest GHFM spec.txt' do context 'when UPDATE_GHFM_SPEC_MD is not true (default)' do it 'does not download' do - expect(URI).not_to receive(:open).with(ghfm_spec_txt_uri) + expect(URI).not_to receive(:parse).with(ghfm_spec_txt_uri) subject.process diff --git a/spec/services/bulk_imports/create_pipeline_trackers_service_spec.rb b/spec/services/bulk_imports/create_pipeline_trackers_service_spec.rb index d7b00ba04ab..c07bc761016 100644 --- a/spec/services/bulk_imports/create_pipeline_trackers_service_spec.rb +++ b/spec/services/bulk_imports/create_pipeline_trackers_service_spec.rb @@ -75,7 +75,7 @@ RSpec.describe BulkImports::CreatePipelineTrackersService do expect_next_instance_of(Gitlab::Import::Logger) do |logger| expect(logger).to receive(:info).with({ message: 'Pipeline skipped as source instance version not compatible with pipeline', - entity_id: entity.id, + bulk_import_entity_id: entity.id, pipeline_name: 'PipelineClass4', minimum_source_version: '15.1.0', maximum_source_version: nil, @@ -84,7 +84,7 @@ RSpec.describe BulkImports::CreatePipelineTrackersService do expect(logger).to receive(:info).with({ message: 'Pipeline skipped as source instance version not compatible with pipeline', - entity_id: entity.id, + bulk_import_entity_id: entity.id, pipeline_name: 'PipelineClass5', minimum_source_version: '16.0.0', maximum_source_version: nil, diff --git a/spec/services/incident_management/timeline_events/create_service_spec.rb b/spec/services/incident_management/timeline_events/create_service_spec.rb index 0e6864387d6..a7f448c825f 100644 --- a/spec/services/incident_management/timeline_events/create_service_spec.rb +++ b/spec/services/incident_management/timeline_events/create_service_spec.rb @@ -84,50 +84,6 @@ RSpec.describe IncidentManagement::TimelineEvents::CreateService do expect(result.action).to eq(IncidentManagement::TimelineEvents::DEFAULT_ACTION) end - end - - context 'with non_default action' do - it_behaves_like 'success response' - - it 'matches the action from arguments', :aggregate_failures do - result = execute.payload[:timeline_event] - - expect(result.action).to eq(args[:action]) - end - end - - context 'with editable param' do - let(:args) do - { - note: 'note', - occurred_at: Time.current, - action: 'new comment', - promoted_from_note: comment, - editable: editable - } - end - - context 'when editable is true' do - let(:editable) { true } - - it_behaves_like 'success response' - end - - context 'when editable is false' do - let(:editable) { false } - - it_behaves_like 'success response' - end - end - - it 'successfully creates a database record', :aggregate_failures do - expect { execute }.to change { ::IncidentManagement::TimelineEvent.count }.by(1) - end - - context 'when incident_timeline feature flag is enabled' do - before do - stub_feature_flags(incident_timeline: project) - end it 'creates a system note' do expect { execute }.to change { incident.notes.reload.count }.by(1) @@ -168,14 +124,42 @@ RSpec.describe IncidentManagement::TimelineEvents::CreateService do end end - context 'when incident_timeline feature flag is disabled' do - before do - stub_feature_flags(incident_timeline: false) + context 'with non_default action' do + it_behaves_like 'success response' + + it 'matches the action from arguments', :aggregate_failures do + result = execute.payload[:timeline_event] + + expect(result.action).to eq(args[:action]) end + end - it 'does not create a system note' do - expect { execute }.not_to change { incident.notes.reload.count } + context 'with editable param' do + let(:args) do + { + note: 'note', + occurred_at: Time.current, + action: 'new comment', + promoted_from_note: comment, + editable: editable + } + end + + context 'when editable is true' do + let(:editable) { true } + + it_behaves_like 'success response' end + + context 'when editable is false' do + let(:editable) { false } + + it_behaves_like 'success response' + end + end + + it 'successfully creates a database record', :aggregate_failures do + expect { execute }.to change { ::IncidentManagement::TimelineEvent.count }.by(1) end end diff --git a/spec/services/incident_management/timeline_events/destroy_service_spec.rb b/spec/services/incident_management/timeline_events/destroy_service_spec.rb index 2fffe75b53c..e1b258960ae 100644 --- a/spec/services/incident_management/timeline_events/destroy_service_spec.rb +++ b/spec/services/incident_management/timeline_events/destroy_service_spec.rb @@ -51,7 +51,7 @@ RSpec.describe IncidentManagement::TimelineEvents::DestroyService do it_behaves_like 'error response', 'Timeline text cannot be removed' end - context 'success response' do + context 'with success response' do it 'successfully returns the timeline event', :aggregate_failures do expect(execute).to be_success @@ -60,27 +60,11 @@ RSpec.describe IncidentManagement::TimelineEvents::DestroyService do expect(result.id).to eq(timeline_event.id) end - it_behaves_like 'an incident management tracked event', :incident_management_timeline_event_deleted - end - - context 'when incident_timeline feature flag is enabled' do - before do - stub_feature_flags(incident_timeline: project) - end - it 'creates a system note' do expect { execute }.to change { incident.notes.reload.count }.by(1) end - end - - context 'when incident_timeline feature flag is disabled' do - before do - stub_feature_flags(incident_timeline: false) - end - it 'does not create a system note' do - expect { execute }.not_to change { incident.notes.reload.count } - end + it_behaves_like 'an incident management tracked event', :incident_management_timeline_event_deleted end end end diff --git a/spec/services/incident_management/timeline_events/update_service_spec.rb b/spec/services/incident_management/timeline_events/update_service_spec.rb index 21aa983dbd1..5d8518cf2ef 100644 --- a/spec/services/incident_management/timeline_events/update_service_spec.rb +++ b/spec/services/incident_management/timeline_events/update_service_spec.rb @@ -12,10 +12,6 @@ RSpec.describe IncidentManagement::TimelineEvents::UpdateService do let(:params) { { note: 'Updated note', occurred_at: occurred_at } } let(:current_user) { user } - before do - stub_feature_flags(incident_timeline: project) - end - describe '#execute' do shared_examples 'successful response' do it 'responds with success', :aggregate_failures do @@ -70,16 +66,6 @@ RSpec.describe IncidentManagement::TimelineEvents::UpdateService do it_behaves_like 'passing the correct was_changed value', :occurred_at_and_note - context 'when incident_timeline feature flag is disabled' do - before do - stub_feature_flags(incident_timeline: false) - end - - it 'does not add a system note' do - expect { execute }.not_to change { incident.notes } - end - end - context 'when note is nil' do let(:params) { { occurred_at: occurred_at } } diff --git a/spec/support/capybara_slow_finder.rb b/spec/support/capybara_slow_finder.rb index 029e11e4460..975ddd52c1f 100644 --- a/spec/support/capybara_slow_finder.rb +++ b/spec/support/capybara_slow_finder.rb @@ -2,7 +2,9 @@ module Capybara MESSAGE = <<~MSG - Timeout reached while running a waiting Capybara finder. Consider using a non-waiting finder. + Timeout (%{timeout}s) reached while running a waiting Capybara finder. + Consider using a non-waiting finder. + See https://www.cloudbees.com/blog/faster-rails-tests MSG @@ -19,7 +21,8 @@ module Capybara raise e unless seconds > 0 && Gitlab::Metrics::System.monotonic_time - start_time > seconds - raise e, "#{$!}#{MESSAGE}", e.backtrace + message = format(MESSAGE, timeout: seconds) + raise e, "#{$!}\n\n#{message}", e.backtrace end end diff --git a/spec/support_specs/capybara_slow_finder_spec.rb b/spec/support_specs/capybara_slow_finder_spec.rb index cba75a7fba2..b0438a7a78b 100644 --- a/spec/support_specs/capybara_slow_finder_spec.rb +++ b/spec/support_specs/capybara_slow_finder_spec.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true -require 'spec_helper' +require 'fast_spec_helper' +require 'capybara' +require 'support/capybara_slow_finder' RSpec.describe Capybara::Node::Base::SlowFinder do # rubocop:disable RSpec/FilePath context 'without timeout' do @@ -38,10 +40,12 @@ RSpec.describe Capybara::Node::Base::SlowFinder do # rubocop:disable RSpec/FileP end context 'with timeout' do + let(:timeout) { 0.01 } + let(:slow_finder) do Class.new do def synchronize(seconds = nil, errors: nil) - sleep 0.1 + sleep 0.02 raise Capybara::ElementNotFound end @@ -50,10 +54,24 @@ RSpec.describe Capybara::Node::Base::SlowFinder do # rubocop:disable RSpec/FileP end.new end - it 'raises a timeout error' do - expect { slow_finder.synchronize(0.01) }.to raise_error( + context 'with default timeout' do + it 'raises a timeout error' do + expect(Capybara).to receive(:default_max_wait_time).and_return(timeout) + + expect { slow_finder.synchronize }.to raise_error_element_not_found + end + end + + context 'when passed as paramater' do + it 'raises a timeout error' do + expect { slow_finder.synchronize(timeout) }.to raise_error_element_not_found + end + end + + def raise_error_element_not_found + raise_error( Capybara::ElementNotFound, - /Timeout reached while running a waiting Capybara finder./ + /\n\nTimeout \(#{timeout}s\) reached while running a waiting Capybara finder./ ) end end diff --git a/spec/views/admin/application_settings/_package_registry.html.haml_spec.rb b/spec/views/admin/application_settings/_package_registry.html.haml_spec.rb index 18a2e29adab..cb41f2e636e 100644 --- a/spec/views/admin/application_settings/_package_registry.html.haml_spec.rb +++ b/spec/views/admin/application_settings/_package_registry.html.haml_spec.rb @@ -47,7 +47,7 @@ RSpec.describe 'admin/application_settings/_package_registry' do it 'does not display the plan name when there is only one plan' do subject - expect(page).not_to have_content('Default') + expect(page).not_to have_selector('a[data-action="plan0"]') end end diff --git a/spec/workers/bulk_imports/entity_worker_spec.rb b/spec/workers/bulk_imports/entity_worker_spec.rb index ab85b587975..ba638e1369e 100644 --- a/spec/workers/bulk_imports/entity_worker_spec.rb +++ b/spec/workers/bulk_imports/entity_worker_spec.rb @@ -37,7 +37,7 @@ RSpec.describe BulkImports::EntityWorker do .to receive(:info).twice .with( hash_including( - 'entity_id' => entity.id, + 'bulk_import_entity_id' => entity.id, 'current_stage' => nil, 'message' => 'Stage starting' ) @@ -67,7 +67,7 @@ RSpec.describe BulkImports::EntityWorker do .to receive(:info).twice .with( hash_including( - 'entity_id' => entity.id, + 'bulk_import_entity_id' => entity.id, 'current_stage' => nil ) ) @@ -76,7 +76,7 @@ RSpec.describe BulkImports::EntityWorker do .to receive(:error) .with( hash_including( - 'entity_id' => entity.id, + 'bulk_import_entity_id' => entity.id, 'current_stage' => nil, 'message' => 'Error!' ) @@ -85,7 +85,7 @@ RSpec.describe BulkImports::EntityWorker do expect(Gitlab::ErrorTracking) .to receive(:track_exception) - .with(exception, entity_id: entity.id) + .with(exception, bulk_import_entity_id: entity.id) subject end @@ -99,7 +99,7 @@ RSpec.describe BulkImports::EntityWorker do .to receive(:info).twice .with( hash_including( - 'entity_id' => entity.id, + 'bulk_import_entity_id' => entity.id, 'current_stage' => 0, 'message' => 'Stage running' ) @@ -127,7 +127,7 @@ RSpec.describe BulkImports::EntityWorker do .to receive(:info).twice .with( hash_including( - 'entity_id' => entity.id, + 'bulk_import_entity_id' => entity.id, 'current_stage' => 0 ) ) diff --git a/spec/workers/bulk_imports/pipeline_worker_spec.rb b/spec/workers/bulk_imports/pipeline_worker_spec.rb index cc403f91191..ef1db948893 100644 --- a/spec/workers/bulk_imports/pipeline_worker_spec.rb +++ b/spec/workers/bulk_imports/pipeline_worker_spec.rb @@ -37,7 +37,7 @@ RSpec.describe BulkImports::PipelineWorker do .with( hash_including( 'pipeline_name' => 'FakePipeline', - 'entity_id' => entity.id + 'bulk_import_entity_id' => entity.id ) ) end @@ -83,7 +83,7 @@ RSpec.describe BulkImports::PipelineWorker do .with( hash_including( 'pipeline_tracker_id' => pipeline_tracker.id, - 'entity_id' => entity.id, + 'bulk_import_entity_id' => entity.id, 'message' => 'Unstarted pipeline not found' ) ) @@ -120,7 +120,7 @@ RSpec.describe BulkImports::PipelineWorker do .with( hash_including( 'pipeline_name' => 'FakePipeline', - 'entity_id' => entity.id, + 'bulk_import_entity_id' => entity.id, 'message' => 'Error!' ) ) @@ -130,7 +130,7 @@ RSpec.describe BulkImports::PipelineWorker do .to receive(:track_exception) .with( instance_of(StandardError), - entity_id: entity.id, + bulk_import_entity_id: entity.id, pipeline_name: pipeline_tracker.pipeline_name ) @@ -178,7 +178,7 @@ RSpec.describe BulkImports::PipelineWorker do .with( hash_including( 'pipeline_name' => 'FakePipeline', - 'entity_id' => entity.id, + 'bulk_import_entity_id' => entity.id, 'message' => 'Skipping pipeline due to failed entity' ) ) @@ -225,7 +225,7 @@ RSpec.describe BulkImports::PipelineWorker do .with( hash_including( 'pipeline_name' => 'FakePipeline', - 'entity_id' => entity.id + 'bulk_import_entity_id' => entity.id ) ) end @@ -348,7 +348,7 @@ RSpec.describe BulkImports::PipelineWorker do .with( hash_including( 'pipeline_name' => 'NdjsonPipeline', - 'entity_id' => entity.id, + 'bulk_import_entity_id' => entity.id, 'message' => 'Pipeline timeout' ) ) @@ -375,7 +375,7 @@ RSpec.describe BulkImports::PipelineWorker do .with( hash_including( 'pipeline_name' => 'NdjsonPipeline', - 'entity_id' => entity.id, + 'bulk_import_entity_id' => entity.id, 'message' => 'Export from source instance failed: Error!' ) ) |