From 25eb713a7fdb787a67d74a88a89433839aab5642 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Tue, 26 Jan 2021 18:09:30 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- .rubocop_manual_todo.yml | 32 --- Dangerfile | 5 +- .../javascripts/pages/admin/runners/index.js | 5 + .../pages/groups/settings/ci_cd/show/index.js | 5 + .../projects/settings/ci_cd/show/index.js | 5 + .../pages/shared/mount_runner_instructions.js | 32 +++ .../components/legacy_header_component.vue | 132 --------- .../pipelines/pipeline_details_bundle.js | 92 +----- .../javascripts/registry/explorer/index.js | 10 +- .../registry/explorer/pages/details.vue | 14 +- .../runner_instructions/constants.js | 18 ++ .../get_runner_platforms.query.graphql | 20 ++ .../queries/get_runner_setup.query.graphql | 16 ++ .../runner_instructions.vue | 261 ++++++++++++++++++ .../stylesheets/_page_specific_files.scss | 1 - ...cation_settings_metrics_and_profiling.scss | 3 + .../page_bundles/admin/jobs_index.scss | 5 + app/assets/stylesheets/pages/admin.scss | 11 - app/controllers/admin/runners_controller.rb | 3 + .../groups/settings/ci_cd_controller.rb | 3 + .../projects/pipelines_controller.rb | 1 - .../projects/settings/ci_cd_controller.rb | 1 + .../http_integration/create.rb | 10 +- .../prometheus_integration/create.rb | 8 +- app/graphql/mutations/branches/create.rb | 10 +- app/graphql/mutations/commits/create.rb | 10 +- .../container_expiration_policies/update.rb | 10 +- app/graphql/mutations/issues/create.rb | 8 +- .../mutations/jira_import/import_users.rb | 16 +- app/graphql/mutations/jira_import/start.rb | 16 +- .../mutations/merge_requests/create.rb | 10 +- app/graphql/mutations/releases/base.rb | 8 +- app/graphql/mutations/releases/create.rb | 2 +- app/graphql/mutations/releases/delete.rb | 2 +- app/graphql/mutations/releases/update.rb | 2 +- .../ci_configuration/configure_sast.rb | 8 +- app/helpers/user_callouts_helper.rb | 5 + app/models/pages/lookup_path.rb | 3 + app/models/user_callout.rb | 3 +- app/services/git/wiki_push_service.rb | 9 + .../metrics_and_profiling.html.haml | 2 + app/views/admin/jobs/index.html.haml | 1 + app/views/admin/runners/index.html.haml | 4 +- .../ci/runner/_how_to_setup_runner.html.haml | 2 + .../registry/repositories/index.html.haml | 5 +- .../groups/runners/_group_runners.html.haml | 4 +- .../registry/repositories/index.html.haml | 5 +- .../runners/_specific_runners.html.haml | 8 +- .../projects/git_garbage_collect_worker.rb | 11 + ...-alert-to-be-permanently-dismissible-f.yml | 5 + ...anvl-remove-graphql-pipeline-header-ff.yml | 5 + ...ssio-bulkimports-group-labels-pipeline.yml | 5 + config/application.rb | 2 + ....yml => pages_serve_from_migrated_zip.yml} | 10 +- .../development/runner_instructions.yml | 8 + .../development/wiki_housekeeping.yml | 8 + danger/changelog/Dangerfile | 2 +- danger/feature_flag/Dangerfile | 57 ++++ danger/plugins/feature_flag.rb | 10 + doc/administration/logs.md | 5 +- doc/ci/yaml/README.md | 42 +-- .../incident_management/incidents.md | 8 +- doc/user/analytics/ci_cd_analytics.md | 23 ++ doc/user/group/index.md | 3 + doc/user/project/description_templates.md | 64 ++--- .../groups/graphql/get_labels_query.rb | 38 +++ .../groups/loaders/labels_loader.rb | 24 ++ .../groups/pipelines/labels_pipeline.rb | 25 ++ lib/bulk_imports/importers/group_importer.rb | 3 +- locale/gitlab.pot | 30 ++ .../create_merge_request_spec.rb | 2 +- .../rebase_merge_request_spec.rb | 2 +- .../push_http_private_token_spec.rb | 2 +- .../create_and_process_pipeline_spec.rb | 2 +- .../create_project_with_auto_devops_spec.rb | 2 +- rubocop/cop/gitlab/httparty.rb | 4 +- rubocop/cop/gitlab/json.rb | 4 +- .../gitlab/module_with_instance_variables.rb | 4 +- rubocop/cop/gitlab/predicate_memoization.rb | 6 +- spec/features/commits_spec.rb | 3 +- .../pipelines/legacy_header_component_spec.js | 116 -------- .../registry/explorer/pages/details_spec.js | 30 +- .../runner_instructions/mock_data.js | 107 +++++++ .../runner_instructions_spec.js | 113 ++++++++ spec/helpers/issues_helper_spec.rb | 27 ++ spec/helpers/user_callouts_helper_spec.rb | 20 ++ spec/javascripts/test_bundle.js | 8 - .../groups/pipelines/labels_pipeline_spec.rb | 94 +++++++ .../importers/group_importer_spec.rb | 1 + spec/models/pages/lookup_path_spec.rb | 38 +++ .../active_record_association_reload_spec.rb | 2 - spec/rubocop/cop/avoid_becomes_spec.rb | 30 +- .../avoid_break_from_strong_memoize_spec.rb | 6 +- ...yword_arguments_in_sidekiq_workers_spec.rb | 7 +- spec/rubocop/cop/ban_catch_throw_spec.rb | 26 +- spec/rubocop/cop/default_scope_spec.rb | 39 ++- .../avoid_uploaded_file_from_params_spec.rb | 9 +- spec/rubocop/cop/gitlab/bulk_insert_spec.rb | 11 +- .../cop/gitlab/change_timezone_spec.rb | 5 +- .../gitlab/const_get_inherit_false_spec.rb | 47 ++-- .../gitlab/duplicate_spec_location_spec.rb | 2 - spec/rubocop/cop/gitlab/except_spec.rb | 3 - .../cop/gitlab/finder_with_find_by_spec.rb | 47 +--- spec/rubocop/cop/gitlab/httparty_spec.rb | 34 +-- spec/rubocop/cop/gitlab/intersect_spec.rb | 3 - spec/rubocop/cop/gitlab/json_spec.rb | 33 +-- .../module_with_instance_variables_spec.rb | 35 ++- .../cop/gitlab/policy_rule_boolean_spec.rb | 3 - .../cop/gitlab/predicate_memoization_spec.rb | 82 +++--- spec/rubocop/cop/gitlab/rails_logger_spec.rb | 22 +- spec/rubocop/cop/gitlab/union_spec.rb | 3 - .../group_public_or_visible_to_user_spec.rb | 23 +- .../inject_enterprise_edition_module_spec.rb | 24 +- .../cop/lint/last_keyword_argument_spec.rb | 2 - .../put_project_routes_under_scope_spec.rb | 2 - .../cop/qa/ambiguous_page_object_name_spec.rb | 4 - .../cop/qa/element_with_pattern_spec.rb | 4 - .../ruby_interpolation_in_translation_spec.rb | 67 ++--- .../cop/static_translation_definition_spec.rb | 98 ++++--- ...istinct_count_by_large_foreign_key_spec.rb | 42 ++- .../cop/usage_data/large_table_spec.rb | 56 ++-- spec/services/git/wiki_push_service_spec.rb | 50 ++++ spec/tooling/danger/feature_flag_spec.rb | 112 ++++++++ spec/tooling/danger/helper_spec.rb | 10 + tooling/danger/feature_flag.rb | 39 +++ tooling/danger/helper.rb | 8 +- tooling/gitlab_danger.rb | 21 +- 127 files changed, 1776 insertions(+), 1054 deletions(-) create mode 100644 app/assets/javascripts/pages/shared/mount_runner_instructions.js delete mode 100644 app/assets/javascripts/pipelines/components/legacy_header_component.vue create mode 100644 app/assets/javascripts/vue_shared/components/runner_instructions/constants.js create mode 100644 app/assets/javascripts/vue_shared/components/runner_instructions/graphql/queries/get_runner_platforms.query.graphql create mode 100644 app/assets/javascripts/vue_shared/components/runner_instructions/graphql/queries/get_runner_setup.query.graphql create mode 100644 app/assets/javascripts/vue_shared/components/runner_instructions/runner_instructions.vue create mode 100644 app/assets/stylesheets/page_bundles/admin/application_settings_metrics_and_profiling.scss create mode 100644 app/assets/stylesheets/page_bundles/admin/jobs_index.scss delete mode 100644 app/assets/stylesheets/pages/admin.scss create mode 100644 changelogs/unreleased/291048-allow-unfinished-tag-cleanup-alert-to-be-permanently-dismissible-f.yml create mode 100644 changelogs/unreleased/jivanvl-remove-graphql-pipeline-header-ff.yml create mode 100644 changelogs/unreleased/kassio-bulkimports-group-labels-pipeline.yml rename config/feature_flags/development/{graphql_pipeline_header.yml => pages_serve_from_migrated_zip.yml} (62%) create mode 100644 config/feature_flags/development/runner_instructions.yml create mode 100644 config/feature_flags/development/wiki_housekeeping.yml create mode 100644 danger/feature_flag/Dangerfile create mode 100644 danger/plugins/feature_flag.rb create mode 100644 lib/bulk_imports/groups/graphql/get_labels_query.rb create mode 100644 lib/bulk_imports/groups/loaders/labels_loader.rb create mode 100644 lib/bulk_imports/groups/pipelines/labels_pipeline.rb delete mode 100644 spec/frontend/pipelines/legacy_header_component_spec.js create mode 100644 spec/frontend/vue_shared/components/runner_instructions/mock_data.js create mode 100644 spec/frontend/vue_shared/components/runner_instructions/runner_instructions_spec.js create mode 100644 spec/lib/bulk_imports/groups/pipelines/labels_pipeline_spec.rb create mode 100644 spec/tooling/danger/feature_flag_spec.rb create mode 100644 tooling/danger/feature_flag.rb diff --git a/.rubocop_manual_todo.yml b/.rubocop_manual_todo.yml index ebc19a1972e..7f18912b34c 100644 --- a/.rubocop_manual_todo.yml +++ b/.rubocop_manual_todo.yml @@ -31,38 +31,6 @@ FactoryBot/InlineAssociation: InternalAffairs/DeprecateCopHelper: Exclude: - - 'spec/rubocop/cop/group_public_or_visible_to_user_spec.rb' - - 'spec/rubocop/cop/static_translation_definition_spec.rb' - - 'spec/rubocop/cop/lint/last_keyword_argument_spec.rb' - - 'spec/rubocop/cop/usage_data/large_table_spec.rb' - - 'spec/rubocop/cop/usage_data/distinct_count_by_large_foreign_key_spec.rb' - - 'spec/rubocop/cop/filename_length_spec.rb' - - 'spec/rubocop/cop/put_project_routes_under_scope_spec.rb' - - 'spec/rubocop/cop/gitlab/rails_logger_spec.rb' - - 'spec/rubocop/cop/gitlab/module_with_instance_variables_spec.rb' - - 'spec/rubocop/cop/gitlab/avoid_uploaded_file_from_params_spec.rb' - - 'spec/rubocop/cop/gitlab/duplicate_spec_location_spec.rb' - - 'spec/rubocop/cop/gitlab/bulk_insert_spec.rb' - - 'spec/rubocop/cop/gitlab/intersect_spec.rb' - - 'spec/rubocop/cop/gitlab/json_spec.rb' - - 'spec/rubocop/cop/gitlab/httparty_spec.rb' - - 'spec/rubocop/cop/gitlab/policy_rule_boolean_spec.rb' - - 'spec/rubocop/cop/gitlab/except_spec.rb' - - 'spec/rubocop/cop/gitlab/const_get_inherit_false_spec.rb' - - 'spec/rubocop/cop/gitlab/change_timezone_spec.rb' - - 'spec/rubocop/cop/gitlab/predicate_memoization_spec.rb' - - 'spec/rubocop/cop/gitlab/union_spec.rb' - - 'spec/rubocop/cop/gitlab/finder_with_find_by_spec.rb' - - 'spec/rubocop/cop/ruby_interpolation_in_translation_spec.rb' - - 'spec/rubocop/cop/active_record_association_reload_spec.rb' - - 'spec/rubocop/cop/ban_catch_throw_spec.rb' - - 'spec/rubocop/cop/avoid_break_from_strong_memoize_spec.rb' - - 'spec/rubocop/cop/avoid_becomes_spec.rb' - - 'spec/rubocop/cop/qa/ambiguous_page_object_name_spec.rb' - - 'spec/rubocop/cop/qa/element_with_pattern_spec.rb' - - 'spec/rubocop/cop/inject_enterprise_edition_module_spec.rb' - - 'spec/rubocop/cop/avoid_keyword_arguments_in_sidekiq_workers_spec.rb' - - 'spec/rubocop/cop/default_scope_spec.rb' - 'spec/rubocop/cop/scalability/bulk_perform_with_context_spec.rb' - 'spec/rubocop/cop/scalability/idempotent_worker_spec.rb' - 'spec/rubocop/cop/scalability/cron_worker_context_spec.rb' diff --git a/Dangerfile b/Dangerfile index 103e38fdd33..34e0efa027a 100644 --- a/Dangerfile +++ b/Dangerfile @@ -3,10 +3,7 @@ require_relative 'tooling/gitlab_danger' require_relative 'tooling/danger/request_helper' -danger.import_plugin('danger/plugins/helper.rb') -danger.import_plugin('danger/plugins/roulette.rb') -danger.import_plugin('danger/plugins/changelog.rb') -danger.import_plugin('danger/plugins/sidekiq_queues.rb') +Dir["danger/plugins/*.rb"].sort.each { |f| danger.import_plugin(f) } return if helper.release_automation? diff --git a/app/assets/javascripts/pages/admin/runners/index.js b/app/assets/javascripts/pages/admin/runners/index.js index e60c6133c7c..1b373226664 100644 --- a/app/assets/javascripts/pages/admin/runners/index.js +++ b/app/assets/javascripts/pages/admin/runners/index.js @@ -1,6 +1,7 @@ import initFilteredSearch from '~/pages/search/init_filtered_search'; import AdminRunnersFilteredSearchTokenKeys from '~/filtered_search/admin_runners_filtered_search_token_keys'; import { FILTERED_SEARCH } from '~/pages/constants'; +import { initInstallRunner } from '~/pages/shared/mount_runner_instructions'; document.addEventListener('DOMContentLoaded', () => { initFilteredSearch({ @@ -8,4 +9,8 @@ document.addEventListener('DOMContentLoaded', () => { filteredSearchTokenKeys: AdminRunnersFilteredSearchTokenKeys, useDefaultState: true, }); + + if (gon?.features?.runnerInstructions) { + initInstallRunner(); + } }); diff --git a/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js b/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js index e8d8c985ade..3ce779975f7 100644 --- a/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js +++ b/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js @@ -4,6 +4,7 @@ import initFilteredSearch from '~/pages/search/init_filtered_search'; import GroupRunnersFilteredSearchTokenKeys from '~/filtered_search/group_runners_filtered_search_token_keys'; import { FILTERED_SEARCH } from '~/pages/constants'; import initSharedRunnersForm from '~/group_settings/mount_shared_runners'; +import { initInstallRunner } from '~/pages/shared/mount_runner_instructions'; document.addEventListener('DOMContentLoaded', () => { // Initialize expandable settings panels @@ -18,4 +19,8 @@ document.addEventListener('DOMContentLoaded', () => { initSharedRunnersForm(); initVariableList(); + + if (gon?.features?.runnerInstructions) { + initInstallRunner(); + } }); diff --git a/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js b/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js index 1321155b7ec..0eb40ff30f9 100644 --- a/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js +++ b/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js @@ -6,6 +6,7 @@ import initDeployFreeze from '~/deploy_freeze'; import initSettingsPipelinesTriggers from '~/ci_settings_pipeline_triggers'; import initSharedRunnersToggle from '~/projects/settings/mount_shared_runners_toggle'; import initArtifactsSettings from '~/artifacts_settings'; +import { initInstallRunner } from '~/pages/shared/mount_runner_instructions'; document.addEventListener('DOMContentLoaded', () => { // Initialize expandable settings panels @@ -39,4 +40,8 @@ document.addEventListener('DOMContentLoaded', () => { if (gon?.features?.vueifySharedRunnersToggle) { initSharedRunnersToggle(); } + + if (gon?.features?.runnerInstructions) { + initInstallRunner(); + } }); diff --git a/app/assets/javascripts/pages/shared/mount_runner_instructions.js b/app/assets/javascripts/pages/shared/mount_runner_instructions.js new file mode 100644 index 00000000000..51028e585b8 --- /dev/null +++ b/app/assets/javascripts/pages/shared/mount_runner_instructions.js @@ -0,0 +1,32 @@ +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; +import createDefaultClient from '~/lib/graphql'; +import InstallRunnerInstructions from '~/vue_shared/components/runner_instructions/runner_instructions.vue'; + +Vue.use(VueApollo); + +export function initInstallRunner(componentId = 'js-install-runner') { + const installRunnerEl = document.getElementById(componentId); + + if (installRunnerEl) { + const defaultClient = createDefaultClient(); + const { projectPath, groupPath } = installRunnerEl.dataset; + + const apolloProvider = new VueApollo({ + defaultClient, + }); + + // eslint-disable-next-line no-new + new Vue({ + el: installRunnerEl, + apolloProvider, + provide: { + projectPath, + groupPath, + }, + render(createElement) { + return createElement(InstallRunnerInstructions); + }, + }); + } +} diff --git a/app/assets/javascripts/pipelines/components/legacy_header_component.vue b/app/assets/javascripts/pipelines/components/legacy_header_component.vue deleted file mode 100644 index c7b72be36ad..00000000000 --- a/app/assets/javascripts/pipelines/components/legacy_header_component.vue +++ /dev/null @@ -1,132 +0,0 @@ - - diff --git a/app/assets/javascripts/pipelines/pipeline_details_bundle.js b/app/assets/javascripts/pipelines/pipeline_details_bundle.js index 1c6f0b3f036..5ee5b45aac9 100644 --- a/app/assets/javascripts/pipelines/pipeline_details_bundle.js +++ b/app/assets/javascripts/pipelines/pipeline_details_bundle.js @@ -2,12 +2,9 @@ import Vue from 'vue'; import { deprecatedCreateFlash as Flash } from '~/flash'; import Translate from '~/vue_shared/translate'; import { __ } from '~/locale'; -import { setUrlFragment, redirectTo } from '~/lib/utils/url_utility'; import PipelineGraphLegacy from './components/graph/graph_component_legacy.vue'; import createDagApp from './pipeline_details_dag'; import GraphBundleMixin from './mixins/graph_pipeline_bundle_mixin'; -import legacyPipelineHeader from './components/legacy_header_component.vue'; -import eventHub from './event_hub'; import TestReports from './components/test_reports/test_reports.vue'; import createTestReportsStore from './stores/test_reports'; import { reportToSentry } from './components/graph/utils'; @@ -59,58 +56,6 @@ const createLegacyPipelinesDetailApp = (mediator) => { }); }; -const createLegacyPipelineHeaderApp = (mediator) => { - if (!document.querySelector(SELECTORS.PIPELINE_HEADER)) { - return; - } - // eslint-disable-next-line no-new - new Vue({ - el: SELECTORS.PIPELINE_HEADER, - components: { - legacyPipelineHeader, - }, - data() { - return { - mediator, - }; - }, - created() { - eventHub.$on('headerPostAction', this.postAction); - eventHub.$on('headerDeleteAction', this.deleteAction); - }, - beforeDestroy() { - eventHub.$off('headerPostAction', this.postAction); - eventHub.$off('headerDeleteAction', this.deleteAction); - }, - errorCaptured(err, _vm, info) { - reportToSentry('pipeline_details_bundle_legacy', `error: ${err}, info: ${info}`); - }, - methods: { - postAction(path) { - this.mediator.service - .postAction(path) - .then(() => this.mediator.refreshPipeline()) - .catch(() => Flash(__('An error occurred while making the request.'))); - }, - deleteAction(path) { - this.mediator.stopPipelinePoll(); - this.mediator.service - .deleteAction(path) - .then(({ request }) => redirectTo(setUrlFragment(request.responseURL, 'delete_success'))) - .catch(() => Flash(__('An error occurred while deleting the pipeline.'))); - }, - }, - render(createElement) { - return createElement('legacy-pipeline-header', { - props: { - isLoading: this.mediator.state.isLoading, - pipeline: this.mediator.store.state.pipeline, - }, - }); - }, - }); -}; - const createTestDetails = () => { const el = document.querySelector(SELECTORS.PIPELINE_TESTS); const { summaryEndpoint, suiteEndpoint } = el?.dataset || {}; @@ -140,19 +85,6 @@ export default async function () { gon.features.graphqlPipelineDetails || gon.features.graphqlPipelineDetailsUsers; const { dataset } = document.querySelector(SELECTORS.PIPELINE_DETAILS); - let mediator; - - if (!gon.features.graphqlPipelineHeader || !canShowNewPipelineDetails) { - try { - const { default: PipelinesMediator } = await import( - /* webpackChunkName: 'PipelinesMediator' */ './pipeline_details_mediator' - ); - mediator = new PipelinesMediator({ endpoint: dataset.endpoint }); - mediator.fetchPipeline(); - } catch { - Flash(__('An error occurred while loading the pipeline.')); - } - } if (canShowNewPipelineDetails) { try { @@ -166,19 +98,21 @@ export default async function () { Flash(__('An error occurred while loading the pipeline.')); } } else { + const { default: PipelinesMediator } = await import( + /* webpackChunkName: 'PipelinesMediator' */ './pipeline_details_mediator' + ); + const mediator = new PipelinesMediator({ endpoint: dataset.endpoint }); + mediator.fetchPipeline(); + createLegacyPipelinesDetailApp(mediator); } - if (gon.features.graphqlPipelineHeader) { - try { - const { createPipelineHeaderApp } = await import( - /* webpackChunkName: 'createPipelineHeaderApp' */ './pipeline_details_header' - ); - createPipelineHeaderApp(SELECTORS.PIPELINE_HEADER); - } catch { - Flash(__('An error occurred while loading a section of this page.')); - } - } else { - createLegacyPipelineHeaderApp(mediator); + try { + const { createPipelineHeaderApp } = await import( + /* webpackChunkName: 'createPipelineHeaderApp' */ './pipeline_details_header' + ); + createPipelineHeaderApp(SELECTORS.PIPELINE_HEADER); + } catch { + Flash(__('An error occurred while loading a section of this page.')); } } diff --git a/app/assets/javascripts/registry/explorer/index.js b/app/assets/javascripts/registry/explorer/index.js index a3890ab5c42..0ddbe50441b 100644 --- a/app/assets/javascripts/registry/explorer/index.js +++ b/app/assets/javascripts/registry/explorer/index.js @@ -29,7 +29,14 @@ export default () => { return null; } - const { endpoint, expirationPolicy, isGroupPage, isAdmin, ...config } = el.dataset; + const { + endpoint, + expirationPolicy, + isGroupPage, + isAdmin, + showUnfinishedTagCleanupCallout, + ...config + } = el.dataset; // This is a mini state to help the breadcrumb have the correct name in the details page const breadCrumbState = Vue.observable({ @@ -57,6 +64,7 @@ export default () => { expirationPolicy: expirationPolicy ? JSON.parse(expirationPolicy) : undefined, isGroupPage: parseBoolean(isGroupPage), isAdmin: parseBoolean(isAdmin), + showUnfinishedTagCleanupCallout: parseBoolean(showUnfinishedTagCleanupCallout), }, /* eslint-disable @gitlab/require-i18n-strings */ dockerBuildCommand: `docker build -t ${config.repositoryUrl} .`, diff --git a/app/assets/javascripts/registry/explorer/pages/details.vue b/app/assets/javascripts/registry/explorer/pages/details.vue index 0894fd6fcfa..71c610b6003 100644 --- a/app/assets/javascripts/registry/explorer/pages/details.vue +++ b/app/assets/javascripts/registry/explorer/pages/details.vue @@ -3,6 +3,7 @@ import { GlKeysetPagination, GlResizeObserverDirective } from '@gitlab/ui'; import { GlBreakpointInstance } from '@gitlab/ui/dist/utils'; import createFlash from '~/flash'; import Tracking from '~/tracking'; +import axios from '~/lib/utils/axios_utils'; import { joinPaths } from '~/lib/utils/url_utility'; import DeleteAlert from '../components/details_page/delete_alert.vue'; import PartialCleanupAlert from '../components/details_page/partial_cleanup_alert.vue'; @@ -68,7 +69,7 @@ export default { isMobile: false, mutationLoading: false, deleteAlertType: null, - dismissPartialCleanupWarning: false, + hidePartialCleanupWarning: false, }; }, computed: { @@ -86,8 +87,9 @@ export default { }, showPartialCleanupWarning() { return ( + this.config.showUnfinishedTagCleanupCallout && this.image?.expirationPolicyCleanupStatus === UNFINISHED_STATUS && - !this.dismissPartialCleanupWarning + !this.hidePartialCleanupWarning ); }, tracking() { @@ -168,6 +170,12 @@ export default { }); } }, + dismissPartialCleanupWarning() { + this.hidePartialCleanupWarning = true; + axios.post(this.config.userCalloutsPath, { + feature_name: this.config.userCalloutId, + }); + }, }, }; @@ -185,7 +193,7 @@ export default { v-if="showPartialCleanupWarning" :run-cleanup-policies-help-page-path="config.runCleanupPoliciesHelpPagePath" :cleanup-policies-help-page-path="config.cleanupPoliciesHelpPagePath" - @dismiss="dismissPartialCleanupWarning = true" + @dismiss="dismissPartialCleanupWarning" /> diff --git a/app/assets/javascripts/vue_shared/components/runner_instructions/constants.js b/app/assets/javascripts/vue_shared/components/runner_instructions/constants.js new file mode 100644 index 00000000000..facace0d809 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/runner_instructions/constants.js @@ -0,0 +1,18 @@ +import { s__ } from '~/locale'; + +export const PLATFORMS_WITHOUT_ARCHITECTURES = ['docker', 'kubernetes']; + +export const INSTRUCTIONS_PLATFORMS_WITHOUT_ARCHITECTURES = { + docker: { + instructions: s__( + 'Runners|To install Runner in a container follow the instructions described in the GitLab documentation', + ), + link: 'https://docs.gitlab.com/runner/install/docker.html', + }, + kubernetes: { + instructions: s__( + 'Runners|To install Runner in Kubernetes follow the instructions described in the GitLab documentation.', + ), + link: 'https://docs.gitlab.com/runner/install/kubernetes.html', + }, +}; diff --git a/app/assets/javascripts/vue_shared/components/runner_instructions/graphql/queries/get_runner_platforms.query.graphql b/app/assets/javascripts/vue_shared/components/runner_instructions/graphql/queries/get_runner_platforms.query.graphql new file mode 100644 index 00000000000..ff0626167a9 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/runner_instructions/graphql/queries/get_runner_platforms.query.graphql @@ -0,0 +1,20 @@ +query getRunnerPlatforms($projectPath: ID!, $groupPath: ID!) { + runnerPlatforms { + nodes { + name + humanReadableName + architectures { + nodes { + name + downloadLocation + } + } + } + } + project(fullPath: $projectPath) { + id + } + group(fullPath: $groupPath) { + id + } +} diff --git a/app/assets/javascripts/vue_shared/components/runner_instructions/graphql/queries/get_runner_setup.query.graphql b/app/assets/javascripts/vue_shared/components/runner_instructions/graphql/queries/get_runner_setup.query.graphql new file mode 100644 index 00000000000..643c1991807 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/runner_instructions/graphql/queries/get_runner_setup.query.graphql @@ -0,0 +1,16 @@ +query runnerSetupInstructions( + $platform: String! + $architecture: String! + $projectId: ID! + $groupId: ID! +) { + runnerSetup( + platform: $platform + architecture: $architecture + projectId: $projectId + groupId: $groupId + ) { + installInstructions + registerInstructions + } +} diff --git a/app/assets/javascripts/vue_shared/components/runner_instructions/runner_instructions.vue b/app/assets/javascripts/vue_shared/components/runner_instructions/runner_instructions.vue new file mode 100644 index 00000000000..1d6db576942 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/runner_instructions/runner_instructions.vue @@ -0,0 +1,261 @@ + + diff --git a/app/assets/stylesheets/_page_specific_files.scss b/app/assets/stylesheets/_page_specific_files.scss index 42d15635566..20ff78d32d3 100644 --- a/app/assets/stylesheets/_page_specific_files.scss +++ b/app/assets/stylesheets/_page_specific_files.scss @@ -1,4 +1,3 @@ -@import './pages/admin'; @import './pages/branches'; @import './pages/ci_projects'; @import './pages/clusters'; diff --git a/app/assets/stylesheets/page_bundles/admin/application_settings_metrics_and_profiling.scss b/app/assets/stylesheets/page_bundles/admin/application_settings_metrics_and_profiling.scss new file mode 100644 index 00000000000..41bb6d107f1 --- /dev/null +++ b/app/assets/stylesheets/page_bundles/admin/application_settings_metrics_and_profiling.scss @@ -0,0 +1,3 @@ +.usage-data { + max-height: 400px; +} diff --git a/app/assets/stylesheets/page_bundles/admin/jobs_index.scss b/app/assets/stylesheets/page_bundles/admin/jobs_index.scss new file mode 100644 index 00000000000..7844cae5f87 --- /dev/null +++ b/app/assets/stylesheets/page_bundles/admin/jobs_index.scss @@ -0,0 +1,5 @@ +.admin-builds-table { + td:last-child { + min-width: 120px; + } +} diff --git a/app/assets/stylesheets/pages/admin.scss b/app/assets/stylesheets/pages/admin.scss deleted file mode 100644 index 34834b9a4a9..00000000000 --- a/app/assets/stylesheets/pages/admin.scss +++ /dev/null @@ -1,11 +0,0 @@ -.usage-data { - max-height: 400px; -} - -[data-page='admin:jobs:index'] { - .admin-builds-table { - td:last-child { - min-width: 120px; - } - } -} diff --git a/app/controllers/admin/runners_controller.rb b/app/controllers/admin/runners_controller.rb index 576b148fbff..91cb33dd4c6 100644 --- a/app/controllers/admin/runners_controller.rb +++ b/app/controllers/admin/runners_controller.rb @@ -4,6 +4,9 @@ class Admin::RunnersController < Admin::ApplicationController include RunnerSetupScripts before_action :runner, except: [:index, :tag_list, :runner_setup_scripts] + before_action do + push_frontend_feature_flag(:runner_instructions, default_enabled: :yaml) + end feature_category :continuous_integration diff --git a/app/controllers/groups/settings/ci_cd_controller.rb b/app/controllers/groups/settings/ci_cd_controller.rb index 723edc4b7e9..a45f3f18976 100644 --- a/app/controllers/groups/settings/ci_cd_controller.rb +++ b/app/controllers/groups/settings/ci_cd_controller.rb @@ -9,6 +9,9 @@ module Groups before_action :authorize_admin_group! before_action :authorize_update_max_artifacts_size!, only: [:update] before_action :define_variables, only: [:show] + before_action do + push_frontend_feature_flag(:runner_instructions, @group, default_enabled: :yaml) + end feature_category :continuous_integration diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index 21ff23c6440..052a7aca069 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -14,7 +14,6 @@ class Projects::PipelinesController < Projects::ApplicationController before_action do push_frontend_feature_flag(:pipelines_security_report_summary, project) push_frontend_feature_flag(:new_pipeline_form, project, default_enabled: true) - push_frontend_feature_flag(:graphql_pipeline_header, project, type: :development, default_enabled: false) push_frontend_feature_flag(:graphql_pipeline_details, project, type: :development, default_enabled: :yaml) push_frontend_feature_flag(:graphql_pipeline_details_users, current_user, type: :development, default_enabled: :yaml) push_frontend_feature_flag(:new_pipeline_form_prefilled_vars, project, type: :development, default_enabled: true) diff --git a/app/controllers/projects/settings/ci_cd_controller.rb b/app/controllers/projects/settings/ci_cd_controller.rb index 31533dfeea0..7586c74431a 100644 --- a/app/controllers/projects/settings/ci_cd_controller.rb +++ b/app/controllers/projects/settings/ci_cd_controller.rb @@ -12,6 +12,7 @@ module Projects before_action do push_frontend_feature_flag(:ajax_new_deploy_token, @project) push_frontend_feature_flag(:vueify_shared_runners_toggle, @project) + push_frontend_feature_flag(:runner_instructions, @project, default_enabled: :yaml) end helper_method :highlight_badge diff --git a/app/graphql/mutations/alert_management/http_integration/create.rb b/app/graphql/mutations/alert_management/http_integration/create.rb index e1531148a76..2d7bffb4333 100644 --- a/app/graphql/mutations/alert_management/http_integration/create.rb +++ b/app/graphql/mutations/alert_management/http_integration/create.rb @@ -4,7 +4,7 @@ module Mutations module AlertManagement module HttpIntegration class Create < HttpIntegrationBase - include ResolvesProject + include FindsProject graphql_name 'HttpIntegrationCreate' @@ -21,7 +21,7 @@ module Mutations description: 'Whether the integration is receiving alerts.' def resolve(args) - project = authorized_find!(full_path: args[:project_path]) + project = authorized_find!(args[:project_path]) response ::AlertManagement::HttpIntegrations::CreateService.new( project, @@ -29,12 +29,6 @@ module Mutations http_integration_params(project, args) ).execute end - - private - - def find_object(full_path:) - resolve_project(full_path: full_path) - end end end end diff --git a/app/graphql/mutations/alert_management/prometheus_integration/create.rb b/app/graphql/mutations/alert_management/prometheus_integration/create.rb index c676cde90b4..87e6bc46937 100644 --- a/app/graphql/mutations/alert_management/prometheus_integration/create.rb +++ b/app/graphql/mutations/alert_management/prometheus_integration/create.rb @@ -4,7 +4,7 @@ module Mutations module AlertManagement module PrometheusIntegration class Create < PrometheusIntegrationBase - include ResolvesProject + include FindsProject graphql_name 'PrometheusIntegrationCreate' @@ -21,7 +21,7 @@ module Mutations description: 'Endpoint at which prometheus can be queried.' def resolve(args) - project = authorized_find!(full_path: args[:project_path]) + project = authorized_find!(args[:project_path]) return integration_exists if project.prometheus_service @@ -37,10 +37,6 @@ module Mutations private - def find_object(full_path:) - resolve_project(full_path: full_path) - end - def integration_exists response(nil, message: _('Multiple Prometheus integrations are not supported')) end diff --git a/app/graphql/mutations/branches/create.rb b/app/graphql/mutations/branches/create.rb index 9fe9bef5403..6354976f1ea 100644 --- a/app/graphql/mutations/branches/create.rb +++ b/app/graphql/mutations/branches/create.rb @@ -3,7 +3,7 @@ module Mutations module Branches class Create < BaseMutation - include ResolvesProject + include FindsProject graphql_name 'CreateBranch' @@ -28,7 +28,7 @@ module Mutations authorize :push_code def resolve(project_path:, name:, ref:) - project = authorized_find!(full_path: project_path) + project = authorized_find!(project_path) context.scoped_set!(:branch_project, project) @@ -40,12 +40,6 @@ module Mutations errors: Array.wrap(result[:message]) } end - - private - - def find_object(full_path:) - resolve_project(full_path: full_path) - end end end end diff --git a/app/graphql/mutations/commits/create.rb b/app/graphql/mutations/commits/create.rb index ae14401558b..84933fee5d2 100644 --- a/app/graphql/mutations/commits/create.rb +++ b/app/graphql/mutations/commits/create.rb @@ -3,7 +3,7 @@ module Mutations module Commits class Create < BaseMutation - include ResolvesProject + include FindsProject graphql_name 'CommitCreate' @@ -37,7 +37,7 @@ module Mutations authorize :push_code def resolve(project_path:, branch:, message:, actions:, **args) - project = authorized_find!(full_path: project_path) + project = authorized_find!(project_path) attributes = { commit_message: message, @@ -53,12 +53,6 @@ module Mutations errors: Array.wrap(result[:message]) } end - - private - - def find_object(full_path:) - resolve_project(full_path: full_path) - end end end end diff --git a/app/graphql/mutations/container_expiration_policies/update.rb b/app/graphql/mutations/container_expiration_policies/update.rb index 37cf2fa6bf3..f61d852bb6c 100644 --- a/app/graphql/mutations/container_expiration_policies/update.rb +++ b/app/graphql/mutations/container_expiration_policies/update.rb @@ -3,7 +3,7 @@ module Mutations module ContainerExpirationPolicies class Update < Mutations::BaseMutation - include ResolvesProject + include FindsProject graphql_name 'UpdateContainerExpirationPolicy' @@ -50,7 +50,7 @@ module Mutations description: 'The container expiration policy after mutation.' def resolve(project_path:, **args) - project = authorized_find!(full_path: project_path) + project = authorized_find!(project_path) result = ::ContainerExpirationPolicies::UpdateService .new(container: project, current_user: current_user, params: args) @@ -61,12 +61,6 @@ module Mutations errors: result.errors } end - - private - - def find_object(full_path:) - resolve_project(full_path: full_path) - end end end end diff --git a/app/graphql/mutations/issues/create.rb b/app/graphql/mutations/issues/create.rb index 18b80ff1736..37fddd92832 100644 --- a/app/graphql/mutations/issues/create.rb +++ b/app/graphql/mutations/issues/create.rb @@ -3,7 +3,7 @@ module Mutations module Issues class Create < BaseMutation - include ResolvesProject + include FindsProject graphql_name 'CreateIssue' authorize :create_issue @@ -70,7 +70,7 @@ module Mutations end def resolve(project_path:, **attributes) - project = authorized_find!(full_path: project_path) + project = authorized_find!(project_path) params = build_create_issue_params(attributes.merge(author_id: current_user.id)) issue = ::Issues::CreateService.new(project, current_user, params).execute @@ -98,10 +98,6 @@ module Mutations def mutually_exclusive_label_args [:labels, :label_ids] end - - def find_object(full_path:) - resolve_project(full_path: full_path) - end end end end diff --git a/app/graphql/mutations/jira_import/import_users.rb b/app/graphql/mutations/jira_import/import_users.rb index 616ef390657..af2bb18161f 100644 --- a/app/graphql/mutations/jira_import/import_users.rb +++ b/app/graphql/mutations/jira_import/import_users.rb @@ -3,10 +3,12 @@ module Mutations module JiraImport class ImportUsers < BaseMutation - include ResolvesProject + include FindsProject graphql_name 'JiraImportUsers' + authorize :admin_project + field :jira_users, [Types::JiraUserType], null: true, @@ -20,7 +22,7 @@ module Mutations description: 'The index of the record the import should started at, default 0 (50 records returned).' def resolve(project_path:, start_at: 0) - project = authorized_find!(full_path: project_path) + project = authorized_find!(project_path) service_response = ::JiraImport::UsersImporter.new(context[:current_user], project, start_at.to_i).execute @@ -29,16 +31,6 @@ module Mutations errors: service_response.errors } end - - private - - def find_object(full_path:) - resolve_project(full_path: full_path) - end - - def authorized_resource?(project) - Ability.allowed?(context[:current_user], :admin_project, project) - end end end end diff --git a/app/graphql/mutations/jira_import/start.rb b/app/graphql/mutations/jira_import/start.rb index 3d50ebde13a..e31aaf53a09 100644 --- a/app/graphql/mutations/jira_import/start.rb +++ b/app/graphql/mutations/jira_import/start.rb @@ -3,10 +3,12 @@ module Mutations module JiraImport class Start < BaseMutation - include ResolvesProject + include FindsProject graphql_name 'JiraImportStart' + authorize :admin_project + field :jira_import, Types::JiraImportType, null: true, @@ -27,7 +29,7 @@ module Mutations description: 'The mapping of Jira to GitLab users.' def resolve(project_path:, jira_project_key:, users_mapping:) - project = authorized_find!(full_path: project_path) + project = authorized_find!(project_path) mapping = users_mapping.to_ary.map { |map| map.to_hash } service_response = ::JiraImport::StartImportService @@ -40,16 +42,6 @@ module Mutations errors: service_response.errors } end - - private - - def find_object(full_path:) - resolve_project(full_path: full_path) - end - - def authorized_resource?(project) - Ability.allowed?(context[:current_user], :admin_project, project) - end end end end diff --git a/app/graphql/mutations/merge_requests/create.rb b/app/graphql/mutations/merge_requests/create.rb index 64fa8417e50..9ac8f70be95 100644 --- a/app/graphql/mutations/merge_requests/create.rb +++ b/app/graphql/mutations/merge_requests/create.rb @@ -3,7 +3,7 @@ module Mutations module MergeRequests class Create < BaseMutation - include ResolvesProject + include FindsProject graphql_name 'MergeRequestCreate' @@ -39,7 +39,7 @@ module Mutations authorize :create_merge_request_from def resolve(project_path:, **attributes) - project = authorized_find!(full_path: project_path) + project = authorized_find!(project_path) params = attributes.merge(author_id: current_user.id) merge_request = ::MergeRequests::CreateService.new(project, current_user, params).execute @@ -49,12 +49,6 @@ module Mutations errors: errors_on_object(merge_request) } end - - private - - def find_object(full_path:) - resolve_project(full_path: full_path) - end end end end diff --git a/app/graphql/mutations/releases/base.rb b/app/graphql/mutations/releases/base.rb index dd1724fe320..610e9cd9cde 100644 --- a/app/graphql/mutations/releases/base.rb +++ b/app/graphql/mutations/releases/base.rb @@ -3,17 +3,11 @@ module Mutations module Releases class Base < BaseMutation - include ResolvesProject + include FindsProject argument :project_path, GraphQL::ID_TYPE, required: true, description: 'Full path of the project the release is associated with.' - - private - - def find_object(full_path:) - resolve_project(full_path: full_path) - end end end end diff --git a/app/graphql/mutations/releases/create.rb b/app/graphql/mutations/releases/create.rb index 91ac256033e..914c1302094 100644 --- a/app/graphql/mutations/releases/create.rb +++ b/app/graphql/mutations/releases/create.rb @@ -41,7 +41,7 @@ module Mutations authorize :create_release def resolve(project_path:, assets: nil, **scalars) - project = authorized_find!(full_path: project_path) + project = authorized_find!(project_path) params = { **scalars, diff --git a/app/graphql/mutations/releases/delete.rb b/app/graphql/mutations/releases/delete.rb index e887b702cce..020c9133b58 100644 --- a/app/graphql/mutations/releases/delete.rb +++ b/app/graphql/mutations/releases/delete.rb @@ -17,7 +17,7 @@ module Mutations authorize :destroy_release def resolve(project_path:, tag:) - project = authorized_find!(full_path: project_path) + project = authorized_find!(project_path) params = { tag: tag }.with_indifferent_access diff --git a/app/graphql/mutations/releases/update.rb b/app/graphql/mutations/releases/update.rb index dff743254bd..35f2a7b3d4b 100644 --- a/app/graphql/mutations/releases/update.rb +++ b/app/graphql/mutations/releases/update.rb @@ -47,7 +47,7 @@ module Mutations end def resolve(project_path:, **scalars) - project = authorized_find!(full_path: project_path) + project = authorized_find!(project_path) params = scalars.with_indifferent_access diff --git a/app/graphql/mutations/security/ci_configuration/configure_sast.rb b/app/graphql/mutations/security/ci_configuration/configure_sast.rb index 6cb3704a19b..e4a3f815396 100644 --- a/app/graphql/mutations/security/ci_configuration/configure_sast.rb +++ b/app/graphql/mutations/security/ci_configuration/configure_sast.rb @@ -4,7 +4,7 @@ module Mutations module Security module CiConfiguration class ConfigureSast < BaseMutation - include ResolvesProject + include FindsProject graphql_name 'ConfigureSast' @@ -25,7 +25,7 @@ module Mutations authorize :push_code def resolve(project_path:, configuration:) - project = authorized_find!(full_path: project_path) + project = authorized_find!(project_path) result = ::Security::CiConfiguration::SastCreateService.new(project, current_user, configuration).execute prepare_response(result) @@ -33,10 +33,6 @@ module Mutations private - def find_object(full_path:) - resolve_project(full_path: full_path) - end - def prepare_response(result) { status: result[:status], diff --git a/app/helpers/user_callouts_helper.rb b/app/helpers/user_callouts_helper.rb index a06a31ddf32..f55a6c3c9e5 100644 --- a/app/helpers/user_callouts_helper.rb +++ b/app/helpers/user_callouts_helper.rb @@ -11,6 +11,7 @@ module UserCalloutsHelper CUSTOMIZE_HOMEPAGE = 'customize_homepage' FEATURE_FLAGS_NEW_VERSION = 'feature_flags_new_version' REGISTRATION_ENABLED_CALLOUT = 'registration_enabled_callout' + UNFINISHED_TAG_CLEANUP_CALLOUT = 'unfinished_tag_cleanup_callout' def show_admin_integrations_moved? !user_dismissed?(ADMIN_INTEGRATIONS_MOVED) @@ -56,6 +57,10 @@ module UserCalloutsHelper !user_dismissed?(FEATURE_FLAGS_NEW_VERSION) end + def show_unfinished_tag_cleanup_callout? + !user_dismissed?(UNFINISHED_TAG_CLEANUP_CALLOUT) + end + def show_registration_enabled_user_callout? !Gitlab.com? && current_user&.admin? && diff --git a/app/models/pages/lookup_path.rb b/app/models/pages/lookup_path.rb index b7e27f460a8..fa2d7bb316c 100644 --- a/app/models/pages/lookup_path.rb +++ b/app/models/pages/lookup_path.rb @@ -5,6 +5,7 @@ module Pages include Gitlab::Utils::StrongMemoize LegacyStorageDisabledError = Class.new(::StandardError) + MIGRATED_FILE_NAME = "_migrated.zip" def initialize(project, trim_prefix: nil, domain: nil) @project = project @@ -54,6 +55,8 @@ module Pages return if deployment.file.file_storage? && !Feature.enabled?(:pages_serve_with_zip_file_protocol, project) + return if deployment.file.filename == MIGRATED_FILE_NAME && !Feature.enabled?(:pages_serve_from_migrated_zip, project) + global_id = ::Gitlab::GlobalId.build(deployment, id: deployment.id).to_s { diff --git a/app/models/user_callout.rb b/app/models/user_callout.rb index ad5651f9439..6e57673bafc 100644 --- a/app/models/user_callout.rb +++ b/app/models/user_callout.rb @@ -27,7 +27,8 @@ class UserCallout < ApplicationRecord customize_homepage: 23, feature_flags_new_version: 24, registration_enabled_callout: 25, - new_user_signups_cap_reached: 26 # EE-only + new_user_signups_cap_reached: 26, # EE-only + unfinished_tag_cleanup_callout: 27 } validates :user, presence: true diff --git a/app/services/git/wiki_push_service.rb b/app/services/git/wiki_push_service.rb index 87e2be858c0..99659bc8ab2 100644 --- a/app/services/git/wiki_push_service.rb +++ b/app/services/git/wiki_push_service.rb @@ -16,6 +16,7 @@ module Git wiki.after_post_receive process_changes + perform_housekeeping if Feature.enabled?(:wiki_housekeeping, wiki.container) end private @@ -72,6 +73,14 @@ module Git def default_branch_changes @default_branch_changes ||= changes.select { |change| on_default_branch?(change) } end + + def perform_housekeeping + housekeeping = Repositories::HousekeepingService.new(wiki) + housekeeping.increment! + housekeeping.execute if housekeeping.needed? + rescue Repositories::HousekeepingService::LeaseTaken + # no-op + end end end diff --git a/app/views/admin/application_settings/metrics_and_profiling.html.haml b/app/views/admin/application_settings/metrics_and_profiling.html.haml index 4959e596148..113ff20e910 100644 --- a/app/views/admin/application_settings/metrics_and_profiling.html.haml +++ b/app/views/admin/application_settings/metrics_and_profiling.html.haml @@ -1,3 +1,5 @@ +- add_page_specific_style 'page_bundles/admin/application_settings_metrics_and_profiling' + - breadcrumb_title _("Metrics and profiling") - page_title _("Metrics and profiling") - @content_class = "limit-container-width" unless fluid_layout diff --git a/app/views/admin/jobs/index.html.haml b/app/views/admin/jobs/index.html.haml index ce377eeea54..670628f7463 100644 --- a/app/views/admin/jobs/index.html.haml +++ b/app/views/admin/jobs/index.html.haml @@ -1,4 +1,5 @@ - add_page_specific_style 'page_bundles/ci_status' +- add_page_specific_style 'page_bundles/admin/jobs_index' - breadcrumb_title _("Jobs") - page_title _("Jobs") diff --git a/app/views/admin/runners/index.html.haml b/app/views/admin/runners/index.html.haml index 9f19d3f5d4e..5ddb03bcc04 100644 --- a/app/views/admin/runners/index.html.haml +++ b/app/views/admin/runners/index.html.haml @@ -39,7 +39,9 @@ = render partial: 'ci/runner/how_to_setup_runner', locals: { registration_token: Gitlab::CurrentSettings.runners_registration_token, type: 'shared', - reset_token_url: reset_registration_token_admin_application_settings_path } + reset_token_url: reset_registration_token_admin_application_settings_path, + project_path: '', + group_path: '' } .row .col-sm-9 diff --git a/app/views/ci/runner/_how_to_setup_runner.html.haml b/app/views/ci/runner/_how_to_setup_runner.html.haml index fc3d5360f9b..6c2e4c69d83 100644 --- a/app/views/ci/runner/_how_to_setup_runner.html.haml +++ b/app/views/ci/runner/_how_to_setup_runner.html.haml @@ -21,3 +21,5 @@ = button_to _("Reset registration token"), reset_token_url, method: :put, class: 'gl-button btn btn-default', data: { confirm: _("Are you sure you want to reset the registration token?") } + +#js-install-runner{ data: { project_path: project_path, group_path: group_path } } diff --git a/app/views/groups/registry/repositories/index.html.haml b/app/views/groups/registry/repositories/index.html.haml index 6d0a3e03019..4f4b6c1089c 100644 --- a/app/views/groups/registry/repositories/index.html.haml +++ b/app/views/groups/registry/repositories/index.html.haml @@ -17,4 +17,7 @@ is_group_page: "true", "group_path": @group.full_path, "gid_prefix": container_repository_gid_prefix, - character_error: @character_error.to_s } } + character_error: @character_error.to_s, + user_callouts_path: user_callouts_path, + user_callout_id: UserCalloutsHelper::UNFINISHED_TAG_CLEANUP_CALLOUT, + show_unfinished_tag_cleanup_callout: show_unfinished_tag_cleanup_callout?.to_s } } diff --git a/app/views/groups/runners/_group_runners.html.haml b/app/views/groups/runners/_group_runners.html.haml index 944ef3435c1..f60cdc9f8da 100644 --- a/app/views/groups/runners/_group_runners.html.haml +++ b/app/views/groups/runners/_group_runners.html.haml @@ -17,5 +17,7 @@ = render partial: 'ci/runner/how_to_setup_runner', locals: { registration_token: @group.runners_token, type: 'group', - reset_token_url: reset_registration_token_group_settings_ci_cd_path } + reset_token_url: reset_registration_token_group_settings_ci_cd_path, + project_path: '', + group_path: @group.path } %br diff --git a/app/views/projects/registry/repositories/index.html.haml b/app/views/projects/registry/repositories/index.html.haml index 97bc366544f..93e94928110 100644 --- a/app/views/projects/registry/repositories/index.html.haml +++ b/app/views/projects/registry/repositories/index.html.haml @@ -19,4 +19,7 @@ "project_path": @project.full_path, "gid_prefix": container_repository_gid_prefix, "is_admin": current_user&.admin.to_s, - character_error: @character_error.to_s } } + character_error: @character_error.to_s, + user_callouts_path: user_callouts_path, + user_callout_id: UserCalloutsHelper::UNFINISHED_TAG_CLEANUP_CALLOUT, + show_unfinished_tag_cleanup_callout: show_unfinished_tag_cleanup_callout?.to_s, } } diff --git a/app/views/projects/runners/_specific_runners.html.haml b/app/views/projects/runners/_specific_runners.html.haml index 3e325b80efd..88895634990 100644 --- a/app/views/projects/runners/_specific_runners.html.haml +++ b/app/views/projects/runners/_specific_runners.html.haml @@ -9,9 +9,11 @@ clusters_path: project_clusters_path(@project) } %hr = render partial: 'ci/runner/how_to_setup_runner', - locals: { registration_token: @project.runners_token, - type: 'specific', - reset_token_url: reset_registration_token_namespace_project_settings_ci_cd_path } + locals: { registration_token: @project.runners_token, + type: 'specific', + reset_token_url: reset_registration_token_namespace_project_settings_ci_cd_path, + project_path: @project.path_with_namespace, + group_path: '' } %hr diff --git a/app/workers/projects/git_garbage_collect_worker.rb b/app/workers/projects/git_garbage_collect_worker.rb index 7c7c7f27a6b..dc22c114115 100644 --- a/app/workers/projects/git_garbage_collect_worker.rb +++ b/app/workers/projects/git_garbage_collect_worker.rb @@ -7,6 +7,17 @@ module Projects private + # At the moment this was added, the default key was like this. + # With the addition of wikis to housekeeping, this will bring a + # problem because the wiki for project 1 will have the same + # lease key as project 1. + # + # In the `GitGarbageCollectMethods` we namespaced the resource, + # giving us the option to have different resources. Nevertheless, + # we kept this override in order for backward compatibility and avoid + # starting all projects from scratch. + # + # See https://gitlab.com/gitlab-org/gitlab/-/issues/299903 override :default_lease_key def default_lease_key(task, resource) "git_gc:#{task}:#{resource.id}" diff --git a/changelogs/unreleased/291048-allow-unfinished-tag-cleanup-alert-to-be-permanently-dismissible-f.yml b/changelogs/unreleased/291048-allow-unfinished-tag-cleanup-alert-to-be-permanently-dismissible-f.yml new file mode 100644 index 00000000000..a01a8f0f273 --- /dev/null +++ b/changelogs/unreleased/291048-allow-unfinished-tag-cleanup-alert-to-be-permanently-dismissible-f.yml @@ -0,0 +1,5 @@ +--- +title: Add callout disabling feature to cleanup policy alert +merge_request: 52327 +author: +type: changed diff --git a/changelogs/unreleased/jivanvl-remove-graphql-pipeline-header-ff.yml b/changelogs/unreleased/jivanvl-remove-graphql-pipeline-header-ff.yml new file mode 100644 index 00000000000..b17a6242dd1 --- /dev/null +++ b/changelogs/unreleased/jivanvl-remove-graphql-pipeline-header-ff.yml @@ -0,0 +1,5 @@ +--- +title: Remove graphql_pipeline_header feature flag +merge_request: 52247 +author: +type: other diff --git a/changelogs/unreleased/kassio-bulkimports-group-labels-pipeline.yml b/changelogs/unreleased/kassio-bulkimports-group-labels-pipeline.yml new file mode 100644 index 00000000000..75a3b40cc00 --- /dev/null +++ b/changelogs/unreleased/kassio-bulkimports-group-labels-pipeline.yml @@ -0,0 +1,5 @@ +--- +title: 'BulkImports: Import Group Labels' +merge_request: 52260 +author: +type: added diff --git a/config/application.rb b/config/application.rb index 341cc36cbdb..a2823b88e98 100644 --- a/config/application.rb +++ b/config/application.rb @@ -176,6 +176,8 @@ module Gitlab config.assets.precompile << "notify.css" config.assets.precompile << "mailers/*.css" config.assets.precompile << "page_bundles/_mixins_and_variables_and_functions.css" + config.assets.precompile << "page_bundles/admin/application_settings_metrics_and_profiling.css" + config.assets.precompile << "page_bundles/admin/jobs_index.css" config.assets.precompile << "page_bundles/alert_management_details.css" config.assets.precompile << "page_bundles/alert_management_settings.css" config.assets.precompile << "page_bundles/boards.css" diff --git a/config/feature_flags/development/graphql_pipeline_header.yml b/config/feature_flags/development/pages_serve_from_migrated_zip.yml similarity index 62% rename from config/feature_flags/development/graphql_pipeline_header.yml rename to config/feature_flags/development/pages_serve_from_migrated_zip.yml index 1d400c17452..18b912215b6 100644 --- a/config/feature_flags/development/graphql_pipeline_header.yml +++ b/config/feature_flags/development/pages_serve_from_migrated_zip.yml @@ -1,8 +1,8 @@ --- -name: graphql_pipeline_header -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/39494 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/254235 -milestone: '13.5' +name: pages_serve_from_migrated_zip +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/52573 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/300021 +milestone: '13.9' type: development -group: group::pipeline authoring +group: group::release default_enabled: false diff --git a/config/feature_flags/development/runner_instructions.yml b/config/feature_flags/development/runner_instructions.yml new file mode 100644 index 00000000000..f3a61adfde3 --- /dev/null +++ b/config/feature_flags/development/runner_instructions.yml @@ -0,0 +1,8 @@ +--- +name: runner_instructions +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/51014 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/296818 +milestone: '13.9' +type: development +group: group::"continuous integration" +default_enabled: false diff --git a/config/feature_flags/development/wiki_housekeeping.yml b/config/feature_flags/development/wiki_housekeeping.yml new file mode 100644 index 00000000000..aeff94b471b --- /dev/null +++ b/config/feature_flags/development/wiki_housekeeping.yml @@ -0,0 +1,8 @@ +--- +name: wiki_housekeeping +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/51576 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/299343 +milestone: '13.9' +type: development +group: group::editor +default_enabled: false diff --git a/danger/changelog/Dangerfile b/danger/changelog/Dangerfile index 06593a04093..c8474157fa5 100644 --- a/danger/changelog/Dangerfile +++ b/danger/changelog/Dangerfile @@ -35,7 +35,7 @@ def check_changelog_yaml(path) elsif yaml["merge_request"] != gitlab.mr_json["iid"] && !cherry_pick_against_stable_branch fail "Merge request ID was not set to #{gitlab.mr_json["iid"]}! #{SEE_DOC}" end -rescue Psych::SyntaxError, Psych::DisallowedClass, Psych::BadAlias +rescue Psych::Exception # YAML could not be parsed, fail the build. fail "#{gitlab.html_link(path)} isn't valid YAML! #{SEE_DOC}" rescue StandardError => e diff --git a/danger/feature_flag/Dangerfile b/danger/feature_flag/Dangerfile new file mode 100644 index 00000000000..089ee2dc50b --- /dev/null +++ b/danger/feature_flag/Dangerfile @@ -0,0 +1,57 @@ +# frozen_string_literal: true +# rubocop:disable Style/SignalException + +SEE_DOC = "See the [feature flag documentation](https://docs.gitlab.com/ee/development/feature_flags/development.html#feature-flag-definition-and-validation)." + +SUGGEST_MR_COMMENT = <<~SUGGEST_COMMENT +```suggestion +group: "%s" +``` + +#{SEE_DOC} +SUGGEST_COMMENT + +def check_feature_flag_yaml(feature_flag) + mr_group_label = helper.group_label(gitlab.mr_labels) + + if feature_flag.group.nil? + message_for_feature_flag_missing_group!(feature_flag: feature_flag, mr_group_label: mr_group_label) + else + message_for_feature_flag_with_group!(feature_flag: feature_flag, mr_group_label: mr_group_label) + end +rescue Psych::Exception + # YAML could not be parsed, fail the build. + fail "#{gitlab.html_link(feature_flag.path)} isn't valid YAML! #{SEE_DOC}" +rescue StandardError => e + warn "There was a problem trying to check the Feature Flag file. Exception: #{e.class.name} - #{e.message}" +end + +def message_for_feature_flag_missing_group!(feature_flag:, mr_group_label:) + if mr_group_label.nil? + warn "Consider setting `group` in #{gitlab.html_link(feature_flag.path)}. #{SEE_DOC}" + else + mr_line = feature_flag.raw.lines.find_index("group:\n") + + if mr_line + markdown(format(SUGGEST_MR_COMMENT, group: mr_group_label), file: feature_flag.path, line: mr_line.succ) + else + warn %(Consider setting `group: "#{mr_group_label}"` in #{gitlab.html_link(feature_flag.path)}. #{SEE_DOC}) + end + end +end + +def message_for_feature_flag_with_group!(feature_flag:, mr_group_label:) + return if feature_flag.group_match_mr_label?(mr_group_label) + + if mr_group_label.nil? + gitlab.api.update_merge_request(gitlab.mr_json['project_id'], + gitlab.mr_json['iid'], + add_labels: feature_flag.group) + else + fail %(`group` is set to ~"#{feature_flag.group}" in #{gitlab.html_link(feature_flag.path)}, which does not match ~"#{mr_group_label}" set on the MR!) + end +end + +feature_flag.feature_flag_files.each do |feature_flag| + check_feature_flag_yaml(feature_flag) +end diff --git a/danger/plugins/feature_flag.rb b/danger/plugins/feature_flag.rb new file mode 100644 index 00000000000..8db8daba6d9 --- /dev/null +++ b/danger/plugins/feature_flag.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require_relative '../../tooling/danger/feature_flag' + +module Danger + class FeatureFlag < Plugin + # Put the helper code somewhere it can be tested + include Tooling::Danger::FeatureFlag + end +end diff --git a/doc/administration/logs.md b/doc/administration/logs.md index c5dc8e6ece8..574d8bde329 100644 --- a/doc/administration/logs.md +++ b/doc/administration/logs.md @@ -381,10 +381,7 @@ only. For example: } ``` -## `audit_json.log` - -NOTE: -Most log entries only exist in [GitLab Starter](https://about.gitlab.com/pricing/), however a few exist in GitLab Core. +## `audit_json.log` **(STARTER)** This file lives in `/var/log/gitlab/gitlab-rails/audit_json.log` for Omnibus GitLab packages or in `/home/git/gitlab/log/audit_json.log` for diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index a355d2e8430..073ad08caf1 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -375,7 +375,7 @@ The files defined by `include` are: NOTE: Use merging to customize and override included CI/CD configurations with local -definitions. Local definitions in `.gitlab-ci.yml` override included definitions. +configurations. Local configurations in `.gitlab-ci.yml` override included configurations. #### Variables with `include` **(CORE ONLY)** @@ -2121,7 +2121,7 @@ build_job: `build_job` downloads the artifacts from the latest successful `build-1` job on the `master` branch in the `group/project-name` project. If the project is in the -same group or namespace, you can omit them from the `project:` key. For example, +same group or namespace, you can omit them from the `project:` keyword. For example, `project: group/project-name` or `project: project-name`. The user running the pipeline must have at least `reporter` access to the group or project, or the group/project must have public visibility. @@ -2460,8 +2460,8 @@ by authorized users. Use `when: delayed` to execute scripts after a waiting period, or if you want to avoid jobs immediately entering the `pending` state. -You can set the period with `start_in` key. The value of `start_in` key is an elapsed time in seconds, unless a unit is -provided. `start_in` key must be less than or equal to one week. Examples of valid values include: +You can set the period with `start_in` keyword. The value of `start_in` is an elapsed time in seconds, unless a unit is +provided. `start_in` must be less than or equal to one week. Examples of valid values include: - `'5'` - `5 seconds` @@ -2728,7 +2728,7 @@ as Review Apps. You can see an example that uses Review Apps at cached between jobs. You can only use paths that are in the local working copy. If `cache` is defined outside the scope of jobs, it means it's set -globally and all jobs use that definition. +globally and all jobs use that configuration. Caching is shared between pipelines and jobs. Caches are restored before [artifacts](#artifacts). @@ -3677,8 +3677,8 @@ deploystacks: > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/8997) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.8. > - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/199224) to GitLab Core in 12.8. -Use `trigger` to define a downstream pipeline trigger. When GitLab starts a job created -with a `trigger` definition, a downstream pipeline is created. +Use `trigger` to define a downstream pipeline trigger. When GitLab starts a `trigger` job, +a downstream pipeline is created. Jobs with `trigger` can only use a [limited set of keywords](../multi_project_pipelines.md#limitations). For example, you can't run commands with [`script`](#script), [`before_script`](#before_script), @@ -3904,7 +3904,7 @@ To avoid these errors, the `resource_group` attribute can be used to ensure that the runner doesn't run certain jobs simultaneously. Resource groups behave similar to semaphores in other programming languages. -When the `resource_group` key is defined for a job in `.gitlab-ci.yml`, +When the `resource_group` keyword is defined for a job in `.gitlab-ci.yml`, job executions are mutually exclusive across different pipelines for the same project. If multiple jobs belonging to the same resource group are enqueued simultaneously, only one of the jobs is picked by the runner. The other jobs wait until the @@ -3936,7 +3936,7 @@ For more information, see [Deployments Safety](../environments/deployment_safety `release` indicates that the job creates a [Release](../../user/project/releases/index.md). -These methods are supported: +These keywords are supported: - [`tag_name`](#releasetag_name) - [`description`](#releasedescription) @@ -4325,26 +4325,26 @@ The following example uses anchors and map merging. It creates two jobs, with their own custom `script` defined: ```yaml -.job_template: &job_definition # Hidden key that defines an anchor named 'job_definition' +.job_template: &job_configuration # Hidden yaml configuration that defines an anchor named 'job_configuration' image: ruby:2.6 services: - postgres - redis test1: - <<: *job_definition # Merge the contents of the 'job_definition' alias + <<: *job_configuration # Merge the contents of the 'job_configuration' alias script: - test1 project test2: - <<: *job_definition # Merge the contents of the 'job_definition' alias + <<: *job_configuration # Merge the contents of the 'job_configuration' alias script: - test2 project ``` -`&` sets up the name of the anchor (`job_definition`), `<<` means "merge the +`&` sets up the name of the anchor (`job_configuration`), `<<` means "merge the given hash into the current one", and `*` includes the named anchor -(`job_definition` again). The expanded version of the example above is: +(`job_configuration` again). The expanded version of the example above is: ```yaml .job_template: @@ -4375,31 +4375,31 @@ and `test:mysql` share the `script` defined in `.job_template`, but use differen `services`, defined in `.postgres_services` and `.mysql_services`: ```yaml -.job_template: &job_definition +.job_template: &job_configuration script: - test project tags: - dev .postgres_services: - services: &postgres_definition + services: &postgres_configuration - postgres - ruby .mysql_services: - services: &mysql_definition + services: &mysql_configuration - mysql - ruby test:postgres: - <<: *job_definition - services: *postgres_definition + <<: *job_configuration + services: *postgres_configuration tags: - postgres test:mysql: - <<: *job_definition - services: *mysql_definition + <<: *job_configuration + services: *mysql_configuration ``` The expanded version is: diff --git a/doc/operations/incident_management/incidents.md b/doc/operations/incident_management/incidents.md index 95c1c76806d..19c8c36c844 100644 --- a/doc/operations/incident_management/incidents.md +++ b/doc/operations/incident_management/incidents.md @@ -36,9 +36,11 @@ Incident, you have two options to do this manually. > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/230857) in GitLab 13.4. -- Navigate to **Issues > List** and click **Create Issue**. -- Create a new issue using the `type` drop-down and select `Incident`. -- The page refreshes and the page only displays fields relevant to Incidents. +1. Go to **Issues > List**, and select **New issue**. +1. In the **Type** dropdown, select **Incident**. Only fields relevant to + incidents are displayed on the page. +1. Create the incident as needed, and select **Submit issue** to save the + incident. ![Incident List Create](img/new_incident_create_v13_4.png) diff --git a/doc/user/analytics/ci_cd_analytics.md b/doc/user/analytics/ci_cd_analytics.md index beb2cbfdc58..f236b47e155 100644 --- a/doc/user/analytics/ci_cd_analytics.md +++ b/doc/user/analytics/ci_cd_analytics.md @@ -22,6 +22,29 @@ View pipeline duration history: ![Pipeline duration](img/pipelines_duration_chart.png) +## DORA4 Metrics + +Customer experience is a key metric. Users want to measure platform stability and other +post-deployment performance KPIs, and set targets for customer behavior, experience, and financial +impact. Tracking and measuring these indicators solves an important pain point. Similarly, creating +views that manage products, not projects or repos, provides users with a more relevant data set. +Since GitLab is a tool for the entire DevOps life-cycle, information from different workflows is +integrated and can be used to measure the success of the teams. + +The DevOps Research and Assessment ([DORA](https://cloud.google.com/blog/products/devops-sre/the-2019-accelerate-state-of-devops-elite-performance-productivity-and-scaling)) +team developed four key metrics that the industry has widely adopted. You can use these metrics as +performance indicators for software development teams: + +- Deployment frequency: How often an organization successfully releases to production. +- Lead time for changes: The amount of time it takes for code to reach production. +- Change failure rate: The percentage of deployments that cause a failure in production. +- Time to restore service: How long it takes an organization to recover from a failure in + production. + +GitLab plans to add support for all the DORA4 metrics at the project and group levels. GitLab added +the first metric, deployment frequency, at the project level for [CI/CD charts](ci_cd_analytics.md#deployment-frequency-charts) +and the [API]( ../../api/project_analytics.md). + ## Deployment frequency charts **(ULTIMATE)** > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/275991) in GitLab 13.8. diff --git a/doc/user/group/index.md b/doc/user/group/index.md index 48a5ddf4329..823e4b86d0f 100644 --- a/doc/user/group/index.md +++ b/doc/user/group/index.md @@ -730,6 +730,9 @@ To enable this feature, navigate to the group settings page, expand the ![Group file template settings](img/group_file_template_settings.png) +To learn how to create templates for issues and merge requests, visit +[Description templates](../project/description_templates.md). + #### Group-level project templates **(PREMIUM)** Define project templates at a group level by setting a group as the template source. diff --git a/doc/user/project/description_templates.md b/doc/user/project/description_templates.md index 0c3e8b88308..4791cae3a24 100644 --- a/doc/user/project/description_templates.md +++ b/doc/user/project/description_templates.md @@ -6,16 +6,13 @@ info: To determine the technical writer assigned to the Stage/Group associated w # Description templates ->[Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/4981) in GitLab 8.11. +> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/4981) in GitLab 8.11. We all know that a properly submitted issue is more likely to be addressed in a timely manner by the developers of a project. -Description templates allow you to define context-specific templates for issue -and merge request description fields for your project, as well as help filter -out a lot of unnecessary noise from issues. - -## Overview +With description templates, you can define context-specific templates for issue and merge request +description fields for your project, and filter out unnecessary noise from issues. By using the description templates, users that create a new issue or merge request can select a description template to help them communicate with other @@ -28,7 +25,10 @@ Description templates must be written in [Markdown](../markdown.md) and stored in your project's repository under a directory named `.gitlab`. Only the templates of the default branch are taken into account. -## Use-cases +To learn how to create templates for various file types in groups, visit +[Group file templates](../group/index.md#group-file-templates). + +## Use cases - Add a template to be used in every issue for a specific project, giving instructions and guidelines, requiring for information specific to that subject. @@ -80,17 +80,17 @@ to the issue description field. The **Reset template** button discards any changes you made after picking the template and returns it to its initial status. NOTE: -You can create short-cut links to create an issue using a designated template. For example: `https://gitlab.com/gitlab-org/gitlab/-/issues/new?issuable_template=Feature%20proposal`. +You can create shortcut links to create an issue using a designated template. +For example: `https://gitlab.com/gitlab-org/gitlab/-/issues/new?issuable_template=Feature%20proposal`. ![Description templates](img/description_templates.png) ## Setting a default template for merge requests and issues **(STARTER)** -> - This feature was introduced before [description templates](#overview) and is available in [GitLab Starter](https://about.gitlab.com/pricing/). It can be enabled in the project's settings. -> - Templates for issues were [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/28) in GitLab EE 8.1. -> - Templates for merge requests were [introduced](https://gitlab.com/gitlab-org/gitlab/commit/7478ece8b48e80782b5465b96c79f85cc91d391b) in GitLab EE 6.9. +> - Templates for merge requests [introduced](https://gitlab.com/gitlab-org/gitlab/commit/7478ece8b48e80782b5465b96c79f85cc91d391b) in GitLab 6.9. +> - Templates for issues [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/28) in GitLab 8.1. -The visibility of issues and/or merge requests should be set to either "Everyone +The visibility of issues or merge requests should be set to either "Everyone with access" or "Only Project Members" in your project's **Settings / Visibility, project features, permissions** section, otherwise the template text areas don't show. This is the default behavior, so in most cases you should be fine. @@ -113,52 +113,46 @@ pre-filled with the text you entered in the template(s). ## Description template example -We make use of Description Templates for Issues and Merge Requests within the GitLab Community -Edition project. Please refer to the [`.gitlab` folder](https://gitlab.com/gitlab-org/gitlab/tree/master/.gitlab) -for some examples. +We make use of description templates for issues and merge requests in the GitLab project. +For some examples, refer to the [`.gitlab` folder](https://gitlab.com/gitlab-org/gitlab/tree/master/.gitlab). NOTE: -It's possible to use [quick actions](quick_actions.md) within description templates to quickly add +It's possible to use [quick actions](quick_actions.md) in description templates to quickly add labels, assignees, and milestones. The quick actions are only executed if the user submitting the issue or merge request has the permissions to perform the relevant actions. Here is an example of a Bug report template: -```plaintext -Summary +```markdown +## Summary (Summarize the bug encountered concisely) - -Steps to reproduce +## Steps to reproduce (How one can reproduce the issue - this is very important) +## Example Project -Example Project +(If possible, please create an example project here on GitLab.com that exhibits the problematic +behavior, and link to it here in the bug report. +If you are using an older version of GitLab, this will also determine whether the bug has been fixed +in a more recent version) -(If possible, please create an example project here on GitLab.com that exhibits the problematic behaviour, and link to it here in the bug report) - -(If you are using an older version of GitLab, this will also determine whether the bug has been fixed in a more recent version) - - -What is the current bug behavior? +## What is the current bug behavior? (What actually happens) - -What is the expected correct behavior? +## What is the expected correct behavior? (What you should see instead) +## Relevant logs and/or screenshots -Relevant logs and/or screenshots +(Paste any relevant logs - please use code blocks (```) to format console output, logs, and code, as +it's very hard to read otherwise.) -(Paste any relevant logs - please use code blocks (```) to format console output, -logs, and code as it's very hard to read otherwise.) - - -Possible fixes +## Possible fixes (If you can, link to the line of code that might be responsible for the problem) diff --git a/lib/bulk_imports/groups/graphql/get_labels_query.rb b/lib/bulk_imports/groups/graphql/get_labels_query.rb new file mode 100644 index 00000000000..d65c8ee27e1 --- /dev/null +++ b/lib/bulk_imports/groups/graphql/get_labels_query.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +module BulkImports + module Groups + module Graphql + module GetLabelsQuery + extend self + + def to_s + <<-'GRAPHQL' + query ($full_path: ID!, $cursor: String) { + group(fullPath: $full_path) { + labels(first: 100, after: $cursor) { + page_info: pageInfo { + end_cursor: endCursor + has_next_page: hasNextPage + } + nodes { + title + description + color + } + } + } + } + GRAPHQL + end + + def variables(entity) + { + full_path: entity.source_full_path, + cursor: entity.next_page_for(:labels) + } + end + end + end + end +end diff --git a/lib/bulk_imports/groups/loaders/labels_loader.rb b/lib/bulk_imports/groups/loaders/labels_loader.rb new file mode 100644 index 00000000000..17799ff2573 --- /dev/null +++ b/lib/bulk_imports/groups/loaders/labels_loader.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module BulkImports + module Groups + module Loaders + class LabelsLoader + def initialize(*); end + + def load(context, data) + Array.wrap(data['nodes']).each do |entry| + Labels::CreateService.new(entry) + .execute(group: context.entity.group) + end + + context.entity.update_tracker_for( + relation: :labels, + has_next_page: data.dig('page_info', 'has_next_page'), + next_page: data.dig('page_info', 'end_cursor') + ) + end + end + end + end +end diff --git a/lib/bulk_imports/groups/pipelines/labels_pipeline.rb b/lib/bulk_imports/groups/pipelines/labels_pipeline.rb new file mode 100644 index 00000000000..0a36f5641e1 --- /dev/null +++ b/lib/bulk_imports/groups/pipelines/labels_pipeline.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module BulkImports + module Groups + module Pipelines + class LabelsPipeline + include Pipeline + + extractor BulkImports::Common::Extractors::GraphqlExtractor, + query: BulkImports::Groups::Graphql::GetLabelsQuery + + transformer BulkImports::Common::Transformers::HashKeyDigger, key_path: %w[data group labels] + transformer Common::Transformers::ProhibitedAttributesTransformer + + loader BulkImports::Groups::Loaders::LabelsLoader + + def after_run(context) + if context.entity.has_next_page?(:labels) + run(context) + end + end + end + end + end +end diff --git a/lib/bulk_imports/importers/group_importer.rb b/lib/bulk_imports/importers/group_importer.rb index 6e1b86e9515..ce10c37f136 100644 --- a/lib/bulk_imports/importers/group_importer.rb +++ b/lib/bulk_imports/importers/group_importer.rb @@ -29,7 +29,8 @@ module BulkImports def pipelines [ BulkImports::Groups::Pipelines::GroupPipeline, - BulkImports::Groups::Pipelines::SubgroupEntitiesPipeline + BulkImports::Groups::Pipelines::SubgroupEntitiesPipeline, + BulkImports::Groups::Pipelines::LabelsPipeline ] end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 5695bcaaf61..484a4cf94b6 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -24624,21 +24624,36 @@ msgstr "" msgid "Runners|Active" msgstr "" +msgid "Runners|An error has occurred fetching instructions" +msgstr "" + msgid "Runners|Architecture" msgstr "" msgid "Runners|Can run untagged jobs" msgstr "" +msgid "Runners|Copy instructions" +msgstr "" + msgid "Runners|Description" msgstr "" +msgid "Runners|Download Latest Binary" +msgstr "" + +msgid "Runners|Download and Install Binary" +msgstr "" + msgid "Runners|Group" msgstr "" msgid "Runners|IP Address" msgstr "" +msgid "Runners|Install a Runner" +msgstr "" + msgid "Runners|Last contact" msgstr "" @@ -24660,24 +24675,39 @@ msgstr "" msgid "Runners|Protected" msgstr "" +msgid "Runners|Register Runner" +msgstr "" + msgid "Runners|Revision" msgstr "" msgid "Runners|Shared" msgstr "" +msgid "Runners|Show Runner installation instructions" +msgstr "" + msgid "Runners|Specific" msgstr "" msgid "Runners|Tags" msgstr "" +msgid "Runners|To install Runner in Kubernetes follow the instructions described in the GitLab documentation." +msgstr "" + +msgid "Runners|To install Runner in a container follow the instructions described in the GitLab documentation" +msgstr "" + msgid "Runners|Value" msgstr "" msgid "Runners|Version" msgstr "" +msgid "Runners|View installation instructions" +msgstr "" + msgid "Runners|You have used %{quotaUsed} out of %{quotaLimit} of your shared Runners pipeline minutes." msgstr "" diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb index 510fc0c9a16..81ad1896075 100644 --- a/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb @@ -16,7 +16,7 @@ module QA Flow::Login.sign_in end - it 'creates a basic merge request', :smoke, testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/409' do + it 'creates a basic merge request', :smoke, testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1276' do Resource::MergeRequest.fabricate_via_browser_ui! do |merge_request| merge_request.project = project merge_request.title = merge_request_title diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb index 823a033ab6d..3414584bae3 100644 --- a/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb @@ -3,7 +3,7 @@ module QA RSpec.describe 'Create', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/30226', type: :bug } do describe 'Merge request rebasing' do - it 'user rebases source branch of merge request', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/398' do + it 'user rebases source branch of merge request', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1274' do Flow::Login.sign_in project = Resource::Project.fabricate_via_api! do |project| diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_http_private_token_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_http_private_token_spec.rb index 08af18a992e..35ec2135491 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/push_http_private_token_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_http_private_token_spec.rb @@ -3,7 +3,7 @@ module QA RSpec.describe 'Create' do describe 'Git push over HTTP', :ldap_no_tls, :smoke do - it 'user using a personal access token pushes code to the repository', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/430' do + it 'user using a personal access token pushes code to the repository', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1278' do Flow::Login.sign_in access_token = Resource::PersonalAccessToken.fabricate!.access_token diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb index 1e6cb4047f9..8617e05f912 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb @@ -24,7 +24,7 @@ module QA runner.remove_via_api! end - it 'users creates a pipeline which gets processed', :smoke, testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/391' do + it 'users creates a pipeline which gets processed', :smoke, testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1279' do Flow::Login.sign_in Resource::Repository::Commit.fabricate_via_api! do |commit| diff --git a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb index a619ccfad19..171a4776eaf 100644 --- a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb +++ b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb @@ -117,7 +117,7 @@ module QA end end - it 'runs an AutoDevOps pipeline', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/444' do + it 'runs an AutoDevOps pipeline', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1277' do Flow::Pipeline.visit_latest_pipeline Page::Project::Pipeline::Show.perform do |pipeline| diff --git a/rubocop/cop/gitlab/httparty.rb b/rubocop/cop/gitlab/httparty.rb index 8acebff624d..20f0c381e11 100644 --- a/rubocop/cop/gitlab/httparty.rb +++ b/rubocop/cop/gitlab/httparty.rb @@ -4,13 +4,13 @@ module RuboCop module Cop module Gitlab class HTTParty < RuboCop::Cop::Cop - MSG_SEND = <<~EOL.freeze + MSG_SEND = <<~EOL Avoid calling `HTTParty` directly. Instead, use the Gitlab::HTTP wrapper. To allow request to localhost or the private network set the option :allow_local_requests in the request call. EOL - MSG_INCLUDE = <<~EOL.freeze + MSG_INCLUDE = <<~EOL Avoid including `HTTParty` directly. Instead, use the Gitlab::HTTP wrapper. To allow request to localhost or the private network set the option :allow_local_requests in the request call. diff --git a/rubocop/cop/gitlab/json.rb b/rubocop/cop/gitlab/json.rb index 8c9027223aa..7cc719aca09 100644 --- a/rubocop/cop/gitlab/json.rb +++ b/rubocop/cop/gitlab/json.rb @@ -4,7 +4,7 @@ module RuboCop module Cop module Gitlab class Json < RuboCop::Cop::Cop - MSG_SEND = <<~EOL.freeze + MSG = <<~EOL Avoid calling `JSON` directly. Instead, use the `Gitlab::Json` wrapper. This allows us to alter the JSON parser being used. EOL @@ -14,7 +14,7 @@ module RuboCop PATTERN def on_send(node) - add_offense(node, location: :expression, message: MSG_SEND) if json_node?(node) + add_offense(node) if json_node?(node) end def autocorrect(node) diff --git a/rubocop/cop/gitlab/module_with_instance_variables.rb b/rubocop/cop/gitlab/module_with_instance_variables.rb index dd8bd2dfdf0..40cdc0d3a57 100644 --- a/rubocop/cop/gitlab/module_with_instance_variables.rb +++ b/rubocop/cop/gitlab/module_with_instance_variables.rb @@ -1,8 +1,10 @@ +# frozen_string_literal: true + module RuboCop module Cop module Gitlab class ModuleWithInstanceVariables < RuboCop::Cop::Cop - MSG = <<~EOL.freeze + MSG = <<~EOL Do not use instance variables in a module. Please read this for the rationale behind it: diff --git a/rubocop/cop/gitlab/predicate_memoization.rb b/rubocop/cop/gitlab/predicate_memoization.rb index 3c25d61d087..4c851f90238 100644 --- a/rubocop/cop/gitlab/predicate_memoization.rb +++ b/rubocop/cop/gitlab/predicate_memoization.rb @@ -1,8 +1,10 @@ +# frozen_string_literal: true + module RuboCop module Cop module Gitlab class PredicateMemoization < RuboCop::Cop::Cop - MSG = <<~EOL.freeze + MSG = <<~EOL Avoid using `@value ||= query` inside predicate methods in order to properly memoize `false` or `nil` values. https://docs.gitlab.com/ee/development/utilities.html#strongmemoize @@ -12,7 +14,7 @@ module RuboCop return unless predicate_method?(node) select_offenses(node).each do |offense| - add_offense(offense, location: :expression) + add_offense(offense) end end diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb index f8e84043c1b..1622979812d 100644 --- a/spec/features/commits_spec.rb +++ b/spec/features/commits_spec.rb @@ -138,9 +138,8 @@ RSpec.describe 'Commits' do end end - context 'when accessing internal project with disallowed access', :js do + context 'when accessing internal project with disallowed access', :js, quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/299575' do before do - stub_feature_flags(graphql_pipeline_header: false) project.update( visibility_level: Gitlab::VisibilityLevel::INTERNAL, public_builds: false) diff --git a/spec/frontend/pipelines/legacy_header_component_spec.js b/spec/frontend/pipelines/legacy_header_component_spec.js deleted file mode 100644 index fb7feb8898a..00000000000 --- a/spec/frontend/pipelines/legacy_header_component_spec.js +++ /dev/null @@ -1,116 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import { GlModal } from '@gitlab/ui'; -import LegacyHeaderComponent from '~/pipelines/components/legacy_header_component.vue'; -import CiHeader from '~/vue_shared/components/header_ci_component.vue'; -import eventHub from '~/pipelines/event_hub'; - -describe('Pipeline details header', () => { - let wrapper; - let glModalDirective; - - const threeWeeksAgo = new Date(); - threeWeeksAgo.setDate(threeWeeksAgo.getDate() - 21); - - const findDeleteModal = () => wrapper.find(GlModal); - - const defaultProps = { - pipeline: { - details: { - status: { - group: 'failed', - icon: 'status_failed', - label: 'failed', - text: 'failed', - details_path: 'path', - }, - }, - id: 123, - created_at: threeWeeksAgo.toISOString(), - user: { - web_url: 'path', - name: 'Foo', - username: 'foobar', - email: 'foo@bar.com', - avatar_url: 'link', - }, - retry_path: 'retry', - cancel_path: 'cancel', - delete_path: 'delete', - }, - isLoading: false, - }; - - const createComponent = (props = {}) => { - glModalDirective = jest.fn(); - - wrapper = shallowMount(LegacyHeaderComponent, { - propsData: { - ...props, - }, - directives: { - glModal: { - bind(el, { value }) { - glModalDirective(value); - }, - }, - }, - }); - }; - - beforeEach(() => { - jest.spyOn(eventHub, '$emit'); - - createComponent(defaultProps); - }); - - afterEach(() => { - eventHub.$off(); - - wrapper.destroy(); - wrapper = null; - }); - - it('should render provided pipeline info', () => { - expect(wrapper.find(CiHeader).props()).toMatchObject({ - status: defaultProps.pipeline.details.status, - itemId: defaultProps.pipeline.id, - time: defaultProps.pipeline.created_at, - user: defaultProps.pipeline.user, - }); - }); - - describe('action buttons', () => { - it('should not trigger eventHub when nothing happens', () => { - expect(eventHub.$emit).not.toHaveBeenCalled(); - }); - - it('should call postAction when retry button action is clicked', () => { - wrapper.find('[data-testid="retryButton"]').vm.$emit('click'); - - expect(eventHub.$emit).toHaveBeenCalledWith('headerPostAction', 'retry'); - }); - - it('should call postAction when cancel button action is clicked', () => { - wrapper.find('[data-testid="cancelPipeline"]').vm.$emit('click'); - - expect(eventHub.$emit).toHaveBeenCalledWith('headerPostAction', 'cancel'); - }); - - it('does not show delete modal', () => { - expect(findDeleteModal()).not.toBeVisible(); - }); - - describe('when delete button action is clicked', () => { - it('displays delete modal', () => { - expect(findDeleteModal().props('modalId')).toBe(wrapper.vm.$options.DELETE_MODAL_ID); - expect(glModalDirective).toHaveBeenCalledWith(wrapper.vm.$options.DELETE_MODAL_ID); - }); - - it('should call delete when modal is submitted', () => { - findDeleteModal().vm.$emit('ok'); - - expect(eventHub.$emit).toHaveBeenCalledWith('headerDeleteAction', 'delete'); - }); - }); - }); -}); diff --git a/spec/frontend/registry/explorer/pages/details_spec.js b/spec/frontend/registry/explorer/pages/details_spec.js index 1746a6a63b6..9e6e74e22f0 100644 --- a/spec/frontend/registry/explorer/pages/details_spec.js +++ b/spec/frontend/registry/explorer/pages/details_spec.js @@ -4,6 +4,7 @@ import VueApollo from 'vue-apollo'; import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; import Tracking from '~/tracking'; +import axios from '~/lib/utils/axios_utils'; import component from '~/registry/explorer/pages/details.vue'; import DeleteAlert from '~/registry/explorer/components/details_page/delete_alert.vue'; import PartialCleanupAlert from '~/registry/explorer/components/details_page/partial_cleanup_alert.vue'; @@ -401,6 +402,9 @@ describe('Details Page', () => { const config = { runCleanupPoliciesHelpPagePath: 'foo', cleanupPoliciesHelpPagePath: 'bar', + userCalloutsPath: 'call_out_path', + userCalloutId: 'call_out_id', + showUnfinishedTagCleanupCallout: true, }; describe(`when expirationPolicyCleanupStatus is ${UNFINISHED_STATUS}`, () => { @@ -413,8 +417,9 @@ describe('Details Page', () => { }), ); }); + it('exists', async () => { - mountComponent({ resolver }); + mountComponent({ resolver, config }); await waitForApolloRequestRender(); @@ -426,11 +431,16 @@ describe('Details Page', () => { await waitForApolloRequestRender(); - expect(findPartialCleanupAlert().props()).toEqual({ ...config }); + expect(findPartialCleanupAlert().props()).toEqual({ + runCleanupPoliciesHelpPagePath: config.runCleanupPoliciesHelpPagePath, + cleanupPoliciesHelpPagePath: config.cleanupPoliciesHelpPagePath, + }); }); it('dismiss hides the component', async () => { - mountComponent({ resolver }); + jest.spyOn(axios, 'post').mockReturnValue(); + + mountComponent({ resolver, config }); await waitForApolloRequestRender(); @@ -440,13 +450,25 @@ describe('Details Page', () => { await wrapper.vm.$nextTick(); + expect(axios.post).toHaveBeenCalledWith(config.userCalloutsPath, { + feature_name: config.userCalloutId, + }); + expect(findPartialCleanupAlert().exists()).toBe(false); + }); + + it('is hidden if the callout is dismissed', async () => { + mountComponent({ resolver }); + + await waitForApolloRequestRender(); + expect(findPartialCleanupAlert().exists()).toBe(false); }); }); describe(`when expirationPolicyCleanupStatus is not ${UNFINISHED_STATUS}`, () => { it('the component is hidden', async () => { - mountComponent(); + mountComponent({ config }); + await waitForApolloRequestRender(); expect(findPartialCleanupAlert().exists()).toBe(false); diff --git a/spec/frontend/vue_shared/components/runner_instructions/mock_data.js b/spec/frontend/vue_shared/components/runner_instructions/mock_data.js new file mode 100644 index 00000000000..01f7f3d49c7 --- /dev/null +++ b/spec/frontend/vue_shared/components/runner_instructions/mock_data.js @@ -0,0 +1,107 @@ +export const mockGraphqlRunnerPlatforms = { + data: { + runnerPlatforms: { + nodes: [ + { + name: 'linux', + humanReadableName: 'Linux', + architectures: { + nodes: [ + { + name: 'amd64', + downloadLocation: + 'https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-amd64', + __typename: 'RunnerArchitecture', + }, + { + name: '386', + downloadLocation: + 'https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-386', + __typename: 'RunnerArchitecture', + }, + { + name: 'arm', + downloadLocation: + 'https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-arm', + __typename: 'RunnerArchitecture', + }, + { + name: 'arm64', + downloadLocation: + 'https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-arm64', + __typename: 'RunnerArchitecture', + }, + ], + __typename: 'RunnerArchitectureConnection', + }, + __typename: 'RunnerPlatform', + }, + { + name: 'osx', + humanReadableName: 'macOS', + architectures: { + nodes: [ + { + name: 'amd64', + downloadLocation: + 'https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-darwin-amd64', + __typename: 'RunnerArchitecture', + }, + ], + __typename: 'RunnerArchitectureConnection', + }, + __typename: 'RunnerPlatform', + }, + { + name: 'windows', + humanReadableName: 'Windows', + architectures: { + nodes: [ + { + name: 'amd64', + downloadLocation: + 'https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-windows-amd64.exe', + __typename: 'RunnerArchitecture', + }, + { + name: '386', + downloadLocation: + 'https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-windows-386.exe', + __typename: 'RunnerArchitecture', + }, + ], + __typename: 'RunnerArchitectureConnection', + }, + __typename: 'RunnerPlatform', + }, + { + name: 'docker', + humanReadableName: 'Docker', + architectures: null, + __typename: 'RunnerPlatform', + }, + { + name: 'kubernetes', + humanReadableName: 'Kubernetes', + architectures: null, + __typename: 'RunnerPlatform', + }, + ], + __typename: 'RunnerPlatformConnection', + }, + project: { id: 'gid://gitlab/Project/1', __typename: 'Project' }, + group: null, + }, +}; + +export const mockGraphqlInstructions = { + data: { + runnerSetup: { + installInstructions: + "# Download the binary for your system\nsudo curl -L --output /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-amd64\n\n# Give it permissions to execute\nsudo chmod +x /usr/local/bin/gitlab-runner\n\n# Create a GitLab CI user\nsudo useradd --comment 'GitLab Runner' --create-home gitlab-runner --shell /bin/bash\n\n# Install and run as service\nsudo gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner\nsudo gitlab-runner start\n", + registerInstructions: + 'sudo gitlab-runner register --url http://192.168.1.81:3000/ --registration-token GE5gsjeep_HAtBf9s3Yz', + __typename: 'RunnerSetup', + }, + }, +}; diff --git a/spec/frontend/vue_shared/components/runner_instructions/runner_instructions_spec.js b/spec/frontend/vue_shared/components/runner_instructions/runner_instructions_spec.js new file mode 100644 index 00000000000..6e2ba603e04 --- /dev/null +++ b/spec/frontend/vue_shared/components/runner_instructions/runner_instructions_spec.js @@ -0,0 +1,113 @@ +import { shallowMount, createLocalVue } from '@vue/test-utils'; +import VueApollo from 'vue-apollo'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import RunnerInstructions from '~/vue_shared/components/runner_instructions/runner_instructions.vue'; +import getRunnerPlatforms from '~/vue_shared/components/runner_instructions/graphql/queries/get_runner_platforms.query.graphql'; +import getRunnerSetupInstructions from '~/vue_shared/components/runner_instructions/graphql/queries/get_runner_setup.query.graphql'; + +import { mockGraphqlRunnerPlatforms, mockGraphqlInstructions } from './mock_data'; + +const projectPath = 'gitlab-org/gitlab'; +const localVue = createLocalVue(); +localVue.use(VueApollo); + +describe('RunnerInstructions component', () => { + let wrapper; + let fakeApollo; + + const findModalButton = () => wrapper.find('[data-testid="show-modal-button"]'); + const findPlatformButtons = () => wrapper.findAll('[data-testid="platform-button"]'); + const findArchitectureDropdownItems = () => + wrapper.findAll('[data-testid="architecture-dropdown-item"]'); + const findBinaryInstructionsSection = () => wrapper.find('[data-testid="binary-instructions"]'); + const findRunnerInstructionsSection = () => wrapper.find('[data-testid="runner-instructions"]'); + + beforeEach(async () => { + const requestHandlers = [ + [getRunnerPlatforms, jest.fn().mockResolvedValue(mockGraphqlRunnerPlatforms)], + [getRunnerSetupInstructions, jest.fn().mockResolvedValue(mockGraphqlInstructions)], + ]; + + fakeApollo = createMockApollo(requestHandlers); + + wrapper = shallowMount(RunnerInstructions, { + provide: { + projectPath, + }, + localVue, + apolloProvider: fakeApollo, + }); + + await wrapper.vm.$nextTick(); + }); + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + it('should show the "Show Runner installation instructions" button', () => { + const button = findModalButton(); + + expect(button.exists()).toBe(true); + expect(button.text()).toBe('Show Runner installation instructions'); + }); + + it('should contain a number of platforms buttons', () => { + const buttons = findPlatformButtons(); + + expect(buttons).toHaveLength(mockGraphqlRunnerPlatforms.data.runnerPlatforms.nodes.length); + }); + + it('should contain a number of dropdown items for the architecture options', () => { + const platformButton = findPlatformButtons().at(0); + platformButton.vm.$emit('click'); + + return wrapper.vm.$nextTick(() => { + const dropdownItems = findArchitectureDropdownItems(); + + expect(dropdownItems).toHaveLength( + mockGraphqlRunnerPlatforms.data.runnerPlatforms.nodes[0].architectures.nodes.length, + ); + }); + }); + + it('should display the binary installation instructions for a selected architecture', async () => { + const platformButton = findPlatformButtons().at(0); + platformButton.vm.$emit('click'); + + await wrapper.vm.$nextTick(); + + const dropdownItem = findArchitectureDropdownItems().at(0); + dropdownItem.vm.$emit('click'); + + await wrapper.vm.$nextTick(); + + const runner = findBinaryInstructionsSection(); + + expect(runner.text()).toMatch('sudo chmod +x /usr/local/bin/gitlab-runner'); + expect(runner.text()).toMatch( + `sudo useradd --comment 'GitLab Runner' --create-home gitlab-runner --shell /bin/bash`, + ); + expect(runner.text()).toMatch( + 'sudo gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner', + ); + expect(runner.text()).toMatch('sudo gitlab-runner start'); + }); + + it('should display the runner register instructions for a selected architecture', async () => { + const platformButton = findPlatformButtons().at(0); + platformButton.vm.$emit('click'); + + await wrapper.vm.$nextTick(); + + const dropdownItem = findArchitectureDropdownItems().at(0); + dropdownItem.vm.$emit('click'); + + await wrapper.vm.$nextTick(); + + const runner = findRunnerInstructionsSection(); + + expect(runner.text()).toMatch(mockGraphqlInstructions.data.runnerSetup.registerInstructions); + }); +}); diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb index 1ed61bd3144..07e55e9b016 100644 --- a/spec/helpers/issues_helper_spec.rb +++ b/spec/helpers/issues_helper_spec.rb @@ -254,4 +254,31 @@ RSpec.describe IssuesHelper do expect(helper.use_startup_call?).to eq(true) end end + + describe '#issue_header_actions_data' do + let(:current_user) { create(:user) } + + before do + allow(helper).to receive(:current_user).and_return(current_user) + allow(helper).to receive(:can?).and_return(true) + end + + it 'returns expected result' do + expected = { + can_create_issue: "true", + can_reopen_issue: "true", + can_report_spam: "false", + can_update_issue: "true", + iid: issue.iid, + is_issue_author: "false", + issue_type: "issue", + new_issue_path: new_project_issue_path(project), + project_path: project.full_path, + report_abuse_path: new_abuse_report_path(user_id: issue.author.id, ref_url: issue_url(issue)), + submit_as_spam_path: mark_as_spam_project_issue_path(project, issue) + } + + expect(helper.issue_header_actions_data(project, issue, current_user)).to include(expected) + end + end end diff --git a/spec/helpers/user_callouts_helper_spec.rb b/spec/helpers/user_callouts_helper_spec.rb index 250aedda906..b6607182461 100644 --- a/spec/helpers/user_callouts_helper_spec.rb +++ b/spec/helpers/user_callouts_helper_spec.rb @@ -222,4 +222,24 @@ RSpec.describe UserCalloutsHelper do it { is_expected.to be true } end end + + describe '.show_unfinished_tag_cleanup_callout?' do + subject { helper.show_unfinished_tag_cleanup_callout? } + + before do + allow(helper).to receive(:user_dismissed?).with(described_class::UNFINISHED_TAG_CLEANUP_CALLOUT) { dismissed } + end + + context 'when user has not dismissed' do + let(:dismissed) { false } + + it { is_expected.to be true } + end + + context 'when user dismissed' do + let(:dismissed) { true } + + it { is_expected.to be false } + end + end end diff --git a/spec/javascripts/test_bundle.js b/spec/javascripts/test_bundle.js index 59136de0b0d..faa07775aaa 100644 --- a/spec/javascripts/test_bundle.js +++ b/spec/javascripts/test_bundle.js @@ -81,14 +81,6 @@ window.addEventListener('unhandledrejection', (event) => { console.error(event.reason.stack || event.reason); }); -// HACK: Chrome 59 disconnects if there are too many synchronous tests in a row -// because it appears to lock up the thread that communicates to Karma's socket -// This async beforeEach gets called on every spec and releases the JS thread long -// enough for the socket to continue to communicate. -// The downside is that it creates a minor performance penalty in the time it takes -// to run our unit tests. -beforeEach((done) => done()); - let longRunningTestTimeoutHandle; beforeEach((done) => { diff --git a/spec/lib/bulk_imports/groups/pipelines/labels_pipeline_spec.rb b/spec/lib/bulk_imports/groups/pipelines/labels_pipeline_spec.rb new file mode 100644 index 00000000000..135995f1ff3 --- /dev/null +++ b/spec/lib/bulk_imports/groups/pipelines/labels_pipeline_spec.rb @@ -0,0 +1,94 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe BulkImports::Groups::Pipelines::LabelsPipeline do + let(:user) { create(:user) } + let(:group) { create(:group) } + let(:entity) do + create( + :bulk_import_entity, + source_full_path: 'source/full/path', + destination_name: 'My Destination Group', + destination_namespace: group.full_path, + group: group + ) + end + + let(:context) do + BulkImports::Pipeline::Context.new( + current_user: user, + entity: entity + ) + end + + def extractor_data(title:, has_next_page:, cursor: "") + { + "data" => { + "group" => { + "labels" => { + "page_info" => { + "end_cursor" => cursor, + "has_next_page" => has_next_page + }, + "nodes" => [ + { + "title" => title, + "description" => "desc", + "color" => "#428BCA" + } + ] + } + } + } + } + end + + describe '#run' do + it 'imports a group labels' do + first_page = extractor_data(title: 'label1', has_next_page: true, cursor: 'nextPageCursor') + last_page = extractor_data(title: 'label2', has_next_page: false) + + allow_next_instance_of(BulkImports::Common::Extractors::GraphqlExtractor) do |extractor| + allow(extractor) + .to receive(:extract) + .and_return(first_page, last_page) + end + + expect { subject.run(context) }.to change(Label, :count).by(2) + + label = group.labels.order(:created_at).last + + expect(label.title).to eq('label2') + expect(label.description).to eq('desc') + expect(label.color).to eq('#428BCA') + end + end + + describe 'pipeline parts' do + it { expect(described_class).to include_module(BulkImports::Pipeline) } + it { expect(described_class).to include_module(BulkImports::Pipeline::Runner) } + + it 'has extractors' do + expect(described_class.get_extractor) + .to eq( + klass: BulkImports::Common::Extractors::GraphqlExtractor, + options: { + query: BulkImports::Groups::Graphql::GetLabelsQuery + } + ) + end + + it 'has transformers' do + expect(described_class.transformers) + .to contain_exactly( + { klass: BulkImports::Common::Transformers::HashKeyDigger, options: { key_path: %w[data group labels] } }, + { klass: BulkImports::Common::Transformers::ProhibitedAttributesTransformer, options: nil } + ) + end + + it 'has loaders' do + expect(described_class.get_loader).to eq(klass: BulkImports::Groups::Loaders::LabelsLoader, options: nil) + end + end +end diff --git a/spec/lib/bulk_imports/importers/group_importer_spec.rb b/spec/lib/bulk_imports/importers/group_importer_spec.rb index 87baf1b8026..083b1507b01 100644 --- a/spec/lib/bulk_imports/importers/group_importer_spec.rb +++ b/spec/lib/bulk_imports/importers/group_importer_spec.rb @@ -24,6 +24,7 @@ RSpec.describe BulkImports::Importers::GroupImporter do describe '#execute' do it 'starts the entity and run its pipelines' do expect_to_run_pipeline BulkImports::Groups::Pipelines::GroupPipeline, context: context + expect_to_run_pipeline BulkImports::Groups::Pipelines::LabelsPipeline, context: context expect_to_run_pipeline('EE::BulkImports::Groups::Pipelines::EpicsPipeline'.constantize, context: context) if Gitlab.ee? expect_to_run_pipeline BulkImports::Groups::Pipelines::SubgroupEntitiesPipeline, context: context diff --git a/spec/models/pages/lookup_path_spec.rb b/spec/models/pages/lookup_path_spec.rb index 25b282f3758..d95a33aabcc 100644 --- a/spec/models/pages/lookup_path_spec.rb +++ b/spec/models/pages/lookup_path_spec.rb @@ -125,6 +125,44 @@ RSpec.describe Pages::LookupPath do include_examples 'uses disk storage' end end + + context 'when deployment were created during migration' do + before do + FileUtils.mkdir_p File.join(project.pages_path, "public") + File.open(File.join(project.pages_path, "public/index.html"), "w") do |f| + f.write("Hello!") + end + + expect(::Pages::MigrateLegacyStorageToDeploymentService.new(project).execute[:status]).to eq(:success) + + project.reload + end + + let(:deployment) { project.pages_metadatum.pages_deployment } + + it 'uses deployment from object storage' do + freeze_time do + expect(source).to( + eq({ + type: 'zip', + path: deployment.file.url(expire_at: 1.day.from_now), + global_id: "gid://gitlab/PagesDeployment/#{deployment.id}", + sha256: deployment.file_sha256, + file_size: deployment.size, + file_count: deployment.file_count + }) + ) + end + end + + context 'when pages_serve_from_migrated_zip feature flag is disabled' do + before do + stub_feature_flags(pages_serve_from_migrated_zip: false) + end + + include_examples 'uses disk storage' + end + end end describe '#prefix' do diff --git a/spec/rubocop/cop/active_record_association_reload_spec.rb b/spec/rubocop/cop/active_record_association_reload_spec.rb index 8dbe6daeeca..f28c4e60f3c 100644 --- a/spec/rubocop/cop/active_record_association_reload_spec.rb +++ b/spec/rubocop/cop/active_record_association_reload_spec.rb @@ -5,8 +5,6 @@ require 'rubocop' require_relative '../../../rubocop/cop/active_record_association_reload' RSpec.describe RuboCop::Cop::ActiveRecordAssociationReload do - include CopHelper - subject(:cop) { described_class.new } context 'when using ActiveRecord::Base' do diff --git a/spec/rubocop/cop/avoid_becomes_spec.rb b/spec/rubocop/cop/avoid_becomes_spec.rb index 07cf374faf5..401c694f373 100644 --- a/spec/rubocop/cop/avoid_becomes_spec.rb +++ b/spec/rubocop/cop/avoid_becomes_spec.rb @@ -2,33 +2,31 @@ require 'fast_spec_helper' require 'rubocop' -require 'rubocop/rspec/support' require_relative '../../../rubocop/cop/avoid_becomes' RSpec.describe RuboCop::Cop::AvoidBecomes do - include CopHelper - subject(:cop) { described_class.new } it 'flags the use of becomes with a constant parameter' do - inspect_source('foo.becomes(Project)') - - expect(cop.offenses.size).to eq(1) + expect_offense(<<~CODE) + foo.becomes(Project) + ^^^^^^^^^^^^^^^^^^^^ Avoid the use of becomes(SomeConstant), [...] + CODE end it 'flags the use of becomes with a namespaced constant parameter' do - inspect_source('foo.becomes(Namespace::Group)') - - expect(cop.offenses.size).to eq(1) + expect_offense(<<~CODE) + foo.becomes(Namespace::Group) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Avoid the use of becomes(SomeConstant), [...] + CODE end it 'flags the use of becomes with a dynamic parameter' do - inspect_source(<<~RUBY) - model = Namespace - project = Project.first - project.becomes(model) - RUBY - - expect(cop.offenses.size).to eq(1) + expect_offense(<<~CODE) + model = Namespace + project = Project.first + project.becomes(model) + ^^^^^^^^^^^^^^^^^^^^^^ Avoid the use of becomes(SomeConstant), [...] + CODE end end diff --git a/spec/rubocop/cop/avoid_break_from_strong_memoize_spec.rb b/spec/rubocop/cop/avoid_break_from_strong_memoize_spec.rb index 3c3aa5b7b5c..ac59d36db3f 100644 --- a/spec/rubocop/cop/avoid_break_from_strong_memoize_spec.rb +++ b/spec/rubocop/cop/avoid_break_from_strong_memoize_spec.rb @@ -5,8 +5,6 @@ require 'rubocop' require_relative '../../../rubocop/cop/avoid_break_from_strong_memoize' RSpec.describe RuboCop::Cop::AvoidBreakFromStrongMemoize do - include CopHelper - subject(:cop) { described_class.new } it 'flags violation for break inside strong_memoize' do @@ -56,7 +54,7 @@ RSpec.describe RuboCop::Cop::AvoidBreakFromStrongMemoize do call do strong_memoize(:result) do break if something - + ^^^^^ Do not use break inside strong_memoize, use next instead. do_an_heavy_calculation end end @@ -65,7 +63,7 @@ RSpec.describe RuboCop::Cop::AvoidBreakFromStrongMemoize do expect(instance).to receive(:add_offense).once end - inspect_source(source) + expect_offense(source) end it "doesn't check when block is empty" do diff --git a/spec/rubocop/cop/avoid_keyword_arguments_in_sidekiq_workers_spec.rb b/spec/rubocop/cop/avoid_keyword_arguments_in_sidekiq_workers_spec.rb index 1e1fe851840..460a0b13458 100644 --- a/spec/rubocop/cop/avoid_keyword_arguments_in_sidekiq_workers_spec.rb +++ b/spec/rubocop/cop/avoid_keyword_arguments_in_sidekiq_workers_spec.rb @@ -2,18 +2,15 @@ require 'fast_spec_helper' require 'rubocop' -require 'rubocop/rspec/support' require_relative '../../../rubocop/cop/avoid_keyword_arguments_in_sidekiq_workers' RSpec.describe RuboCop::Cop::AvoidKeywordArgumentsInSidekiqWorkers do - include CopHelper - subject(:cop) { described_class.new } it 'flags violation for keyword arguments usage in perform method signature' do expect_offense(<<~RUBY) def perform(id:) - ^^^^^^^^^^^^^^^^ Do not use keyword arguments in Sidekiq workers. For details, check https://github.com/mperham/sidekiq/issues/2372 + ^^^^^^^^^^^^^^^^ Do not use keyword arguments in Sidekiq workers. For details, [...] end RUBY end @@ -21,7 +18,7 @@ RSpec.describe RuboCop::Cop::AvoidKeywordArgumentsInSidekiqWorkers do it 'flags violation for optional keyword arguments usage in perform method signature' do expect_offense(<<~RUBY) def perform(id: nil) - ^^^^^^^^^^^^^^^^^^^^ Do not use keyword arguments in Sidekiq workers. For details, check https://github.com/mperham/sidekiq/issues/2372 + ^^^^^^^^^^^^^^^^^^^^ Do not use keyword arguments in Sidekiq workers. For details, [...] end RUBY end diff --git a/spec/rubocop/cop/ban_catch_throw_spec.rb b/spec/rubocop/cop/ban_catch_throw_spec.rb index 4f669bad4af..b3c4ad8688c 100644 --- a/spec/rubocop/cop/ban_catch_throw_spec.rb +++ b/spec/rubocop/cop/ban_catch_throw_spec.rb @@ -1,30 +1,28 @@ # frozen_string_literal: true require 'fast_spec_helper' - require 'rubocop' -require 'rubocop/rspec/support' require_relative '../../../rubocop/cop/ban_catch_throw' RSpec.describe RuboCop::Cop::BanCatchThrow do - include CopHelper - subject(:cop) { described_class.new } it 'registers an offense when `catch` or `throw` are used' do - inspect_source("catch(:foo) {\n throw(:foo)\n}") - - aggregate_failures do - expect(cop.offenses.size).to eq(2) - expect(cop.offenses.map(&:line)).to eq([1, 2]) - expect(cop.highlights).to eq(['catch(:foo)', 'throw(:foo)']) - end + expect_offense(<<~CODE) + catch(:foo) { + ^^^^^^^^^^^ Do not use catch or throw unless a gem's API demands it. + throw(:foo) + ^^^^^^^^^^^ Do not use catch or throw unless a gem's API demands it. + } + CODE end it 'does not register an offense for a method called catch or throw' do - inspect_source("foo.catch(:foo) {\n foo.throw(:foo)\n}") - - expect(cop.offenses).to be_empty + expect_no_offenses(<<~CODE) + foo.catch(:foo) { + foo.throw(:foo) + } + CODE end end diff --git a/spec/rubocop/cop/default_scope_spec.rb b/spec/rubocop/cop/default_scope_spec.rb index fee1895603c..506843e030e 100644 --- a/spec/rubocop/cop/default_scope_spec.rb +++ b/spec/rubocop/cop/default_scope_spec.rb @@ -2,47 +2,44 @@ require 'fast_spec_helper' require 'rubocop' -require 'rubocop/rspec/support' require_relative '../../../rubocop/cop/default_scope' RSpec.describe RuboCop::Cop::DefaultScope do - include CopHelper - subject(:cop) { described_class.new } it 'does not flag the use of default_scope with a send receiver' do - inspect_source('foo.default_scope') - - expect(cop.offenses.size).to eq(0) + expect_no_offenses('foo.default_scope') end it 'flags the use of default_scope with a constant receiver' do - inspect_source('User.default_scope') - - expect(cop.offenses.size).to eq(1) + expect_offense(<<~SOURCE) + User.default_scope + ^^^^^^^^^^^^^^^^^^ Do not use `default_scope`, [...] + SOURCE end it 'flags the use of default_scope with a nil receiver' do - inspect_source('class Foo ; default_scope ; end') - - expect(cop.offenses.size).to eq(1) + expect_offense(<<~SOURCE) + class Foo ; default_scope ; end + ^^^^^^^^^^^^^ Do not use `default_scope`, [...] + SOURCE end it 'flags the use of default_scope when passing arguments' do - inspect_source('class Foo ; default_scope(:foo) ; end') - - expect(cop.offenses.size).to eq(1) + expect_offense(<<~SOURCE) + class Foo ; default_scope(:foo) ; end + ^^^^^^^^^^^^^^^^^^^ Do not use `default_scope`, [...] + SOURCE end it 'flags the use of default_scope when passing a block' do - inspect_source('class Foo ; default_scope { :foo } ; end') - - expect(cop.offenses.size).to eq(1) + expect_offense(<<~SOURCE) + class Foo ; default_scope { :foo } ; end + ^^^^^^^^^^^^^ Do not use `default_scope`, [...] + SOURCE end it 'ignores the use of default_scope with a local variable receiver' do - inspect_source('users = User.all ; users.default_scope') - - expect(cop.offenses.size).to eq(0) + expect_no_offenses('users = User.all ; users.default_scope') end end diff --git a/spec/rubocop/cop/gitlab/avoid_uploaded_file_from_params_spec.rb b/spec/rubocop/cop/gitlab/avoid_uploaded_file_from_params_spec.rb index 2db03898e01..f96e25c59e7 100644 --- a/spec/rubocop/cop/gitlab/avoid_uploaded_file_from_params_spec.rb +++ b/spec/rubocop/cop/gitlab/avoid_uploaded_file_from_params_spec.rb @@ -2,19 +2,16 @@ require 'fast_spec_helper' require 'rubocop' -require 'rubocop/rspec/support' require_relative '../../../../rubocop/cop/gitlab/avoid_uploaded_file_from_params' RSpec.describe RuboCop::Cop::Gitlab::AvoidUploadedFileFromParams do - include CopHelper - subject(:cop) { described_class.new } - context 'UploadedFile.from_params' do + context 'when using UploadedFile.from_params' do it 'flags its call' do expect_offense(<<~SOURCE) - UploadedFile.from_params(params) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use the `UploadedFile` set by `multipart.rb` instead of calling `UploadedFile.from_params` directly. See https://docs.gitlab.com/ee/development/uploads.html#how-to-add-a-new-upload-route + UploadedFile.from_params(params) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use the `UploadedFile` set by `multipart.rb` instead of calling [...] SOURCE end end diff --git a/spec/rubocop/cop/gitlab/bulk_insert_spec.rb b/spec/rubocop/cop/gitlab/bulk_insert_spec.rb index ad7e685e505..c280ab8fa8b 100644 --- a/spec/rubocop/cop/gitlab/bulk_insert_spec.rb +++ b/spec/rubocop/cop/gitlab/bulk_insert_spec.rb @@ -2,25 +2,22 @@ require 'fast_spec_helper' require 'rubocop' -require 'rubocop/rspec/support' require_relative '../../../../rubocop/cop/gitlab/bulk_insert' RSpec.describe RuboCop::Cop::Gitlab::BulkInsert do - include CopHelper - subject(:cop) { described_class.new } it 'flags the use of Gitlab::Database.bulk_insert' do expect_offense(<<~SOURCE) - Gitlab::Database.bulk_insert('merge_request_diff_files', rows) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{RuboCop::Cop::Gitlab::BulkInsert::MSG} + Gitlab::Database.bulk_insert('merge_request_diff_files', rows) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use the `BulkInsertSafe` concern, [...] SOURCE end it 'flags the use of ::Gitlab::Database.bulk_insert' do expect_offense(<<~SOURCE) - ::Gitlab::Database.bulk_insert('merge_request_diff_files', rows) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{RuboCop::Cop::Gitlab::BulkInsert::MSG} + ::Gitlab::Database.bulk_insert('merge_request_diff_files', rows) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use the `BulkInsertSafe` concern, [...] SOURCE end end diff --git a/spec/rubocop/cop/gitlab/change_timezone_spec.rb b/spec/rubocop/cop/gitlab/change_timezone_spec.rb index 6abbc06bb1a..9cb822ec4f2 100644 --- a/spec/rubocop/cop/gitlab/change_timezone_spec.rb +++ b/spec/rubocop/cop/gitlab/change_timezone_spec.rb @@ -2,19 +2,16 @@ require 'fast_spec_helper' require 'rubocop' -require 'rubocop/rspec/support' require_relative '../../../../rubocop/cop/gitlab/change_timzone' RSpec.describe RuboCop::Cop::Gitlab::ChangeTimezone do - include CopHelper - subject(:cop) { described_class.new } context 'Time.zone=' do it 'registers an offense with no 2nd argument' do expect_offense(<<~PATTERN) Time.zone = 'Awkland' - ^^^^^^^^^^^^^^^^^^^^^ Do not change timezone in the runtime (application or rspec), it could result in silently modifying other behavior. + ^^^^^^^^^^^^^^^^^^^^^ Do not change timezone in the runtime (application or rspec), it could result [...] PATTERN end end diff --git a/spec/rubocop/cop/gitlab/const_get_inherit_false_spec.rb b/spec/rubocop/cop/gitlab/const_get_inherit_false_spec.rb index bed06ab2b17..19e5fe946be 100644 --- a/spec/rubocop/cop/gitlab/const_get_inherit_false_spec.rb +++ b/spec/rubocop/cop/gitlab/const_get_inherit_false_spec.rb @@ -2,78 +2,75 @@ require 'fast_spec_helper' require 'rubocop' -require 'rubocop/rspec/support' require_relative '../../../../rubocop/cop/gitlab/const_get_inherit_false' RSpec.describe RuboCop::Cop::Gitlab::ConstGetInheritFalse do - include CopHelper - subject(:cop) { described_class.new } context 'Object.const_get' do - it 'registers an offense with no 2nd argument' do + it 'registers an offense with no 2nd argument and corrects' do expect_offense(<<~PATTERN) Object.const_get(:CONSTANT) ^^^^^^^^^ Use inherit=false when using const_get. PATTERN - end - it 'autocorrects' do - expect(autocorrect_source('Object.const_get(:CONSTANT)')).to eq('Object.const_get(:CONSTANT, false)') + expect_correction(<<~PATTERN) + Object.const_get(:CONSTANT, false) + PATTERN end context 'inherit=false' do it 'does not register an offense' do expect_no_offenses(<<~PATTERN) - Object.const_get(:CONSTANT, false) + Object.const_get(:CONSTANT, false) PATTERN end end context 'inherit=true' do - it 'registers an offense' do + it 'registers an offense and corrects' do expect_offense(<<~PATTERN) - Object.const_get(:CONSTANT, true) - ^^^^^^^^^ Use inherit=false when using const_get. + Object.const_get(:CONSTANT, true) + ^^^^^^^^^ Use inherit=false when using const_get. PATTERN - end - it 'autocorrects' do - expect(autocorrect_source('Object.const_get(:CONSTANT, true)')).to eq('Object.const_get(:CONSTANT, false)') + expect_correction(<<~PATTERN) + Object.const_get(:CONSTANT, false) + PATTERN end end end context 'const_get for a nested class' do - it 'registers an offense on reload usage' do + it 'registers an offense on reload usage and corrects' do expect_offense(<<~PATTERN) Nested::Blog.const_get(:CONSTANT) ^^^^^^^^^ Use inherit=false when using const_get. PATTERN - end - it 'autocorrects' do - expect(autocorrect_source('Nested::Blag.const_get(:CONSTANT)')).to eq('Nested::Blag.const_get(:CONSTANT, false)') + expect_correction(<<~PATTERN) + Nested::Blog.const_get(:CONSTANT, false) + PATTERN end context 'inherit=false' do it 'does not register an offense' do expect_no_offenses(<<~PATTERN) - Nested::Blog.const_get(:CONSTANT, false) + Nested::Blog.const_get(:CONSTANT, false) PATTERN end end context 'inherit=true' do - it 'registers an offense if inherit is true' do + it 'registers an offense if inherit is true and corrects' do expect_offense(<<~PATTERN) - Nested::Blog.const_get(:CONSTANT, true) - ^^^^^^^^^ Use inherit=false when using const_get. + Nested::Blog.const_get(:CONSTANT, true) + ^^^^^^^^^ Use inherit=false when using const_get. PATTERN - end - it 'autocorrects' do - expect(autocorrect_source('Nested::Blag.const_get(:CONSTANT, true)')).to eq('Nested::Blag.const_get(:CONSTANT, false)') + expect_correction(<<~PATTERN) + Nested::Blog.const_get(:CONSTANT, false) + PATTERN end end end diff --git a/spec/rubocop/cop/gitlab/duplicate_spec_location_spec.rb b/spec/rubocop/cop/gitlab/duplicate_spec_location_spec.rb index 5804b03b641..a207155f432 100644 --- a/spec/rubocop/cop/gitlab/duplicate_spec_location_spec.rb +++ b/spec/rubocop/cop/gitlab/duplicate_spec_location_spec.rb @@ -6,8 +6,6 @@ require 'rubocop' require_relative '../../../../rubocop/cop/gitlab/duplicate_spec_location' RSpec.describe RuboCop::Cop::Gitlab::DuplicateSpecLocation do - include CopHelper - subject(:cop) { described_class.new } let(:rails_root) { '../../../../' } diff --git a/spec/rubocop/cop/gitlab/except_spec.rb b/spec/rubocop/cop/gitlab/except_spec.rb index 173e5943da5..7a122e3cf53 100644 --- a/spec/rubocop/cop/gitlab/except_spec.rb +++ b/spec/rubocop/cop/gitlab/except_spec.rb @@ -2,12 +2,9 @@ require 'fast_spec_helper' require 'rubocop' -require 'rubocop/rspec/support' require_relative '../../../../rubocop/cop/gitlab/except' RSpec.describe RuboCop::Cop::Gitlab::Except do - include CopHelper - subject(:cop) { described_class.new } it 'flags the use of Gitlab::SQL::Except.new' do diff --git a/spec/rubocop/cop/gitlab/finder_with_find_by_spec.rb b/spec/rubocop/cop/gitlab/finder_with_find_by_spec.rb index db3bcf1dfdb..03d7fc5e8b1 100644 --- a/spec/rubocop/cop/gitlab/finder_with_find_by_spec.rb +++ b/spec/rubocop/cop/gitlab/finder_with_find_by_spec.rb @@ -1,46 +1,31 @@ # frozen_string_literal: true require 'fast_spec_helper' - require 'rubocop' -require 'rubocop/rspec/support' require_relative '../../../../rubocop/cop/gitlab/finder_with_find_by' RSpec.describe RuboCop::Cop::Gitlab::FinderWithFindBy do - include CopHelper - subject(:cop) { described_class.new } context 'when calling execute.find' do - let(:source) do - <<~SRC - DummyFinder.new(some_args) - .execute - .find_by!(1) - SRC - end + it 'registers an offense and corrects' do + expect_offense(<<~CODE) + DummyFinder.new(some_args) + .execute + .find_by!(1) + ^^^^^^^^ Don't chain finders `#execute` method with [...] + CODE - let(:corrected_source) do - <<~SRC - DummyFinder.new(some_args) - .find_by!(1) - SRC - end - - it 'registers an offence' do - inspect_source(source) - - expect(cop.offenses.size).to eq(1) - end - - it 'can autocorrect the source' do - expect(autocorrect_source(source)).to eq(corrected_source) + expect_correction(<<~CODE) + DummyFinder.new(some_args) + .find_by!(1) + CODE end context 'when called within the `FinderMethods` module' do - let(:source) do - <<~SRC + it 'does not register an offense' do + expect_no_offenses(<<~SRC) module FinderMethods def find_by!(*args) execute.find_by!(args) @@ -48,12 +33,6 @@ RSpec.describe RuboCop::Cop::Gitlab::FinderWithFindBy do end SRC end - - it 'does not register an offence' do - inspect_source(source) - - expect(cop.offenses).to be_empty - end end end end diff --git a/spec/rubocop/cop/gitlab/httparty_spec.rb b/spec/rubocop/cop/gitlab/httparty_spec.rb index b112ac84bff..fcd18b0eb9b 100644 --- a/spec/rubocop/cop/gitlab/httparty_spec.rb +++ b/spec/rubocop/cop/gitlab/httparty_spec.rb @@ -2,46 +2,30 @@ require 'fast_spec_helper' require 'rubocop' -require 'rubocop/rspec/support' require_relative '../../../../rubocop/cop/gitlab/httparty' RSpec.describe RuboCop::Cop::Gitlab::HTTParty do # rubocop:disable RSpec/FilePath - include CopHelper - subject(:cop) { described_class.new } - shared_examples('registering include offense') do |options| - let(:offending_lines) { options[:offending_lines] } - + shared_examples('registering include offense') do it 'registers an offense when the class includes HTTParty' do - inspect_source(source) - - aggregate_failures do - expect(cop.offenses.size).to eq(offending_lines.size) - expect(cop.offenses.map(&:line)).to eq(offending_lines) - end + expect_offense(source) end end - shared_examples('registering call offense') do |options| - let(:offending_lines) { options[:offending_lines] } - + shared_examples('registering call offense') do it 'registers an offense when the class calls HTTParty' do - inspect_source(source) - - aggregate_failures do - expect(cop.offenses.size).to eq(offending_lines.size) - expect(cop.offenses.map(&:line)).to eq(offending_lines) - end + expect_offense(source) end end context 'when source is a regular module' do - it_behaves_like 'registering include offense', offending_lines: [2] do + it_behaves_like 'registering include offense' do let(:source) do <<~RUBY module M include HTTParty + ^^^^^^^^^^^^^^^^ Avoid including `HTTParty` directly. [...] end RUBY end @@ -49,11 +33,12 @@ RSpec.describe RuboCop::Cop::Gitlab::HTTParty do # rubocop:disable RSpec/FilePat end context 'when source is a regular class' do - it_behaves_like 'registering include offense', offending_lines: [2] do + it_behaves_like 'registering include offense' do let(:source) do <<~RUBY class Foo include HTTParty + ^^^^^^^^^^^^^^^^ Avoid including `HTTParty` directly. [...] end RUBY end @@ -61,12 +46,13 @@ RSpec.describe RuboCop::Cop::Gitlab::HTTParty do # rubocop:disable RSpec/FilePat end context 'when HTTParty is called' do - it_behaves_like 'registering call offense', offending_lines: [3] do + it_behaves_like 'registering call offense' do let(:source) do <<~RUBY class Foo def bar HTTParty.get('http://example.com') + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Avoid calling `HTTParty` directly. [...] end end RUBY diff --git a/spec/rubocop/cop/gitlab/intersect_spec.rb b/spec/rubocop/cop/gitlab/intersect_spec.rb index e724f47029c..6f0367591cd 100644 --- a/spec/rubocop/cop/gitlab/intersect_spec.rb +++ b/spec/rubocop/cop/gitlab/intersect_spec.rb @@ -2,12 +2,9 @@ require 'fast_spec_helper' require 'rubocop' -require 'rubocop/rspec/support' require_relative '../../../../rubocop/cop/gitlab/intersect' RSpec.describe RuboCop::Cop::Gitlab::Intersect do - include CopHelper - subject(:cop) { described_class.new } it 'flags the use of Gitlab::SQL::Intersect.new' do diff --git a/spec/rubocop/cop/gitlab/json_spec.rb b/spec/rubocop/cop/gitlab/json_spec.rb index fc25f69a244..29c3b96cc1a 100644 --- a/spec/rubocop/cop/gitlab/json_spec.rb +++ b/spec/rubocop/cop/gitlab/json_spec.rb @@ -2,38 +2,21 @@ require 'fast_spec_helper' require 'rubocop' -require 'rubocop/rspec/support' require_relative '../../../../rubocop/cop/gitlab/json' RSpec.describe RuboCop::Cop::Gitlab::Json do - include CopHelper - subject(:cop) { described_class.new } - shared_examples('registering call offense') do |options| - let(:offending_lines) { options[:offending_lines] } - - it 'registers an offense when the class calls JSON' do - inspect_source(source) - - aggregate_failures do - expect(cop.offenses.size).to eq(offending_lines.size) - expect(cop.offenses.map(&:line)).to eq(offending_lines) - end - end - end - context 'when JSON is called' do - it_behaves_like 'registering call offense', offending_lines: [3] do - let(:source) do - <<~RUBY - class Foo - def bar - JSON.parse('{ "foo": "bar" }') - end + it 'registers an offense' do + expect_offense(<<~RUBY) + class Foo + def bar + JSON.parse('{ "foo": "bar" }') + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Avoid calling `JSON` directly. [...] end - RUBY - end + end + RUBY end end end diff --git a/spec/rubocop/cop/gitlab/module_with_instance_variables_spec.rb b/spec/rubocop/cop/gitlab/module_with_instance_variables_spec.rb index 1d09c720bf7..08634d5753a 100644 --- a/spec/rubocop/cop/gitlab/module_with_instance_variables_spec.rb +++ b/spec/rubocop/cop/gitlab/module_with_instance_variables_spec.rb @@ -2,42 +2,33 @@ require 'fast_spec_helper' require 'rubocop' -require 'rubocop/rspec/support' require_relative '../../../../rubocop/cop/gitlab/module_with_instance_variables' RSpec.describe RuboCop::Cop::Gitlab::ModuleWithInstanceVariables do - include CopHelper + let(:msg) { "Do not use instance variables in a module. [...]" } subject(:cop) { described_class.new } - shared_examples('registering offense') do |options| - let(:offending_lines) { options[:offending_lines] } - + shared_examples('registering offense') do it 'registers an offense when instance variable is used in a module' do - inspect_source(source) - - aggregate_failures do - expect(cop.offenses.size).to eq(offending_lines.size) - expect(cop.offenses.map(&:line)).to eq(offending_lines) - end + expect_offense(source) end end shared_examples('not registering offense') do it 'does not register offenses' do - inspect_source(source) - - expect(cop.offenses).to be_empty + expect_no_offenses(source) end end context 'when source is a regular module' do - it_behaves_like 'registering offense', offending_lines: [3] do + it_behaves_like 'registering offense' do let(:source) do <<~RUBY module M def f @f = true + ^^^^^^^^^ #{msg} end end RUBY @@ -46,13 +37,14 @@ RSpec.describe RuboCop::Cop::Gitlab::ModuleWithInstanceVariables do end context 'when source is a nested module' do - it_behaves_like 'registering offense', offending_lines: [4] do + it_behaves_like 'registering offense' do let(:source) do <<~RUBY module N module M def f @f = true + ^^^^^^^^^ #{msg} end end end @@ -62,13 +54,14 @@ RSpec.describe RuboCop::Cop::Gitlab::ModuleWithInstanceVariables do end context 'when source is a nested module with multiple offenses' do - it_behaves_like 'registering offense', offending_lines: [4, 12] do + it_behaves_like 'registering offense' do let(:source) do <<~RUBY module N module M def f @f = true + ^^^^^^^^^ #{msg} end def g @@ -77,6 +70,7 @@ RSpec.describe RuboCop::Cop::Gitlab::ModuleWithInstanceVariables do def h @h = true + ^^^^^^^^^ #{msg} end end end @@ -129,12 +123,13 @@ RSpec.describe RuboCop::Cop::Gitlab::ModuleWithInstanceVariables do end context 'when source is using simple or ivar assignment with other ivar' do - it_behaves_like 'registering offense', offending_lines: [3] do + it_behaves_like 'registering offense' do let(:source) do <<~RUBY module M def f @f ||= g(@g) + ^^ #{msg} end end RUBY @@ -143,13 +138,15 @@ RSpec.describe RuboCop::Cop::Gitlab::ModuleWithInstanceVariables do end context 'when source is using or ivar assignment with something else' do - it_behaves_like 'registering offense', offending_lines: [3, 4] do + it_behaves_like 'registering offense' do let(:source) do <<~RUBY module M def f @f ||= true + ^^ #{msg} @f.to_s + ^^ #{msg} end end RUBY diff --git a/spec/rubocop/cop/gitlab/policy_rule_boolean_spec.rb b/spec/rubocop/cop/gitlab/policy_rule_boolean_spec.rb index e6fb9ab9d57..6dbbcdd8324 100644 --- a/spec/rubocop/cop/gitlab/policy_rule_boolean_spec.rb +++ b/spec/rubocop/cop/gitlab/policy_rule_boolean_spec.rb @@ -2,12 +2,9 @@ require 'fast_spec_helper' require 'rubocop' -require 'rubocop/rspec/support' require_relative '../../../../rubocop/cop/gitlab/policy_rule_boolean' RSpec.describe RuboCop::Cop::Gitlab::PolicyRuleBoolean do - include CopHelper - subject(:cop) { described_class.new } it 'registers offense for &&' do diff --git a/spec/rubocop/cop/gitlab/predicate_memoization_spec.rb b/spec/rubocop/cop/gitlab/predicate_memoization_spec.rb index 322c7c82968..071ddcf8b7d 100644 --- a/spec/rubocop/cop/gitlab/predicate_memoization_spec.rb +++ b/spec/rubocop/cop/gitlab/predicate_memoization_spec.rb @@ -2,59 +2,14 @@ require 'fast_spec_helper' require 'rubocop' -require 'rubocop/rspec/support' require_relative '../../../../rubocop/cop/gitlab/predicate_memoization' RSpec.describe RuboCop::Cop::Gitlab::PredicateMemoization do - include CopHelper - subject(:cop) { described_class.new } - shared_examples('registering offense') do |options| - let(:offending_lines) { options[:offending_lines] } - - it 'registers an offense when a predicate method is memoizing via ivar' do - inspect_source(source) - - aggregate_failures do - expect(cop.offenses.size).to eq(offending_lines.size) - expect(cop.offenses.map(&:line)).to eq(offending_lines) - end - end - end - shared_examples('not registering offense') do it 'does not register offenses' do - inspect_source(source) - - expect(cop.offenses).to be_empty - end - end - - context 'when source is a predicate method memoizing via ivar' do - it_behaves_like 'registering offense', offending_lines: [3] do - let(:source) do - <<~RUBY - class C - def really? - @really ||= true - end - end - RUBY - end - end - - it_behaves_like 'registering offense', offending_lines: [4] do - let(:source) do - <<~RUBY - class C - def really? - value = true - @really ||= value - end - end - RUBY - end + expect_no_offenses(source) end end @@ -99,4 +54,39 @@ RSpec.describe RuboCop::Cop::Gitlab::PredicateMemoization do end end end + + context 'when source is a predicate method memoizing via ivar' do + let(:msg) { "Avoid using `@value ||= query` [...]" } + + context 'when assigning to boolean' do + it 'registers an offense' do + node = "@really ||= true" + + expect_offense(<<~CODE, node: node, msg: msg) + class C + def really? + %{node} + ^{node} %{msg} + end + end + CODE + end + end + + context 'when assigning to another variable that is a boolean' do + it 'registers an offense' do + node = "@really ||= value" + + expect_offense(<<~CODE, node: node, msg: msg) + class C + def really? + value = true + %{node} + ^{node} %{msg} + end + end + CODE + end + end + end end diff --git a/spec/rubocop/cop/gitlab/rails_logger_spec.rb b/spec/rubocop/cop/gitlab/rails_logger_spec.rb index 768da243b02..7258b047191 100644 --- a/spec/rubocop/cop/gitlab/rails_logger_spec.rb +++ b/spec/rubocop/cop/gitlab/rails_logger_spec.rb @@ -2,37 +2,31 @@ require 'fast_spec_helper' require 'rubocop' -require 'rubocop/rspec/support' require_relative '../../../../rubocop/cop/gitlab/rails_logger' RSpec.describe RuboCop::Cop::Gitlab::RailsLogger do - include CopHelper - subject(:cop) { described_class.new } described_class::LOG_METHODS.each do |method| it "flags the use of Rails.logger.#{method} with a constant receiver" do - inspect_source("Rails.logger.#{method}('some error')") + node = "Rails.logger.#{method}('some error')" - expect(cop.offenses.size).to eq(1) + expect_offense(<<~CODE, node: node, msg: "Use a structured JSON logger instead of `Rails.logger`. [...]") + %{node} + ^{node} %{msg} + CODE end end it 'does not flag the use of Rails.logger with a constant that is not Rails' do - inspect_source("AppLogger.error('some error')") - - expect(cop.offenses.size).to eq(0) + expect_no_offenses("AppLogger.error('some error')") end it 'does not flag the use of logger with a send receiver' do - inspect_source("file_logger.info('important info')") - - expect(cop.offenses.size).to eq(0) + expect_no_offenses("file_logger.info('important info')") end it 'does not flag the use of Rails.logger.level' do - inspect_source("Rails.logger.level") - - expect(cop.offenses.size).to eq(0) + expect_no_offenses("Rails.logger.level") end end diff --git a/spec/rubocop/cop/gitlab/union_spec.rb b/spec/rubocop/cop/gitlab/union_spec.rb index 20364b1b901..04a3db8e7dd 100644 --- a/spec/rubocop/cop/gitlab/union_spec.rb +++ b/spec/rubocop/cop/gitlab/union_spec.rb @@ -2,12 +2,9 @@ require 'fast_spec_helper' require 'rubocop' -require 'rubocop/rspec/support' require_relative '../../../../rubocop/cop/gitlab/union' RSpec.describe RuboCop::Cop::Gitlab::Union do - include CopHelper - subject(:cop) { described_class.new } it 'flags the use of Gitlab::SQL::Union.new' do diff --git a/spec/rubocop/cop/group_public_or_visible_to_user_spec.rb b/spec/rubocop/cop/group_public_or_visible_to_user_spec.rb index ac6c481a7c3..b3ec426dc07 100644 --- a/spec/rubocop/cop/group_public_or_visible_to_user_spec.rb +++ b/spec/rubocop/cop/group_public_or_visible_to_user_spec.rb @@ -2,29 +2,28 @@ require 'fast_spec_helper' require 'rubocop' -require 'rubocop/rspec/support' require_relative '../../../rubocop/cop/group_public_or_visible_to_user' RSpec.describe RuboCop::Cop::GroupPublicOrVisibleToUser do - include CopHelper + let(:msg) do + "`Group.public_or_visible_to_user` should be used with extreme care. " \ + "Please ensure that you are not using it on its own and that the amount of rows being filtered is reasonable." + end subject(:cop) { described_class.new } it 'flags the use of Group.public_or_visible_to_user with a constant receiver' do - inspect_source('Group.public_or_visible_to_user') - - expect(cop.offenses.size).to eq(1) + expect_offense(<<~CODE) + Group.public_or_visible_to_user + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{msg} + CODE end - it 'does not flat the use of public_or_visible_to_user with a constant that is not Group' do - inspect_source('Project.public_or_visible_to_user') - - expect(cop.offenses.size).to eq(0) + it 'does not flag the use of public_or_visible_to_user with a constant that is not Group' do + expect_no_offenses('Project.public_or_visible_to_user') end it 'does not flag the use of Group.public_or_visible_to_user with a send receiver' do - inspect_source('foo.public_or_visible_to_user') - - expect(cop.offenses.size).to eq(0) + expect_no_offenses('foo.public_or_visible_to_user') end end diff --git a/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb b/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb index 47247006e42..2d293fd0a05 100644 --- a/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb +++ b/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb @@ -2,12 +2,9 @@ require 'fast_spec_helper' require 'rubocop' -require 'rubocop/rspec/support' require_relative '../../../rubocop/cop/inject_enterprise_edition_module' RSpec.describe RuboCop::Cop::InjectEnterpriseEditionModule do - include CopHelper - subject(:cop) { described_class.new } it 'flags the use of `prepend_if_ee EE` in the middle of a file' do @@ -185,18 +182,19 @@ RSpec.describe RuboCop::Cop::InjectEnterpriseEditionModule do end it 'autocorrects offenses by just disabling the Cop' do - source = <<~SOURCE - class Foo - prepend_if_ee 'EE::Foo' - include_if_ee 'Bar' - end + expect_offense(<<~SOURCE) + class Foo + prepend_if_ee 'EE::Foo' + ^^^^^^^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions + include_if_ee 'Bar' + end SOURCE - expect(autocorrect_source(source)).to eq(<<~SOURCE) - class Foo - prepend_if_ee 'EE::Foo' # rubocop: disable Cop/InjectEnterpriseEditionModule - include_if_ee 'Bar' - end + expect_correction(<<~SOURCE) + class Foo + prepend_if_ee 'EE::Foo' # rubocop: disable Cop/InjectEnterpriseEditionModule + include_if_ee 'Bar' + end SOURCE end diff --git a/spec/rubocop/cop/lint/last_keyword_argument_spec.rb b/spec/rubocop/cop/lint/last_keyword_argument_spec.rb index 826c681a880..aac59f0db4c 100644 --- a/spec/rubocop/cop/lint/last_keyword_argument_spec.rb +++ b/spec/rubocop/cop/lint/last_keyword_argument_spec.rb @@ -5,8 +5,6 @@ require 'rubocop' require_relative '../../../../rubocop/cop/lint/last_keyword_argument' RSpec.describe RuboCop::Cop::Lint::LastKeywordArgument do - include CopHelper - subject(:cop) { described_class.new } before do diff --git a/spec/rubocop/cop/put_project_routes_under_scope_spec.rb b/spec/rubocop/cop/put_project_routes_under_scope_spec.rb index b0627af0e8b..eb783d22129 100644 --- a/spec/rubocop/cop/put_project_routes_under_scope_spec.rb +++ b/spec/rubocop/cop/put_project_routes_under_scope_spec.rb @@ -5,8 +5,6 @@ require 'rubocop' require_relative '../../../rubocop/cop/put_project_routes_under_scope' RSpec.describe RuboCop::Cop::PutProjectRoutesUnderScope do - include CopHelper - subject(:cop) { described_class.new } %w[resource resources get post put patch delete].each do |route_method| diff --git a/spec/rubocop/cop/qa/ambiguous_page_object_name_spec.rb b/spec/rubocop/cop/qa/ambiguous_page_object_name_spec.rb index 4876fcd5050..9332ab4186e 100644 --- a/spec/rubocop/cop/qa/ambiguous_page_object_name_spec.rb +++ b/spec/rubocop/cop/qa/ambiguous_page_object_name_spec.rb @@ -1,15 +1,11 @@ # frozen_string_literal: true require 'fast_spec_helper' - require 'rubocop' -require 'rubocop/rspec/support' require_relative '../../../../rubocop/cop/qa/ambiguous_page_object_name' RSpec.describe RuboCop::Cop::QA::AmbiguousPageObjectName do - include CopHelper - let(:source_file) { 'qa/page.rb' } subject(:cop) { described_class.new } diff --git a/spec/rubocop/cop/qa/element_with_pattern_spec.rb b/spec/rubocop/cop/qa/element_with_pattern_spec.rb index 6289b1a7c97..28c351ccf1e 100644 --- a/spec/rubocop/cop/qa/element_with_pattern_spec.rb +++ b/spec/rubocop/cop/qa/element_with_pattern_spec.rb @@ -1,15 +1,11 @@ # frozen_string_literal: true require 'fast_spec_helper' - require 'rubocop' -require 'rubocop/rspec/support' require_relative '../../../../rubocop/cop/qa/element_with_pattern' RSpec.describe RuboCop::Cop::QA::ElementWithPattern do - include CopHelper - let(:source_file) { 'qa/page.rb' } subject(:cop) { described_class.new } diff --git a/spec/rubocop/cop/ruby_interpolation_in_translation_spec.rb b/spec/rubocop/cop/ruby_interpolation_in_translation_spec.rb index a6a44b3fa68..cacf0a1b67d 100644 --- a/spec/rubocop/cop/ruby_interpolation_in_translation_spec.rb +++ b/spec/rubocop/cop/ruby_interpolation_in_translation_spec.rb @@ -1,68 +1,51 @@ # frozen_string_literal: true require 'fast_spec_helper' - require 'rubocop' -require 'rubocop/rspec/support' require_relative '../../../rubocop/cop/ruby_interpolation_in_translation' # Disabling interpolation check as we deliberately want to have #{} in strings. # rubocop:disable Lint/InterpolationCheck RSpec.describe RuboCop::Cop::RubyInterpolationInTranslation do + let(:msg) { "Don't use ruby interpolation \#{} inside translated strings, instead use %{}" } + subject(:cop) { described_class.new } - it 'does not add an offence for a regular messages' do - inspect_source('_("Hello world")') - - expect(cop.offenses).to be_empty + it 'does not add an offense for a regular messages' do + expect_no_offenses('_("Hello world")') end - it 'adds the correct offence when using interpolation in a string' do - inspect_source('_("Hello #{world}")') - - offense = cop.offenses.first - - expect(offense.location.source).to eq('#{world}') - expect(offense.message).to eq('Don\'t use ruby interpolation #{} inside translated strings, instead use %{}') + it 'adds the correct offense when using interpolation in a string' do + expect_offense(<<~CODE) + _("Hello \#{world}") + ^^^^^ #{msg} + ^^^^^^^^ #{msg} + CODE end it 'detects when using a ruby interpolation in the first argument of a pluralized string' do - inspect_source('n_("Hello #{world}", "Hello world")') - - expect(cop.offenses).not_to be_empty + expect_offense(<<~CODE) + n_("Hello \#{world}", "Hello world") + ^^^^^ #{msg} + ^^^^^^^^ #{msg} + CODE end it 'detects when using a ruby interpolation in the second argument of a pluralized string' do - inspect_source('n_("Hello world", "Hello #{world}")') - - expect(cop.offenses).not_to be_empty + expect_offense(<<~CODE) + n_("Hello world", "Hello \#{world}") + ^^^^^ #{msg} + ^^^^^^^^ #{msg} + CODE end it 'detects when using interpolation in a namespaced translation' do - inspect_source('s_("Hello|#{world}")') - - expect(cop.offenses).not_to be_empty - end - - it 'does not add an offence for messages defined over multiple lines' do - source = <<~SRC - _("Hello "\ - "world ") - SRC - - inspect_source(source) - expect(cop.offenses).to be_empty - end - - it 'adds an offence for violations in a message defined over multiple lines' do - source = <<~SRC - _("Hello "\ - "\#{world} ") - SRC - - inspect_source(source) - expect(cop.offenses).not_to be_empty + expect_offense(<<~CODE) + s_("Hello|\#{world}") + ^^^^^ #{msg} + ^^^^^^^^ #{msg} + CODE end end # rubocop:enable Lint/InterpolationCheck diff --git a/spec/rubocop/cop/static_translation_definition_spec.rb b/spec/rubocop/cop/static_translation_definition_spec.rb index 8a38a318999..8656b07a6e4 100644 --- a/spec/rubocop/cop/static_translation_definition_spec.rb +++ b/spec/rubocop/cop/static_translation_definition_spec.rb @@ -8,78 +8,76 @@ require 'rspec-parameterized' require_relative '../../../rubocop/cop/static_translation_definition' RSpec.describe RuboCop::Cop::StaticTranslationDefinition do - include CopHelper - using RSpec::Parameterized::TableSyntax + let(:msg) do + "The text you're translating will be already in the translated form when it's assigned to the constant. " \ + "When a users changes the locale, these texts won't be translated again. " \ + "Consider moving the translation logic to a method." + end + subject(:cop) { described_class.new } - shared_examples 'offense' do |code, highlight, line| + shared_examples 'offense' do |code| it 'registers an offense' do - inspect_source(code) - - expect(cop.offenses.size).to eq(1) - expect(cop.offenses.map(&:line)).to eq([line]) - expect(cop.highlights).to eq([highlight]) + expect_offense(code) end end shared_examples 'no offense' do |code| it 'does not register an offense' do - inspect_source(code) - - expect(cop.offenses).to be_empty + expect_no_offenses(code) end end describe 'offenses' do - where(:code, :highlight, :line) do + where(:code) do [ - ['A = _("a")', '_("a")', 1], - ['B = s_("b")', 's_("b")', 1], - ['C = n_("c")', 'n_("c")', 1], - [ - <<~CODE, - class MyClass - def self.translations - @cache ||= { hello: _("hello") } - end + <<~CODE, + A = _("a") + ^^^^^^ #{msg} + CODE + <<~CODE, + B = s_("b") + ^^^^^^^ #{msg} + CODE + <<~CODE, + C = n_("c") + ^^^^^^^ #{msg} + CODE + <<~CODE, + class MyClass + def self.translations + @cache ||= { hello: _("hello") } + ^^^^^^^^^^ #{msg} end - CODE - '_("hello")', - 3 - ], - [ - <<~CODE, - module MyModule - A = { - b: { - c: _("a") - } + end + CODE + <<~CODE, + module MyModule + A = { + b: { + c: _("a") + ^^^^^^ #{msg} } - end - CODE - '_("a")', - 4 - ], - [ - <<~CODE, - class MyClass - B = [ - [ - s_("a") - ] + } + end + CODE + <<~CODE + class MyClass + B = [ + [ + s_("a") + ^^^^^^^ #{msg} ] - end - CODE - 's_("a")', - 4 - ] + ] + end + CODE ] end with_them do - include_examples 'offense', params[:code], params[:highlight], params[:line] + include_examples 'offense', params[:code] end end diff --git a/spec/rubocop/cop/usage_data/distinct_count_by_large_foreign_key_spec.rb b/spec/rubocop/cop/usage_data/distinct_count_by_large_foreign_key_spec.rb index 1c90df798a5..b6711effe9e 100644 --- a/spec/rubocop/cop/usage_data/distinct_count_by_large_foreign_key_spec.rb +++ b/spec/rubocop/cop/usage_data/distinct_count_by_large_foreign_key_spec.rb @@ -1,17 +1,13 @@ # frozen_string_literal: true require 'fast_spec_helper' - require 'rubocop' -require 'rubocop/rspec/support' require_relative '../../../../rubocop/cop/usage_data/distinct_count_by_large_foreign_key' RSpec.describe RuboCop::Cop::UsageData::DistinctCountByLargeForeignKey do - include CopHelper - let(:allowed_foreign_keys) { [:author_id, :user_id, :'merge_requests.target_project_id'] } - + let(:msg) { 'Avoid doing `distinct_count` on foreign keys for large tables having above 100 million rows.' } let(:config) do RuboCop::Config.new('UsageData/DistinctCountByLargeForeignKey' => { 'AllowedForeignKeys' => allowed_foreign_keys @@ -21,36 +17,32 @@ RSpec.describe RuboCop::Cop::UsageData::DistinctCountByLargeForeignKey do subject(:cop) { described_class.new(config) } context 'when counting by disallowed key' do - it 'registers an offence' do - inspect_source('distinct_count(Issue, :creator_id)') - - expect(cop.offenses.size).to eq(1) + it 'registers an offense' do + expect_offense(<<~CODE) + distinct_count(Issue, :creator_id) + ^^^^^^^^^^^^^^ #{msg} + CODE end - it 'does not register an offence when batch is false' do - inspect_source('distinct_count(Issue, :creator_id, batch: false)') - - expect(cop.offenses).to be_empty + it 'does not register an offense when batch is false' do + expect_no_offenses('distinct_count(Issue, :creator_id, batch: false)') end - it 'register an offence when batch is true' do - inspect_source('distinct_count(Issue, :creator_id, batch: true)') - - expect(cop.offenses.size).to eq(1) + it 'registers an offense when batch is true' do + expect_offense(<<~CODE) + distinct_count(Issue, :creator_id, batch: true) + ^^^^^^^^^^^^^^ #{msg} + CODE end end context 'when calling by allowed key' do - it 'does not register an offence with symbol' do - inspect_source('distinct_count(Issue, :author_id)') - - expect(cop.offenses).to be_empty + it 'does not register an offense with symbol' do + expect_no_offenses('distinct_count(Issue, :author_id)') end - it 'does not register an offence with string' do - inspect_source("distinct_count(Issue, 'merge_requests.target_project_id')") - - expect(cop.offenses).to be_empty + it 'does not register an offense with string' do + expect_no_offenses("distinct_count(Issue, 'merge_requests.target_project_id')") end end end diff --git a/spec/rubocop/cop/usage_data/large_table_spec.rb b/spec/rubocop/cop/usage_data/large_table_spec.rb index 638e8c67dc8..26bd4e61625 100644 --- a/spec/rubocop/cop/usage_data/large_table_spec.rb +++ b/spec/rubocop/cop/usage_data/large_table_spec.rb @@ -1,18 +1,15 @@ # frozen_string_literal: true require 'fast_spec_helper' - require 'rubocop' -require 'rubocop/rspec/support' require_relative '../../../../rubocop/cop/usage_data/large_table' RSpec.describe RuboCop::Cop::UsageData::LargeTable do - include CopHelper - let(:large_tables) { %i[Rails Time] } let(:count_methods) { %i[count distinct_count] } let(:allowed_methods) { %i[minimum maximum] } + let(:msg) { 'Use one of the count, distinct_count methods for counting on' } let(:config) do RuboCop::Config.new('UsageData/LargeTable' => { @@ -31,59 +28,54 @@ RSpec.describe RuboCop::Cop::UsageData::LargeTable do context 'with large tables' do context 'when calling Issue.count' do - it 'register an offence' do - inspect_source('Issue.count') - - expect(cop.offenses.size).to eq(1) + it 'registers an offense' do + expect_offense(<<~CODE) + Issue.count + ^^^^^^^^^^^ #{msg} Issue + CODE end end context 'when calling Issue.active.count' do - it 'register an offence' do - inspect_source('Issue.active.count') - - expect(cop.offenses.size).to eq(1) + it 'registers an offense' do + expect_offense(<<~CODE) + Issue.active.count + ^^^^^^^^^^^^ #{msg} Issue + CODE end end context 'when calling count(Issue)' do - it 'does not register an offence' do - inspect_source('count(Issue)') - - expect(cop.offenses).to be_empty + it 'does not register an offense' do + expect_no_offenses('count(Issue)') end end context 'when calling count(Ci::Build.active)' do - it 'does not register an offence' do - inspect_source('count(Ci::Build.active)') - - expect(cop.offenses).to be_empty + it 'does not register an offense' do + expect_no_offenses('count(Ci::Build.active)') end end context 'when calling Ci::Build.active.count' do - it 'register an offence' do - inspect_source('Ci::Build.active.count') - - expect(cop.offenses.size).to eq(1) + it 'registers an offense' do + expect_offense(<<~CODE) + Ci::Build.active.count + ^^^^^^^^^^^^^^^^ #{msg} Ci::Build + CODE end end context 'when using allowed methods' do - it 'does not register an offence' do - inspect_source('Issue.minimum') - - expect(cop.offenses).to be_empty + it 'does not register an offense' do + expect_no_offenses('Issue.minimum') end end end context 'with non related class' do - it 'does not register an offence' do - inspect_source('Rails.count') - - expect(cop.offenses).to be_empty + it 'does not register an offense' do + expect_no_offenses('Rails.count') end end end diff --git a/spec/services/git/wiki_push_service_spec.rb b/spec/services/git/wiki_push_service_spec.rb index cd38f2e97fb..0431e06723f 100644 --- a/spec/services/git/wiki_push_service_spec.rb +++ b/spec/services/git/wiki_push_service_spec.rb @@ -257,6 +257,56 @@ RSpec.describe Git::WikiPushService, services: true do end end + describe '#perform_housekeeping', :clean_gitlab_redis_shared_state do + let(:housekeeping) { Repositories::HousekeepingService.new(wiki) } + + subject { create_service(current_sha).execute } + + before do + allow(Repositories::HousekeepingService).to receive(:new).and_return(housekeeping) + end + + it 'does not perform housekeeping when not needed' do + expect(housekeeping).not_to receive(:execute) + + subject + end + + context 'when housekeeping is needed' do + before do + allow(housekeeping).to receive(:needed?).and_return(true) + end + + it 'performs housekeeping' do + expect(housekeeping).to receive(:execute) + + subject + end + + it 'does not raise an exception' do + allow(housekeeping).to receive(:try_obtain_lease).and_return(false) + + expect { subject }.not_to raise_error + end + + context 'when feature flag :wiki_housekeeping is disabled' do + it 'does not perform housekeeping' do + stub_feature_flags(wiki_housekeeping: false) + + expect(housekeeping).not_to receive(:execute) + + subject + end + end + end + + it 'increments the push counter' do + expect(housekeeping).to receive(:increment!) + + subject + end + end + # In order to construct the correct GitPostReceive object that represents the # changes we are applying, we need to describe the changes between old-ref and # new-ref. Old ref (the base sha) we have to capture before we perform any diff --git a/spec/tooling/danger/feature_flag_spec.rb b/spec/tooling/danger/feature_flag_spec.rb new file mode 100644 index 00000000000..1ee79e3c74e --- /dev/null +++ b/spec/tooling/danger/feature_flag_spec.rb @@ -0,0 +1,112 @@ +# frozen_string_literal: true + +require_relative 'danger_spec_helper' + +require_relative '../../../tooling/danger/feature_flag' + +RSpec.describe Tooling::Danger::FeatureFlag do + include DangerSpecHelper + + let(:added_files) { nil } + let(:fake_git) { double('fake-git', added_files: added_files) } + + let(:mr_labels) { nil } + let(:mr_json) { nil } + let(:fake_gitlab) { double('fake-gitlab', mr_labels: mr_labels, mr_json: mr_json) } + + let(:changes_by_category) { nil } + let(:sanitize_mr_title) { nil } + let(:ee?) { false } + let(:fake_helper) { double('fake-helper', changes_by_category: changes_by_category, sanitize_mr_title: sanitize_mr_title, ee?: ee?) } + + let(:fake_danger) { new_fake_danger.include(described_class) } + + subject(:feature_flag) { fake_danger.new(git: fake_git, gitlab: fake_gitlab, helper: fake_helper) } + + describe '#feature_flag_files' do + context 'added files contain several feature flags' do + let(:added_files) do + [ + 'config/feature_flags/development/entry.yml', + 'ee/config/feature_flags/ops/entry.yml' + ] + end + + it 'returns an array of Found objects' do + expect(feature_flag.feature_flag_files).to contain_exactly(an_instance_of(described_class::Found), an_instance_of(described_class::Found)) + end + end + + context 'added files do not contain a feature_flag' do + let(:added_files) do + [ + 'app/models/model.rb', + 'app/assets/javascripts/file.js' + ] + end + + it 'returns the feature flag file path' do + expect(feature_flag.feature_flag_files).to be_empty + end + end + end + + describe described_class::Found do + let(:feature_flag_path) { 'config/feature_flags/development/entry.yml' } + let(:group) { 'group::source code' } + let(:raw_yaml) do + YAML.dump('group' => group) + end + + subject(:found) { described_class.new(feature_flag_path) } + + before do + allow(File).to receive(:read).and_call_original + expect(File).to receive(:read).with(feature_flag_path).and_return(raw_yaml) + end + + describe '#raw' do + it 'returns the raw YAML' do + expect(found.raw).to eq(raw_yaml) + end + end + + describe '#group' do + it 'returns the group found in the YAML' do + expect(found.group).to eq(group) + end + end + + describe '#group_match_mr_label?' do + subject(:result) { found.group_match_mr_label?(mr_group_label) } + + context 'when MR labels match FF group' do + let(:mr_group_label) { 'group::source code' } + + specify { expect(result).to eq(true) } + end + + context 'when MR labels does not match FF group' do + let(:mr_group_label) { 'group::access' } + + specify { expect(result).to eq(false) } + end + + context 'when group is nil' do + let(:group) { nil } + + context 'and MR has no group label' do + let(:mr_group_label) { nil } + + specify { expect(result).to eq(true) } + end + + context 'and MR has a group label' do + let(:mr_group_label) { 'group::source code' } + + specify { expect(result).to eq(false) } + end + end + end + end +end diff --git a/spec/tooling/danger/helper_spec.rb b/spec/tooling/danger/helper_spec.rb index 69aefddc536..40170dcd355 100644 --- a/spec/tooling/danger/helper_spec.rb +++ b/spec/tooling/danger/helper_spec.rb @@ -599,4 +599,14 @@ RSpec.describe Tooling::Danger::Helper do end end end + + describe '#group_label' do + it 'returns nil when no group label is present' do + expect(helper.group_label(%w[foo bar])).to be_nil + end + + it 'returns the group label when a group label is present' do + expect(helper.group_label(['foo', 'group::source code', 'bar'])).to eq('group::source code') + end + end end diff --git a/tooling/danger/feature_flag.rb b/tooling/danger/feature_flag.rb new file mode 100644 index 00000000000..27352457f95 --- /dev/null +++ b/tooling/danger/feature_flag.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require 'yaml' + +module Tooling + module Danger + module FeatureFlag + def feature_flag_files + @feature_flag_files ||= git.added_files.select { |path| path =~ %r{\A(ee/)?config/feature_flags/} }.map { |path| Found.new(path) } + end + + class Found + attr_reader :path + + def initialize(path) + @path = path + end + + def raw + @raw ||= File.read(path) + end + + def group + @group ||= yaml['group'] + end + + def group_match_mr_label?(mr_group_label) + mr_group_label == group + end + + private + + def yaml + @yaml ||= YAML.safe_load(raw) + end + end + end + end +end diff --git a/tooling/danger/helper.rb b/tooling/danger/helper.rb index 70df0dd82c7..afb6220e116 100644 --- a/tooling/danger/helper.rb +++ b/tooling/danger/helper.rb @@ -260,13 +260,17 @@ module Tooling all_changed_files.grep(regex) end - def has_database_scoped_labels?(current_mr_labels) - current_mr_labels.any? { |label| label.start_with?('database::') } + def has_database_scoped_labels?(labels) + labels.any? { |label| label.start_with?('database::') } end def has_ci_changes? changed_files(%r{\A(\.gitlab-ci\.yml|\.gitlab/ci/)}).any? end + + def group_label(labels) + labels.find { |label| label.start_with?('group::') } + end end end end diff --git a/tooling/gitlab_danger.rb b/tooling/gitlab_danger.rb index ca62e93a59d..d20d3499641 100644 --- a/tooling/gitlab_danger.rb +++ b/tooling/gitlab_danger.rb @@ -4,28 +4,29 @@ class GitlabDanger LOCAL_RULES ||= %w[ changes_size + commit_messages + database documentation duplicate_yarn_dependencies - prettier eslint karma - database - commit_messages - product_intelligence - utility_css pajamas pipeline + prettier + product_intelligence + utility_css ].freeze CI_ONLY_RULES ||= %w[ - metadata - changelog - specs - roulette ce_ee_vue_templates + changelog + ci_templates + metadata + feature_flag + roulette sidekiq_queues specialization_labels - ci_templates + specs ].freeze MESSAGE_PREFIX = '==>'.freeze