diff options
122 files changed, 955 insertions, 891 deletions
diff --git a/.gitlab/ci/package-and-test/main.gitlab-ci.yml b/.gitlab/ci/package-and-test/main.gitlab-ci.yml index 97462b9a4dd..3c9742f59e6 100644 --- a/.gitlab/ci/package-and-test/main.gitlab-ci.yml +++ b/.gitlab/ci/package-and-test/main.gitlab-ci.yml @@ -11,7 +11,7 @@ include: - local: .gitlab/ci/package-and-test/rules.gitlab-ci.yml - local: .gitlab/ci/package-and-test/variables.gitlab-ci.yml - project: gitlab-org/quality/pipeline-common - ref: 2.2.0 + ref: 3.0.0 file: - /ci/base.gitlab-ci.yml - /ci/allure-report.yml diff --git a/.gitlab/ci/review-apps/qa.gitlab-ci.yml b/.gitlab/ci/review-apps/qa.gitlab-ci.yml index 917184e0b5b..ab6737b8278 100644 --- a/.gitlab/ci/review-apps/qa.gitlab-ci.yml +++ b/.gitlab/ci/review-apps/qa.gitlab-ci.yml @@ -1,6 +1,6 @@ include: - project: gitlab-org/quality/pipeline-common - ref: 2.2.0 + ref: 3.0.0 file: - /ci/base.gitlab-ci.yml - /ci/allure-report.yml diff --git a/.rubocop_todo/gitlab/strong_memoize_attr.yml b/.rubocop_todo/gitlab/strong_memoize_attr.yml index 06efefef2f1..f05e7ba35af 100644 --- a/.rubocop_todo/gitlab/strong_memoize_attr.yml +++ b/.rubocop_todo/gitlab/strong_memoize_attr.yml @@ -180,7 +180,7 @@ Gitlab/StrongMemoizeAttr: - 'app/services/ci/pipelines/hook_service.rb' - 'app/services/ci/queue/build_queue_service.rb' - 'app/services/ci/update_build_state_service.rb' - - 'app/services/clusters/agents/refresh_authorization_service.rb' + - 'app/services/clusters/agents/authorizations/ci_access/refresh_service.rb' - 'app/services/clusters/integrations/prometheus_health_check_service.rb' - 'app/services/concerns/alert_management/alert_processing.rb' - 'app/services/concerns/incident_management/settings.rb' diff --git a/.rubocop_todo/layout/line_length.yml b/.rubocop_todo/layout/line_length.yml index a59997afc55..9f11c94f242 100644 --- a/.rubocop_todo/layout/line_length.yml +++ b/.rubocop_todo/layout/line_length.yml @@ -105,7 +105,7 @@ Layout/LineLength: - 'app/controllers/users_controller.rb' - 'app/finders/analytics/cycle_analytics/stage_finder.rb' - 'app/finders/ci/runners_finder.rb' - - 'app/finders/clusters/agent_authorizations_finder.rb' + - 'app/finders/clusters/agents/authorizations/ci_access/finder.rb' - 'app/finders/group_descendants_finder.rb' - 'app/finders/group_members_finder.rb' - 'app/finders/group_projects_finder.rb' @@ -507,6 +507,7 @@ Layout/LineLength: - 'app/services/ci/runners/register_runner_service.rb' - 'app/services/ci/runners/unregister_runner_service.rb' - 'app/services/clusters/agent_tokens/create_service.rb' + - 'app/services/clusters/agents/authorizations/ci_access/refresh_service.rb' - 'app/services/clusters/agents/delete_service.rb' - 'app/services/clusters/build_kubernetes_namespace_service.rb' - 'app/services/clusters/integrations/create_service.rb' @@ -2038,7 +2039,7 @@ Layout/LineLength: - 'ee/spec/models/ci/minutes/namespace_monthly_usage_spec.rb' - 'ee/spec/models/ci/minutes/project_monthly_usage_spec.rb' - 'ee/spec/models/ci/pipeline_spec.rb' - - 'ee/spec/models/concerns/ee/clusters/agents/authorization_config_scopes_spec.rb' + - 'ee/spec/models/concerns/ee/clusters/agents/authorizations/ci_access/config_scopes_spec.rb' - 'ee/spec/models/concerns/ee/issuable_spec.rb' - 'ee/spec/models/concerns/ee/noteable_spec.rb' - 'ee/spec/models/concerns/ee/project_security_scanners_information_spec.rb' @@ -3516,6 +3517,8 @@ Layout/LineLength: - 'spec/factories/ci/job_artifacts.rb' - 'spec/factories/ci/pipelines.rb' - 'spec/factories/ci/reports/codequality_degradations.rb' + - 'spec/factories/clusters/agents/authorizations/ci_access/group_authorizations.rb' + - 'spec/factories/clusters/agents/authorizations/ci_access/project_authorizations.rb' - 'spec/factories/container_repositories.rb' - 'spec/factories/dependency_proxy.rb' - 'spec/factories/deployments.rb' @@ -3744,6 +3747,7 @@ Layout/LineLength: - 'spec/finders/ci/pipelines_finder_spec.rb' - 'spec/finders/ci/pipelines_for_merge_request_finder_spec.rb' - 'spec/finders/ci/runners_finder_spec.rb' + - 'spec/finders/clusters/agents/authorizations/ci_access/finder_spec.rb' - 'spec/finders/clusters/agent_authorizations_finder_spec.rb' - 'spec/finders/clusters_finder_spec.rb' - 'spec/finders/deploy_tokens/tokens_finder_spec.rb' @@ -4570,7 +4574,7 @@ Layout/LineLength: - 'spec/models/concerns/cache_markdown_field_spec.rb' - 'spec/models/concerns/cacheable_attributes_spec.rb' - 'spec/models/concerns/ci/artifactable_spec.rb' - - 'spec/models/concerns/clusters/agents/authorization_config_scopes_spec.rb' + - 'spec/models/concerns/clusters/agents/authorizations/ci_access/config_scopes_spec.rb' - 'spec/models/concerns/deployment_platform_spec.rb' - 'spec/models/concerns/group_descendant_spec.rb' - 'spec/models/concerns/id_in_ordered_spec.rb' @@ -5020,6 +5024,7 @@ Layout/LineLength: - 'spec/services/ci/test_failure_history_service_spec.rb' - 'spec/services/ci/unlock_artifacts_service_spec.rb' - 'spec/services/ci/update_pending_build_service_spec.rb' + - 'spec/services/clusters/agents/authorizations/ci_access/filter_service_spec.rb' - 'spec/services/clusters/create_service_spec.rb' - 'spec/services/clusters/integrations/prometheus_health_check_service_spec.rb' - 'spec/services/clusters/kubernetes/create_or_update_namespace_service_spec.rb' diff --git a/.rubocop_todo/rspec/context_wording.yml b/.rubocop_todo/rspec/context_wording.yml index 63b24a04c56..a7ec2fcd55a 100644 --- a/.rubocop_todo/rspec/context_wording.yml +++ b/.rubocop_todo/rspec/context_wording.yml @@ -181,7 +181,7 @@ RSpec/ContextWording: - 'ee/spec/finders/dast_site_profiles_finder_spec.rb' - 'ee/spec/finders/dast_site_validations_finder_spec.rb' - 'ee/spec/finders/ee/alert_management/http_integrations_finder_spec.rb' - - 'ee/spec/finders/ee/clusters/agent_authorizations_finder_spec.rb' + - 'ee/spec/finders/ee/clusters/agents/authorizations/ci_access/finder_spec.rb' - 'ee/spec/finders/ee/clusters/agents_finder_spec.rb' - 'ee/spec/finders/ee/group_members_finder_spec.rb' - 'ee/spec/finders/ee/namespaces/projects_finder_spec.rb' @@ -1343,6 +1343,7 @@ RSpec/ContextWording: - 'spec/finders/ci/pipelines_finder_spec.rb' - 'spec/finders/ci/runners_finder_spec.rb' - 'spec/finders/cluster_ancestors_finder_spec.rb' + - 'spec/finders/clusters/agents/authorizations/ci_access/finder_spec.rb' - 'spec/finders/clusters/agent_authorizations_finder_spec.rb' - 'spec/finders/clusters/agents_finder_spec.rb' - 'spec/finders/clusters/kubernetes_namespace_finder_spec.rb' @@ -1528,7 +1529,7 @@ RSpec/ContextWording: - 'spec/initializers/validate_database_config_spec.rb' - 'spec/lib/api/entities/application_setting_spec.rb' - 'spec/lib/api/entities/basic_project_details_spec.rb' - - 'spec/lib/api/entities/clusters/agent_authorization_spec.rb' + - 'spec/lib/api/entities/clusters/agents/authorizations/ci_access_spec.rb' - 'spec/lib/api/entities/nuget/dependency_group_spec.rb' - 'spec/lib/api/entities/user_spec.rb' - 'spec/lib/api/every_api_endpoint_spec.rb' @@ -2685,7 +2686,7 @@ RSpec/ContextWording: - 'spec/services/ci/update_pending_build_service_spec.rb' - 'spec/services/clusters/agent_tokens/track_usage_service_spec.rb' - 'spec/services/clusters/agents/delete_expired_events_service_spec.rb' - - 'spec/services/clusters/agents/refresh_authorization_service_spec.rb' + - 'spec/services/clusters/agents/authorizations/ci_access/refresh_service_spec.rb' - 'spec/services/clusters/build_kubernetes_namespace_service_spec.rb' - 'spec/services/clusters/create_service_spec.rb' - 'spec/services/clusters/kubernetes/create_or_update_namespace_service_spec.rb' diff --git a/.rubocop_todo/rspec/missing_feature_category.yml b/.rubocop_todo/rspec/missing_feature_category.yml index 5790af0ac12..c4b8c206433 100644 --- a/.rubocop_todo/rspec/missing_feature_category.yml +++ b/.rubocop_todo/rspec/missing_feature_category.yml @@ -4544,7 +4544,6 @@ RSpec/MissingFeatureCategory: - 'spec/lib/gitlab/slash_commands/presenters/issue_show_spec.rb' - 'spec/lib/gitlab/slash_commands/presenters/run_spec.rb' - 'spec/lib/gitlab/slash_commands/run_spec.rb' - - 'spec/lib/gitlab/slug/environment_spec.rb' - 'spec/lib/gitlab/snippet_search_results_spec.rb' - 'spec/lib/gitlab/sourcegraph_spec.rb' - 'spec/lib/gitlab/spamcheck/client_spec.rb' @@ -5239,7 +5238,6 @@ RSpec/MissingFeatureCategory: - 'spec/models/namespaces/user_namespace_spec.rb' - 'spec/models/network/graph_spec.rb' - 'spec/models/note_diff_file_spec.rb' - - 'spec/models/note_spec.rb' - 'spec/models/notification_setting_spec.rb' - 'spec/models/oauth_access_grant_spec.rb' - 'spec/models/oauth_access_token_spec.rb' diff --git a/.rubocop_todo/style/percent_literal_delimiters.yml b/.rubocop_todo/style/percent_literal_delimiters.yml index cd15e2aa858..b3fbf73a75f 100644 --- a/.rubocop_todo/style/percent_literal_delimiters.yml +++ b/.rubocop_todo/style/percent_literal_delimiters.yml @@ -54,7 +54,7 @@ Style/PercentLiteralDelimiters: - 'app/models/ci/pipeline.rb' - 'app/models/clusters/platforms/kubernetes.rb' - 'app/models/commit.rb' - - 'app/models/concerns/clusters/agents/authorization_config_scopes.rb' + - 'app/models/concerns/clusters/agents/authorizations/ci_access/config_scopes.rb' - 'app/models/concerns/diff_positionable_note.rb' - 'app/models/concerns/enums/prometheus_metric.rb' - 'app/models/concerns/issuable.rb' diff --git a/.rubocop_todo/style/string_concatenation.yml b/.rubocop_todo/style/string_concatenation.yml index 8e0858e982c..8209781e78a 100644 --- a/.rubocop_todo/style/string_concatenation.yml +++ b/.rubocop_todo/style/string_concatenation.yml @@ -121,7 +121,6 @@ Style/StringConcatenation: - 'lib/gitlab/route_map.rb' - 'lib/gitlab/sanitizers/exception_message.rb' - 'lib/gitlab/sidekiq_logging/json_formatter.rb' - - 'lib/gitlab/slug/environment.rb' - 'lib/gitlab/sql/set_operator.rb' - 'lib/gitlab/ssh_public_key.rb' - 'lib/gitlab/throttle.rb' diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index d91a6d82185..d906aafc5a7 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -9255b4522513ee91d7fef535137d57deb498bbf8 +28c0539251cf00a675b78e987ff30b7741425653 diff --git a/app/assets/javascripts/design_management/pages/design/index.vue b/app/assets/javascripts/design_management/pages/design/index.vue index 0251ffe28f9..2f2b2ed1a90 100644 --- a/app/assets/javascripts/design_management/pages/design/index.vue +++ b/app/assets/javascripts/design_management/pages/design/index.vue @@ -332,7 +332,7 @@ export default { <template> <div - class="design-detail js-design-detail fixed-top gl-w-full gl-bottom-0 gl-display-flex gl-justify-content-center gl-flex-direction-column gl-lg-flex-direction-row" + class="design-detail js-design-detail fixed-top gl-w-full gl-display-flex gl-justify-content-center gl-flex-direction-column gl-lg-flex-direction-row" > <div class="gl-display-flex gl-overflow-hidden gl-flex-grow-1 gl-flex-direction-column gl-relative" diff --git a/app/assets/javascripts/ml/experiment_tracking/routes/experiments/show/components/experiment_header.vue b/app/assets/javascripts/ml/experiment_tracking/routes/experiments/show/components/experiment_header.vue new file mode 100644 index 00000000000..5b840a5ed43 --- /dev/null +++ b/app/assets/javascripts/ml/experiment_tracking/routes/experiments/show/components/experiment_header.vue @@ -0,0 +1,47 @@ +<script> +import { GlButton } from '@gitlab/ui'; +import DeleteButton from '~/ml/experiment_tracking/components/delete_button.vue'; +import { __ } from '~/locale'; +import { visitUrl } from '~/lib/utils/url_utility'; + +export default { + name: 'ExperimentHeader', + components: { + DeleteButton, + GlButton, + }, + props: { + title: { + type: String, + required: true, + }, + deleteInfo: { + type: Object, + required: true, + }, + }, + methods: { + downloadCsv() { + const currentPath = window.location.pathname; + const currentSearch = window.location.search; + + visitUrl(`${currentPath}.csv${currentSearch}`); + }, + }, + i18n: { + downloadAsCsvLabel: __('Download as CSV'), + }, +}; +</script> + +<template> + <div class="detail-page-header gl-flex-wrap-wrap"> + <div class="detail-page-header-body"> + <h1 class="page-title gl-font-size-h-display flex-fill">{{ title }}</h1> + + <gl-button @click="downloadCsv">{{ $options.i18n.downloadAsCsvLabel }}</gl-button> + + <delete-button v-bind="deleteInfo" /> + </div> + </div> +</template> diff --git a/app/assets/javascripts/ml/experiment_tracking/routes/experiments/show/ml_experiments_show.vue b/app/assets/javascripts/ml/experiment_tracking/routes/experiments/show/ml_experiments_show.vue index 40b9e0723e9..acb5fc7cad2 100644 --- a/app/assets/javascripts/ml/experiment_tracking/routes/experiments/show/ml_experiments_show.vue +++ b/app/assets/javascripts/ml/experiment_tracking/routes/experiments/show/ml_experiments_show.vue @@ -12,7 +12,7 @@ import { queryToObject, setUrlParams, visitUrl } from '~/lib/utils/url_utility'; import { capitalizeFirstCharacter } from '~/lib/utils/text_utility'; import KeysetPagination from '~/vue_shared/components/incubation/pagination.vue'; import IncubationAlert from '~/vue_shared/components/incubation/incubation_alert.vue'; -import DeleteButton from '~/ml/experiment_tracking/components/delete_button.vue'; +import ExperimentHeader from './components/experiment_header.vue'; import { LIST_KEY_CREATED_AT, BASE_SORT_FIELDS, @@ -31,7 +31,7 @@ export default { IncubationAlert, RegistrySearch, KeysetPagination, - DeleteButton, + ExperimentHeader, }, props: { experiment: { @@ -130,6 +130,14 @@ export default { hasItems() { return this.candidates.length > 0; }, + deleteButtonInfo() { + return { + deletePath: this.experiment.path, + deleteConfirmationText: translations.DELETE_EXPERIMENT_CONFIRMATION_MESSAGE, + actionPrimaryText: translations.DELETE_EXPERIMENT_PRIMARY_ACTION_LABEL, + modalTitle: translations.DELETE_EXPERIMENT_MODAL_TITLE, + }; + }, }, methods: { submitFilters() { @@ -163,20 +171,7 @@ export default { :link-to-feedback-issue="$options.constants.FEATURE_FEEDBACK_ISSUE" /> - <div class="detail-page-header gl-flex-wrap-wrap"> - <div class="detail-page-header-body"> - <h1 class="page-title gl-font-size-h-display flex-fill"> - {{ experiment.name }} - </h1> - - <delete-button - :delete-path="experiment.path" - :delete-confirmation-text="$options.i18n.DELETE_EXPERIMENT_CONFIRMATION_MESSAGE" - :action-primary-text="$options.i18n.DELETE_EXPERIMENT_PRIMARY_ACTION_LABEL" - :modal-title="$options.i18n.DELETE_EXPERIMENT_MODAL_TITLE" - /> - </div> - </div> + <experiment-header :title="experiment.name" :delete-info="deleteButtonInfo" /> <registry-search :filters="filters" diff --git a/app/assets/javascripts/search/sidebar/components/scope_navigation.vue b/app/assets/javascripts/search/sidebar/components/scope_navigation.vue index 02a3870f499..1c81f652387 100644 --- a/app/assets/javascripts/search/sidebar/components/scope_navigation.vue +++ b/app/assets/javascripts/search/sidebar/components/scope_navigation.vue @@ -22,7 +22,9 @@ export default { ...mapState(['navigation', 'urlQuery']), }, created() { - this.fetchSidebarCount(); + if (this.urlQuery?.search) { + this.fetchSidebarCount(); + } }, methods: { ...mapActions(['fetchSidebarCount']), diff --git a/app/assets/javascripts/search/sidebar/constants/index.js b/app/assets/javascripts/search/sidebar/constants/index.js index 395629dc70c..f123494fba2 100644 --- a/app/assets/javascripts/search/sidebar/constants/index.js +++ b/app/assets/javascripts/search/sidebar/constants/index.js @@ -12,7 +12,7 @@ export const NAV_LINK_DEFAULT_CLASSES = [ 'gl-justify-content-space-between', ]; export const NAV_LINK_COUNT_DEFAULT_CLASSES = ['gl-font-sm', 'gl-font-weight-normal']; -export const HR_DEFAULT_CLASSES = ['gl-my-5', 'gl-mx-5', 'gl-border-gray-100']; +export const HR_DEFAULT_CLASSES = ['gl-m-5', 'gl-border-gray-100']; export const ONLY_SHOW_MD = ['gl-display-none', 'gl-md-display-block']; export const TRACKING_LABEL_CHECKBOX = 'Checkbox'; diff --git a/app/assets/javascripts/super_sidebar/components/context_switcher.vue b/app/assets/javascripts/super_sidebar/components/context_switcher.vue index 5c1cebe0195..fa9da6cef9d 100644 --- a/app/assets/javascripts/super_sidebar/components/context_switcher.vue +++ b/app/assets/javascripts/super_sidebar/components/context_switcher.vue @@ -150,7 +150,12 @@ export default { {{ $options.i18n.switchTo }} </div> <ul :aria-label="$options.i18n.switchTo" class="gl-p-0"> - <nav-item v-for="item in persistentLinks" :key="item.link" :item="item" /> + <nav-item + v-for="item in persistentLinks" + :key="item.link" + :item="item" + :link-classes="{ [item.link_classes]: item.link_classes }" + /> </ul> </li> <projects-list diff --git a/app/assets/javascripts/super_sidebar/components/context_switcher_toggle.vue b/app/assets/javascripts/super_sidebar/components/context_switcher_toggle.vue index 451e12df697..e56ef9e410b 100644 --- a/app/assets/javascripts/super_sidebar/components/context_switcher_toggle.vue +++ b/app/assets/javascripts/super_sidebar/components/context_switcher_toggle.vue @@ -38,7 +38,7 @@ export default { <button v-collapse-toggle.context-switcher type="button" - class="context-switcher-toggle gl-p-0 gl-bg-transparent gl-hover-bg-t-gray-a-08 gl-border-0 border-top border-bottom gl-border-gray-a-08 gl-box-shadow-none gl-display-flex gl-align-items-center gl-font-weight-bold gl-w-full gl-h-8" + class="context-switcher-toggle gl-p-0 gl-bg-transparent gl-hover-bg-t-gray-a-08 gl-focus-bg-t-gray-a-08 gl-border-0 border-top border-bottom gl-border-gray-a-08 gl-box-shadow-none gl-display-flex gl-align-items-center gl-font-weight-bold gl-w-full gl-h-8" > <span v-if="context.icon" diff --git a/app/assets/javascripts/super_sidebar/components/groups_list.vue b/app/assets/javascripts/super_sidebar/components/groups_list.vue index eb256e4971b..4fa15f1cd76 100644 --- a/app/assets/javascripts/super_sidebar/components/groups_list.vue +++ b/app/assets/javascripts/super_sidebar/components/groups_list.vue @@ -36,11 +36,14 @@ export default { storageKey() { return `${this.username}/frequent-groups`; }, - viewAllItem() { + viewAllProps() { return { - link: this.viewAllLink, - title: s__('Navigation|View all your groups'), - icon: 'group', + item: { + link: this.viewAllLink, + title: s__('Navigation|View all your groups'), + icon: 'group', + }, + linkClasses: { 'dashboard-shortcuts-groups': true }, }; }, }, @@ -61,7 +64,7 @@ export default { :search-results="searchResults" > <template #view-all-items> - <nav-item :item="viewAllItem" /> + <nav-item v-bind="viewAllProps" /> </template> </search-results> <frequent-items-list @@ -72,7 +75,7 @@ export default { :pristine-text="$options.i18n.pristineText" > <template #view-all-items> - <nav-item :item="viewAllItem" /> + <nav-item v-bind="viewAllProps" /> </template> </frequent-items-list> </template> diff --git a/app/assets/javascripts/super_sidebar/components/nav_item.vue b/app/assets/javascripts/super_sidebar/components/nav_item.vue index 3a3eb72a4a8..53698a808a7 100644 --- a/app/assets/javascripts/super_sidebar/components/nav_item.vue +++ b/app/assets/javascripts/super_sidebar/components/nav_item.vue @@ -146,7 +146,7 @@ export default { <component :is="elem" v-bind="linkProps" - class="gl-rounded-base gl-relative gl-display-flex gl-align-items-center gl-px-0 gl-line-height-normal gl-text-black-normal! gl-hover-bg-t-gray-a-08 gl-text-decoration-none!" + class="gl-rounded-base gl-relative gl-display-flex gl-align-items-center gl-mb-1 gl-px-0 gl-line-height-normal gl-text-black-normal! gl-hover-bg-t-gray-a-08 gl-focus-bg-t-gray-a-08 gl-text-decoration-none!" :class="computedLinkClasses" data-qa-selector="nav_item_link" data-testid="nav-item-link" diff --git a/app/assets/javascripts/super_sidebar/components/pinned_section.vue b/app/assets/javascripts/super_sidebar/components/pinned_section.vue index d1c0e757a91..193de143c2b 100644 --- a/app/assets/javascripts/super_sidebar/components/pinned_section.vue +++ b/app/assets/javascripts/super_sidebar/components/pinned_section.vue @@ -60,7 +60,7 @@ export default { <section class="gl-mx-2"> <a href="#" - class="gl-rounded-base gl-relative gl-display-flex gl-align-items-center gl-py-3 gl-px-0 gl-line-height-normal gl-text-black-normal! gl-hover-bg-t-gray-a-08 gl-text-decoration-none!" + class="gl-rounded-base gl-relative gl-display-flex gl-align-items-center gl-py-3 gl-px-0 gl-line-height-normal gl-text-black-normal! gl-hover-bg-t-gray-a-08 gl-focus-bg-t-gray-a-08 gl-text-decoration-none!" @click.prevent="expanded = !expanded" > <div class="gl-flex-shrink-0 gl-w-6 gl-mx-3"> diff --git a/app/assets/javascripts/super_sidebar/components/projects_list.vue b/app/assets/javascripts/super_sidebar/components/projects_list.vue index b7a29a78d5f..78860e35eb1 100644 --- a/app/assets/javascripts/super_sidebar/components/projects_list.vue +++ b/app/assets/javascripts/super_sidebar/components/projects_list.vue @@ -36,11 +36,14 @@ export default { storageKey() { return `${this.username}/frequent-projects`; }, - viewAllItem() { + viewAllProps() { return { - link: this.viewAllLink, - title: s__('Navigation|View all your projects'), - icon: 'project', + item: { + link: this.viewAllLink, + title: s__('Navigation|View all your projects'), + icon: 'project', + }, + linkClasses: { 'dashboard-shortcuts-projects': true }, }; }, }, @@ -62,7 +65,7 @@ export default { :search-results="searchResults" > <template #view-all-items> - <nav-item :item="viewAllItem" /> + <nav-item v-bind="viewAllProps" /> </template> </search-results> <frequent-items-list @@ -73,7 +76,7 @@ export default { :pristine-text="$options.i18n.pristineText" > <template #view-all-items> - <nav-item :item="viewAllItem" /> + <nav-item v-bind="viewAllProps" /> </template> </frequent-items-list> </template> diff --git a/app/assets/javascripts/super_sidebar/components/super_sidebar.vue b/app/assets/javascripts/super_sidebar/components/super_sidebar.vue index 302b6a9d4b0..2a95d2c37c4 100644 --- a/app/assets/javascripts/super_sidebar/components/super_sidebar.vue +++ b/app/assets/javascripts/super_sidebar/components/super_sidebar.vue @@ -108,5 +108,14 @@ export default { </div> </div> </aside> + <a + v-for="shortcutLink in sidebarData.shortcut_links" + :key="shortcutLink.href" + :href="shortcutLink.href" + :class="shortcutLink.css_class" + class="gl-display-none" + > + {{ shortcutLink.title }} + </a> </div> </template> diff --git a/app/assets/javascripts/super_sidebar/components/user_bar.vue b/app/assets/javascripts/super_sidebar/components/user_bar.vue index 498c082ddb2..b69ebc6be17 100644 --- a/app/assets/javascripts/super_sidebar/components/user_bar.vue +++ b/app/assets/javascripts/super_sidebar/components/user_bar.vue @@ -137,7 +137,7 @@ export default { <div class="gl-display-flex gl-justify-content-space-between gl-px-3 gl-py-2 gl-gap-2"> <counter v-gl-tooltip:super-sidebar.hover.bottom="$options.i18n.issues" - class="gl-flex-basis-third" + class="gl-flex-basis-third dashboard-shortcuts-issues" icon="issues" :count="sidebarData.assigned_open_issues_count" :href="sidebarData.issues_dashboard_path" @@ -165,7 +165,7 @@ export default { </merge-request-menu> <counter v-gl-tooltip:super-sidebar.hover.bottom="$options.i18n.todoList" - class="gl-flex-basis-third" + class="gl-flex-basis-third shortcuts-todos" icon="todo-done" :count="sidebarData.todos_pending_count" href="/dashboard/todos" diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index 0d18835ac0f..22c6fb04d3b 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -8,7 +8,7 @@ $search-input-field-x-min-width: 200px; min-height: $header-height; border: 0; position: fixed; - top: 0; + top: $calc-application-bars-height; left: 0; right: 0; border-radius: 0; @@ -479,11 +479,6 @@ $search-input-field-x-min-width: 200px; visibility: visible; } -.with-performance-bar .navbar-gitlab, -.with-performance-bar .fixed-top { - top: $performance-bar-height; -} - .navbar-empty { justify-content: center; height: $header-height; diff --git a/app/assets/stylesheets/framework/system_messages.scss b/app/assets/stylesheets/framework/system_messages.scss index db59a482c64..946a241e6dd 100644 --- a/app/assets/stylesheets/framework/system_messages.scss +++ b/app/assets/stylesheets/framework/system_messages.scss @@ -36,27 +36,6 @@ } } -// System Header -.with-system-header { - // main navigation - // login page - .navbar-gitlab, - .fixed-top { - top: $system-header-height; - } - - // Performance Bar - // System Header - &.with-performance-bar { - // main navigation - header.navbar-gitlab, - .fixed-top { - top: $performance-bar-height + $system-header-height; - } - - } -} - // System Footer .with-system-footer { // navless pages' footer eg: login page diff --git a/app/assets/stylesheets/page_bundles/design_management.scss b/app/assets/stylesheets/page_bundles/design_management.scss index 143682e1cd7..f56eb4ae6fb 100644 --- a/app/assets/stylesheets/page_bundles/design_management.scss +++ b/app/assets/stylesheets/page_bundles/design_management.scss @@ -20,10 +20,7 @@ $t-gray-a-16-design-pin: rgba($black, 0.16); .design-detail { background-color: rgba($modal-backdrop-bg, $modal-backdrop-opacity); - - .with-performance-bar & { - top: 35px; - } + bottom: $calc-application-footer-height; .comment-indicator { border-radius: 50%; diff --git a/app/assets/stylesheets/page_bundles/search.scss b/app/assets/stylesheets/page_bundles/search.scss index cde570cfb0f..d37e87b5cd5 100644 --- a/app/assets/stylesheets/page_bundles/search.scss +++ b/app/assets/stylesheets/page_bundles/search.scss @@ -21,18 +21,18 @@ $border-radius-medium: 3px; } } +.language-filter-checkbox { + .custom-control-label { + flex-grow: 1; + } +} + .search-sidebar { @include media-breakpoint-up(md) { min-width: $search-sidebar-min-width; max-width: $search-sidebar-max-width; } - .language-filter-checkbox { - .custom-control-label { - flex-grow: 1; - } - } - .language-filter-max-height { max-height: $language-filter-max-height; } diff --git a/app/assets/stylesheets/startup/startup-dark.scss b/app/assets/stylesheets/startup/startup-dark.scss index de2a2ddcf32..5219cd62775 100644 --- a/app/assets/stylesheets/startup/startup-dark.scss +++ b/app/assets/stylesheets/startup/startup-dark.scss @@ -750,7 +750,7 @@ kbd { min-height: var(--header-height, 48px); border: 0; position: fixed; - top: 0; + top: calc(var(--system-header-height) + var(--performance-bar-height)); left: 0; right: 0; border-radius: 0; diff --git a/app/assets/stylesheets/startup/startup-general.scss b/app/assets/stylesheets/startup/startup-general.scss index e0a71a1c306..16bf2c20bbc 100644 --- a/app/assets/stylesheets/startup/startup-general.scss +++ b/app/assets/stylesheets/startup/startup-general.scss @@ -750,7 +750,7 @@ kbd { min-height: var(--header-height, 48px); border: 0; position: fixed; - top: 0; + top: calc(var(--system-header-height) + var(--performance-bar-height)); left: 0; right: 0; border-radius: 0; diff --git a/app/assets/stylesheets/startup/startup-signin.scss b/app/assets/stylesheets/startup/startup-signin.scss index 3663c97f41a..fcdcab5caeb 100644 --- a/app/assets/stylesheets/startup/startup-signin.scss +++ b/app/assets/stylesheets/startup/startup-signin.scss @@ -769,6 +769,9 @@ svg { fill: currentColor; } +.fixed-top { + top: calc(var(--system-header-height) + var(--performance-bar-height)); +} .gl-display-flex { display: flex; } diff --git a/app/assets/stylesheets/utilities.scss b/app/assets/stylesheets/utilities.scss index 66c543aa654..e5f99879166 100644 --- a/app/assets/stylesheets/utilities.scss +++ b/app/assets/stylesheets/utilities.scss @@ -39,6 +39,12 @@ .border-radius-small { border-radius: $border-radius-small; } .box-shadow-default { box-shadow: 0 2px 4px 0 $black-transparent; } +// Override Bootstrap class with offset for system-header and +// performance bar when present +.fixed-top { + top: $calc-application-bars-height; +} + .gl-children-ml-sm-3 > * { @include media-breakpoint-up(sm) { @include gl-ml-3; diff --git a/app/finders/clusters/agent_authorizations_finder.rb b/app/finders/clusters/agent_authorizations_finder.rb deleted file mode 100644 index 70c0868cc7f..00000000000 --- a/app/finders/clusters/agent_authorizations_finder.rb +++ /dev/null @@ -1,69 +0,0 @@ -# frozen_string_literal: true - -module Clusters - class AgentAuthorizationsFinder - def initialize(project) - @project = project - end - - def execute - # closest, most-specific authorization for a given agent wins - (project_authorizations + implicit_authorizations + group_authorizations) - .uniq(&:agent_id) - end - - private - - attr_reader :project - - def implicit_authorizations - project.cluster_agents.map do |agent| - Clusters::Agents::ImplicitAuthorization.new(agent: agent) - end - end - - # rubocop: disable CodeReuse/ActiveRecord - def project_authorizations - namespace_ids = project.group ? all_namespace_ids : project.namespace_id - - Clusters::Agents::ProjectAuthorization - .where(project_id: project.id) - .joins(agent: :project) - .preload(agent: :project) - .where(cluster_agents: { projects: { namespace_id: namespace_ids } }) - .with_available_ci_access_fields(project) - .to_a - end - - def group_authorizations - return [] unless project.group - - authorizations = Clusters::Agents::GroupAuthorization.arel_table - - ordered_ancestors_cte = Gitlab::SQL::CTE.new( - :ordered_ancestors, - project.group.self_and_ancestors(hierarchy_order: :asc).reselect(:id) - ) - - cte_join_sources = authorizations.join(ordered_ancestors_cte.table).on( - authorizations[:group_id].eq(ordered_ancestors_cte.table[:id]) - ).join_sources - - Clusters::Agents::GroupAuthorization - .with(ordered_ancestors_cte.to_arel) - .joins(cte_join_sources) - .joins(agent: :project) - .with_available_ci_access_fields(project) - .where(projects: { namespace_id: all_namespace_ids }) - .order(Arel.sql('agent_id, array_position(ARRAY(SELECT id FROM ordered_ancestors)::bigint[], agent_group_authorizations.group_id)')) - .select('DISTINCT ON (agent_id) agent_group_authorizations.*') - .preload(agent: :project) - .to_a - end - # rubocop: enable CodeReuse/ActiveRecord - - def all_namespace_ids - project.root_ancestor.self_and_descendants.select(:id) - end - end -end diff --git a/app/finders/clusters/agents/authorizations/ci_access/finder.rb b/app/finders/clusters/agents/authorizations/ci_access/finder.rb new file mode 100644 index 00000000000..97d378669a4 --- /dev/null +++ b/app/finders/clusters/agents/authorizations/ci_access/finder.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true + +module Clusters + module Agents + module Authorizations + module CiAccess + class Finder + def initialize(project) + @project = project + end + + def execute + # closest, most-specific authorization for a given agent wins + (project_authorizations + implicit_authorizations + group_authorizations) + .uniq(&:agent_id) + end + + private + + attr_reader :project + + def implicit_authorizations + project.cluster_agents.map do |agent| + Clusters::Agents::Authorizations::CiAccess::ImplicitAuthorization.new(agent: agent) + end + end + + # rubocop: disable CodeReuse/ActiveRecord + def project_authorizations + namespace_ids = project.group ? all_namespace_ids : project.namespace_id + + Clusters::Agents::Authorizations::CiAccess::ProjectAuthorization + .where(project_id: project.id) + .joins(agent: :project) + .preload(agent: :project) + .where(cluster_agents: { projects: { namespace_id: namespace_ids } }) + .with_available_ci_access_fields(project) + .to_a + end + + def group_authorizations + return [] unless project.group + + authorizations = Clusters::Agents::Authorizations::CiAccess::GroupAuthorization.arel_table + + ordered_ancestors_cte = Gitlab::SQL::CTE.new( + :ordered_ancestors, + project.group.self_and_ancestors(hierarchy_order: :asc).reselect(:id) + ) + + cte_join_sources = authorizations.join(ordered_ancestors_cte.table).on( + authorizations[:group_id].eq(ordered_ancestors_cte.table[:id]) + ).join_sources + + Clusters::Agents::Authorizations::CiAccess::GroupAuthorization + .with(ordered_ancestors_cte.to_arel) + .joins(cte_join_sources) + .joins(agent: :project) + .with_available_ci_access_fields(project) + .where(projects: { namespace_id: all_namespace_ids }) + .order(Arel.sql('agent_id, array_position(ARRAY(SELECT id FROM ordered_ancestors)::bigint[], agent_group_authorizations.group_id)')) + .select('DISTINCT ON (agent_id) agent_group_authorizations.*') + .preload(agent: :project) + .to_a + end + # rubocop: enable CodeReuse/ActiveRecord + + def all_namespace_ids + project.root_ancestor.self_and_descendants.select(:id) + end + end + end + end + end +end diff --git a/app/helpers/sidebars_helper.rb b/app/helpers/sidebars_helper.rb index a6eddb4d529..8af4204e5e1 100644 --- a/app/helpers/sidebars_helper.rb +++ b/app/helpers/sidebars_helper.rb @@ -89,7 +89,8 @@ module SidebarsHelper panel_type: panel_type, update_pins_url: pins_url, is_impersonating: impersonating?, - stop_impersonation_path: admin_impersonation_path + stop_impersonation_path: admin_impersonation_path, + shortcut_links: shortcut_links } end @@ -180,7 +181,8 @@ module SidebarsHelper extraAttrs: { 'data-track-action': 'click_link', 'data-track-label': 'merge_requests_assigned', - 'data-track-property': 'nav_core_menu' + 'data-track-property': 'nav_core_menu', + class: 'dashboard-shortcuts-merge_requests' } }, { @@ -190,7 +192,8 @@ module SidebarsHelper extraAttrs: { 'data-track-action': 'click_link', 'data-track-label': 'merge_requests_to_review', - 'data-track-property': 'nav_core_menu' + 'data-track-property': 'nav_core_menu', + class: 'dashboard-shortcuts-review_requests' } } ] @@ -334,6 +337,26 @@ module SidebarsHelper def impersonating? !!session[:impersonator_id] end + + def shortcut_links + [ + { + title: _('Milestones'), + href: dashboard_milestones_path, + css_class: 'dashboard-shortcuts-milestones' + }, + { + title: _('Snippets'), + href: dashboard_snippets_path, + css_class: 'dashboard-shortcuts-snippets' + }, + { + title: _('Activity'), + href: activity_dashboard_path, + css_class: 'dashboard-shortcuts-activity' + } + ] + end end SidebarsHelper.prepend_mod_with('SidebarsHelper') diff --git a/app/models/ci/build_metadata.rb b/app/models/ci/build_metadata.rb index 4b2be446fe3..b98fdba44ec 100644 --- a/app/models/ci/build_metadata.rb +++ b/app/models/ci/build_metadata.rb @@ -11,9 +11,11 @@ module Ci include ChronicDurationAttribute include Gitlab::Utils::StrongMemoize include IgnorableColumns + include SafelyChangeColumnDefault self.table_name = 'p_ci_builds_metadata' self.primary_key = 'id' + columns_changing_default :partition_id partitionable scope: :build diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 8c7b0193199..f5a2911fc59 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -1334,7 +1334,7 @@ module Ci def cluster_agent_authorizations strong_memoize(:cluster_agent_authorizations) do - ::Clusters::AgentAuthorizationsFinder.new(project).execute + ::Clusters::Agents::Authorizations::CiAccess::Finder.new(project).execute end end diff --git a/app/models/clusters/agent.rb b/app/models/clusters/agent.rb index 3478bb69707..4e2de06577d 100644 --- a/app/models/clusters/agent.rb +++ b/app/models/clusters/agent.rb @@ -12,11 +12,11 @@ module Clusters has_many :agent_tokens, -> { order_last_used_at_desc }, class_name: 'Clusters::AgentToken', inverse_of: :agent - has_many :group_authorizations, class_name: 'Clusters::Agents::GroupAuthorization' - has_many :authorized_groups, class_name: '::Group', through: :group_authorizations, source: :group + has_many :ci_access_group_authorizations, class_name: 'Clusters::Agents::Authorizations::CiAccess::GroupAuthorization' + has_many :ci_access_authorized_groups, class_name: '::Group', through: :ci_access_group_authorizations, source: :group - has_many :project_authorizations, class_name: 'Clusters::Agents::ProjectAuthorization' - has_many :authorized_projects, class_name: '::Project', through: :project_authorizations, source: :project + has_many :ci_access_project_authorizations, class_name: 'Clusters::Agents::Authorizations::CiAccess::ProjectAuthorization' + has_many :ci_access_authorized_projects, class_name: '::Project', through: :ci_access_project_authorizations, source: :project has_many :activity_events, -> { in_timeline_order }, class_name: 'Clusters::Agents::ActivityEvent', inverse_of: :agent diff --git a/app/models/clusters/agents/authorizations/ci_access/group_authorization.rb b/app/models/clusters/agents/authorizations/ci_access/group_authorization.rb new file mode 100644 index 00000000000..4261fd6570f --- /dev/null +++ b/app/models/clusters/agents/authorizations/ci_access/group_authorization.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module Clusters + module Agents + module Authorizations + module CiAccess + class GroupAuthorization < ApplicationRecord + include ConfigScopes + + self.table_name = 'agent_group_authorizations' + + belongs_to :agent, class_name: 'Clusters::Agent', optional: false + belongs_to :group, class_name: '::Group', optional: false + + validates :config, json_schema: { filename: 'clusters_agents_authorizations_ci_access_config' } + + def config_project + agent.project + end + end + end + end + end +end diff --git a/app/models/clusters/agents/authorizations/ci_access/implicit_authorization.rb b/app/models/clusters/agents/authorizations/ci_access/implicit_authorization.rb new file mode 100644 index 00000000000..b996ae3f92b --- /dev/null +++ b/app/models/clusters/agents/authorizations/ci_access/implicit_authorization.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module Clusters + module Agents + module Authorizations + module CiAccess + class ImplicitAuthorization + attr_reader :agent + + delegate :id, to: :agent, prefix: true + + def initialize(agent:) + @agent = agent + end + + def config_project + agent.project + end + + def config + {} + end + end + end + end + end +end diff --git a/app/models/clusters/agents/authorizations/ci_access/project_authorization.rb b/app/models/clusters/agents/authorizations/ci_access/project_authorization.rb new file mode 100644 index 00000000000..7742d109cdb --- /dev/null +++ b/app/models/clusters/agents/authorizations/ci_access/project_authorization.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module Clusters + module Agents + module Authorizations + module CiAccess + class ProjectAuthorization < ApplicationRecord + include ConfigScopes + + self.table_name = 'agent_project_authorizations' + + belongs_to :agent, class_name: 'Clusters::Agent', optional: false + belongs_to :project, class_name: '::Project', optional: false + + validates :config, json_schema: { filename: 'clusters_agents_authorizations_ci_access_config' } + + def config_project + agent.project + end + end + end + end + end +end diff --git a/app/models/clusters/agents/group_authorization.rb b/app/models/clusters/agents/group_authorization.rb deleted file mode 100644 index 58ba874ab53..00000000000 --- a/app/models/clusters/agents/group_authorization.rb +++ /dev/null @@ -1,20 +0,0 @@ -# frozen_string_literal: true - -module Clusters - module Agents - class GroupAuthorization < ApplicationRecord - include ::Clusters::Agents::AuthorizationConfigScopes - - self.table_name = 'agent_group_authorizations' - - belongs_to :agent, class_name: 'Clusters::Agent', optional: false - belongs_to :group, class_name: '::Group', optional: false - - validates :config, json_schema: { filename: 'cluster_agent_authorization_configuration' } - - def config_project - agent.project - end - end - end -end diff --git a/app/models/clusters/agents/implicit_authorization.rb b/app/models/clusters/agents/implicit_authorization.rb deleted file mode 100644 index a365ccdc568..00000000000 --- a/app/models/clusters/agents/implicit_authorization.rb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: true - -module Clusters - module Agents - class ImplicitAuthorization - attr_reader :agent - - delegate :id, to: :agent, prefix: true - - def initialize(agent:) - @agent = agent - end - - def config_project - agent.project - end - - def config - {} - end - end - end -end diff --git a/app/models/clusters/agents/project_authorization.rb b/app/models/clusters/agents/project_authorization.rb deleted file mode 100644 index b9b44741936..00000000000 --- a/app/models/clusters/agents/project_authorization.rb +++ /dev/null @@ -1,20 +0,0 @@ -# frozen_string_literal: true - -module Clusters - module Agents - class ProjectAuthorization < ApplicationRecord - include ::Clusters::Agents::AuthorizationConfigScopes - - self.table_name = 'agent_project_authorizations' - - belongs_to :agent, class_name: 'Clusters::Agent', optional: false - belongs_to :project, class_name: '::Project', optional: false - - validates :config, json_schema: { filename: 'cluster_agent_authorization_configuration' } - - def config_project - agent.project - end - end - end -end diff --git a/app/models/concerns/clusters/agents/authorization_config_scopes.rb b/app/models/concerns/clusters/agents/authorization_config_scopes.rb deleted file mode 100644 index 0a0406c3389..00000000000 --- a/app/models/concerns/clusters/agents/authorization_config_scopes.rb +++ /dev/null @@ -1,25 +0,0 @@ -# frozen_string_literal: true - -module Clusters - module Agents - module AuthorizationConfigScopes - extend ActiveSupport::Concern - - included do - scope :with_available_ci_access_fields, ->(project) { - where("config->'access_as' IS NULL") - .or(where("config->'access_as' = '{}'")) - .or(where("config->'access_as' ?| array[:fields]", fields: available_ci_access_fields(project))) - } - end - - class_methods do - def available_ci_access_fields(_project) - %w(agent) - end - end - end - end -end - -Clusters::Agents::AuthorizationConfigScopes.prepend_mod diff --git a/app/models/concerns/clusters/agents/authorizations/ci_access/config_scopes.rb b/app/models/concerns/clusters/agents/authorizations/ci_access/config_scopes.rb new file mode 100644 index 00000000000..eef68bfd349 --- /dev/null +++ b/app/models/concerns/clusters/agents/authorizations/ci_access/config_scopes.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module Clusters + module Agents + module Authorizations + module CiAccess + module ConfigScopes + extend ActiveSupport::Concern + + included do + scope :with_available_ci_access_fields, ->(project) { + where("config->'access_as' IS NULL") + .or(where("config->'access_as' = '{}'")) + .or(where("config->'access_as' ?| array[:fields]", fields: available_ci_access_fields(project))) + } + end + + class_methods do + def available_ci_access_fields(_project) + %w(agent) + end + end + end + end + end + end +end + +Clusters::Agents::Authorizations::CiAccess::ConfigScopes.prepend_mod diff --git a/app/services/ci/generate_kubeconfig_service.rb b/app/services/ci/generate_kubeconfig_service.rb index 1c6aaa9d1ff..56e22a64529 100644 --- a/app/services/ci/generate_kubeconfig_service.rb +++ b/app/services/ci/generate_kubeconfig_service.rb @@ -41,7 +41,7 @@ module Ci attr_reader :pipeline, :token, :environment, :template def agent_authorizations - ::Clusters::Agents::FilterAuthorizationsService.new( + ::Clusters::Agents::Authorizations::CiAccess::FilterService.new( pipeline.cluster_agent_authorizations, environment: environment ).execute diff --git a/app/services/clusters/agents/authorizations/ci_access/filter_service.rb b/app/services/clusters/agents/authorizations/ci_access/filter_service.rb new file mode 100644 index 00000000000..cd08aaa12d4 --- /dev/null +++ b/app/services/clusters/agents/authorizations/ci_access/filter_service.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +module Clusters + module Agents + module Authorizations + module CiAccess + class FilterService + def initialize(authorizations, filter_params) + @authorizations = authorizations + @filter_params = filter_params + + @environments_matcher = {} + end + + def execute + filter_by_environment(authorizations) + end + + private + + attr_reader :authorizations, :filter_params + + def filter_by_environment(auths) + return auths unless filter_by_environment? + + auths.select do |auth| + next true if auth.config['environments'].blank? + + auth.config['environments'].any? { |environment_pattern| matches_environment?(environment_pattern) } + end + end + + def filter_by_environment? + filter_params.has_key?(:environment) + end + + def environment_filter + @environment_filter ||= filter_params[:environment] + end + + def matches_environment?(environment_pattern) + return false if environment_filter.nil? + + environments_matcher(environment_pattern).match?(environment_filter) + end + + def environments_matcher(environment_pattern) + @environments_matcher[environment_pattern] ||= ::Gitlab::Ci::EnvironmentMatcher.new(environment_pattern) + end + end + end + end + end +end diff --git a/app/services/clusters/agents/authorizations/ci_access/refresh_service.rb b/app/services/clusters/agents/authorizations/ci_access/refresh_service.rb new file mode 100644 index 00000000000..047a0725a2c --- /dev/null +++ b/app/services/clusters/agents/authorizations/ci_access/refresh_service.rb @@ -0,0 +1,106 @@ +# frozen_string_literal: true + +module Clusters + module Agents + module Authorizations + module CiAccess + class RefreshService + include Gitlab::Utils::StrongMemoize + + AUTHORIZED_ENTITY_LIMIT = 100 + + delegate :project, to: :agent, private: true + delegate :root_ancestor, to: :project, private: true + + def initialize(agent, config:) + @agent = agent + @config = config + end + + def execute + refresh_projects! + refresh_groups! + + true + end + + private + + attr_reader :agent, :config + + def refresh_projects! + if allowed_project_configurations.present? + project_ids = allowed_project_configurations.map { |config| config.fetch(:project_id) } + + agent.with_lock do + agent.ci_access_project_authorizations.upsert_all(allowed_project_configurations, unique_by: [:agent_id, :project_id]) + agent.ci_access_project_authorizations.where.not(project_id: project_ids).delete_all # rubocop: disable CodeReuse/ActiveRecord + end + else + agent.ci_access_project_authorizations.delete_all(:delete_all) + end + end + + def refresh_groups! + if allowed_group_configurations.present? + group_ids = allowed_group_configurations.map { |config| config.fetch(:group_id) } + + agent.with_lock do + agent.ci_access_group_authorizations.upsert_all(allowed_group_configurations, unique_by: [:agent_id, :group_id]) + agent.ci_access_group_authorizations.where.not(group_id: group_ids).delete_all # rubocop: disable CodeReuse/ActiveRecord + end + else + agent.ci_access_group_authorizations.delete_all(:delete_all) + end + end + + def allowed_project_configurations + strong_memoize(:allowed_project_configurations) do + project_entries = extract_config_entries(entity: 'projects') + + if project_entries + allowed_projects.where_full_path_in(project_entries.keys).map do |project| + { project_id: project.id, config: project_entries[project.full_path.downcase] } + end + end + end + end + + def allowed_group_configurations + strong_memoize(:allowed_group_configurations) do + group_entries = extract_config_entries(entity: 'groups') + + if group_entries + allowed_groups.where_full_path_in(group_entries.keys).map do |group| + { group_id: group.id, config: group_entries[group.full_path.downcase] } + end + end + end + end + + def extract_config_entries(entity:) + config.dig('ci_access', entity) + &.first(AUTHORIZED_ENTITY_LIMIT) + &.index_by { |config| config.delete('id').downcase } + end + + def allowed_projects + root_ancestor.all_projects + end + + def allowed_groups + if group_root_ancestor? + root_ancestor.self_and_descendants + else + ::Group.none + end + end + + def group_root_ancestor? + root_ancestor.group_namespace? + end + end + end + end + end +end diff --git a/app/services/clusters/agents/authorize_proxy_user_service.rb b/app/services/clusters/agents/authorize_proxy_user_service.rb index ec6645b2db4..ba90d61a7ef 100644 --- a/app/services/clusters/agents/authorize_proxy_user_service.rb +++ b/app/services/clusters/agents/authorize_proxy_user_service.rb @@ -57,7 +57,7 @@ module Clusters def authorized_projects(user_access) strong_memoize_with(:authorized_projects, user_access) do user_access.fetch(:projects, []) - .first(::Clusters::Agents::RefreshAuthorizationService::AUTHORIZED_ENTITY_LIMIT) + .first(::Clusters::Agents::Authorizations::CiAccess::RefreshService::AUTHORIZED_ENTITY_LIMIT) .map { |project| ::Project.find_by_full_path(project[:id]) } .select { |project| current_user.can?(:use_k8s_proxies, project) } end @@ -66,7 +66,7 @@ module Clusters def authorized_groups(user_access) strong_memoize_with(:authorized_groups, user_access) do user_access.fetch(:groups, []) - .first(::Clusters::Agents::RefreshAuthorizationService::AUTHORIZED_ENTITY_LIMIT) + .first(::Clusters::Agents::Authorizations::CiAccess::RefreshService::AUTHORIZED_ENTITY_LIMIT) .map { |group| ::Group.find_by_full_path(group[:id]) } .select { |group| current_user.can?(:use_k8s_proxies, group) } end diff --git a/app/services/clusters/agents/filter_authorizations_service.rb b/app/services/clusters/agents/filter_authorizations_service.rb deleted file mode 100644 index 68517ceec04..00000000000 --- a/app/services/clusters/agents/filter_authorizations_service.rb +++ /dev/null @@ -1,50 +0,0 @@ -# frozen_string_literal: true - -module Clusters - module Agents - class FilterAuthorizationsService - def initialize(authorizations, filter_params) - @authorizations = authorizations - @filter_params = filter_params - - @environments_matcher = {} - end - - def execute - filter_by_environment(authorizations) - end - - private - - attr_reader :authorizations, :filter_params - - def filter_by_environment(auths) - return auths unless filter_by_environment? - - auths.select do |auth| - next true if auth.config['environments'].blank? - - auth.config['environments'].any? { |environment_pattern| matches_environment?(environment_pattern) } - end - end - - def filter_by_environment? - filter_params.has_key?(:environment) - end - - def environment_filter - @environment_filter ||= filter_params[:environment] - end - - def matches_environment?(environment_pattern) - return false if environment_filter.nil? - - environments_matcher(environment_pattern).match?(environment_filter) - end - - def environments_matcher(environment_pattern) - @environments_matcher[environment_pattern] ||= ::Gitlab::Ci::EnvironmentMatcher.new(environment_pattern) - end - end - end -end diff --git a/app/services/clusters/agents/refresh_authorization_service.rb b/app/services/clusters/agents/refresh_authorization_service.rb deleted file mode 100644 index 23ececef6a1..00000000000 --- a/app/services/clusters/agents/refresh_authorization_service.rb +++ /dev/null @@ -1,102 +0,0 @@ -# frozen_string_literal: true - -module Clusters - module Agents - class RefreshAuthorizationService - include Gitlab::Utils::StrongMemoize - - AUTHORIZED_ENTITY_LIMIT = 100 - - delegate :project, to: :agent, private: true - delegate :root_ancestor, to: :project, private: true - - def initialize(agent, config:) - @agent = agent - @config = config - end - - def execute - refresh_projects! - refresh_groups! - - true - end - - private - - attr_reader :agent, :config - - def refresh_projects! - if allowed_project_configurations.present? - project_ids = allowed_project_configurations.map { |config| config.fetch(:project_id) } - - agent.with_lock do - agent.project_authorizations.upsert_all(allowed_project_configurations, unique_by: [:agent_id, :project_id]) - agent.project_authorizations.where.not(project_id: project_ids).delete_all # rubocop: disable CodeReuse/ActiveRecord - end - else - agent.project_authorizations.delete_all(:delete_all) - end - end - - def refresh_groups! - if allowed_group_configurations.present? - group_ids = allowed_group_configurations.map { |config| config.fetch(:group_id) } - - agent.with_lock do - agent.group_authorizations.upsert_all(allowed_group_configurations, unique_by: [:agent_id, :group_id]) - agent.group_authorizations.where.not(group_id: group_ids).delete_all # rubocop: disable CodeReuse/ActiveRecord - end - else - agent.group_authorizations.delete_all(:delete_all) - end - end - - def allowed_project_configurations - strong_memoize(:allowed_project_configurations) do - project_entries = extract_config_entries(entity: 'projects') - - if project_entries - allowed_projects.where_full_path_in(project_entries.keys).map do |project| - { project_id: project.id, config: project_entries[project.full_path.downcase] } - end - end - end - end - - def allowed_group_configurations - strong_memoize(:allowed_group_configurations) do - group_entries = extract_config_entries(entity: 'groups') - - if group_entries - allowed_groups.where_full_path_in(group_entries.keys).map do |group| - { group_id: group.id, config: group_entries[group.full_path.downcase] } - end - end - end - end - - def extract_config_entries(entity:) - config.dig('ci_access', entity) - &.first(AUTHORIZED_ENTITY_LIMIT) - &.index_by { |config| config.delete('id').downcase } - end - - def allowed_projects - root_ancestor.all_projects - end - - def allowed_groups - if group_root_ancestor? - root_ancestor.self_and_descendants - else - ::Group.none - end - end - - def group_root_ancestor? - root_ancestor.group_namespace? - end - end - end -end diff --git a/app/validators/json_schemas/cluster_agent_authorization_configuration.json b/app/validators/json_schemas/clusters_agents_authorizations_ci_access_config.json index f3de0b7043b..f3de0b7043b 100644 --- a/app/validators/json_schemas/cluster_agent_authorization_configuration.json +++ b/app/validators/json_schemas/clusters_agents_authorizations_ci_access_config.json diff --git a/app/views/search/_results.html.haml b/app/views/search/_results.html.haml index 3280dcf2cd4..99558f61b25 100644 --- a/app/views/search/_results.html.haml +++ b/app/views/search/_results.html.haml @@ -1,9 +1,5 @@ -- search_bar_classes = 'search-sidebar gl-display-flex gl-flex-direction-column gl-mr-4' - = render_if_exists 'shared/promotions/promote_advanced_search' -.results.gl-md-display-flex.gl-mt-0 - #js-search-sidebar{ class: search_bar_classes, data: { navigation_json: search_navigation_json } } - .gl-w-full.gl-flex-grow-1.gl-overflow-x-hidden - = render partial: 'search/results_status' if @search_objects.present? - = render partial: 'search/results_list' +.gl-w-full.gl-flex-grow-1.gl-overflow-x-hidden + = render partial: 'search/results_status' unless @search_objects.to_a.empty? + = render partial: 'search/results_list' diff --git a/app/views/search/show.html.haml b/app/views/search/show.html.haml index 826d78c470d..934f59ea586 100644 --- a/app/views/search/show.html.haml +++ b/app/views/search/show.html.haml @@ -1,12 +1,14 @@ - @hide_top_links = true - breadcrumb_title _('Search') - page_title @search_term +- nav 'search' - if params[:group_id].present? = hidden_field_tag :group_id, params[:group_id] - if params[:project_id].present? = hidden_field_tag :project_id, params[:project_id] - group_attributes = @group&.attributes&.slice('id', 'name')&.merge(full_name: @group&.full_name) - project_attributes = @project&.attributes&.slice('id', 'namespace_id', 'name')&.merge(name_with_namespace: @project&.name_with_namespace) +- search_bar_classes = 'search-sidebar gl-display-flex gl-flex-direction-column gl-mr-4' - if @search_results && !(@search_results.respond_to?(:failed?) && @search_results.failed?) - if @search_service_presenter.without_count? @@ -20,5 +22,7 @@ = render_if_exists 'search/form_elasticsearch', attrs: { class: 'mb-2 mb-sm-0 align-self-center' } #js-search-topbar{ data: { "group-initial-json": group_attributes.to_json, "project-initial-json": project_attributes.to_json, "elasticsearch-enabled": @search_service_presenter.advanced_search_enabled?.to_s, "default-branch-name": @project&.default_branch } } -- if @search_term - = render 'search/results' +.results.gl-md-display-flex.gl-mt-0 + #js-search-sidebar{ class: search_bar_classes, data: { navigation_json: search_navigation_json } } + - if @search_term + = render 'search/results' diff --git a/config/feature_categories.yml b/config/feature_categories.yml index eb20a65caf7..296fb69b83f 100644 --- a/config/feature_categories.yml +++ b/config/feature_categories.yml @@ -43,7 +43,6 @@ - dependency_firewall - dependency_management - dependency_proxy -- dependency_scanning - deployment_management - design_management - design_system @@ -80,7 +79,6 @@ - interactive_application_security_testing - internationalization - kubernetes_management -- license_compliance - logging - merge_trains - metrics @@ -123,6 +121,7 @@ - service_desk - service_ping - sm_provisioning +- software_composition_analysis - source_code_management - static_application_security_testing - subgroups diff --git a/db/docs/agent_group_authorizations.yml b/db/docs/agent_group_authorizations.yml index 61c8733383a..c300ed3ba08 100644 --- a/db/docs/agent_group_authorizations.yml +++ b/db/docs/agent_group_authorizations.yml @@ -1,7 +1,7 @@ --- table_name: agent_group_authorizations classes: -- Clusters::Agents::GroupAuthorization +- Clusters::Agents::Authorizations::CiAccess::GroupAuthorization feature_categories: - kubernetes_management description: Configuration for a group that is authorized to use a particular cluster agent diff --git a/db/docs/agent_project_authorizations.yml b/db/docs/agent_project_authorizations.yml index e595c84b5d5..98a74b9f9b7 100644 --- a/db/docs/agent_project_authorizations.yml +++ b/db/docs/agent_project_authorizations.yml @@ -1,7 +1,7 @@ --- table_name: agent_project_authorizations classes: -- Clusters::Agents::ProjectAuthorization +- Clusters::Agents::Authorizations::CiAccess::ProjectAuthorization feature_categories: - kubernetes_management description: Configuration for a project that is authorized to use a particular cluster agent diff --git a/db/docs/pm_checkpoints.yml b/db/docs/pm_checkpoints.yml index e360e8ad356..2e42077943d 100644 --- a/db/docs/pm_checkpoints.yml +++ b/db/docs/pm_checkpoints.yml @@ -3,7 +3,7 @@ table_name: pm_checkpoints classes: - PackageMetadata::Checkpoint feature_categories: -- license_compliance +- software_composition_analysis description: Tracks position of last synced file. introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/109713 milestone: '15.9' diff --git a/db/docs/pm_licenses.yml b/db/docs/pm_licenses.yml index 55ef2719cbc..31d9ae7a179 100644 --- a/db/docs/pm_licenses.yml +++ b/db/docs/pm_licenses.yml @@ -3,7 +3,7 @@ table_name: pm_licenses classes: - PackageMetadata::License feature_categories: - - license_compliance + - software_composition_analysis description: Tracks licenses referenced by public package registries. introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/102794 milestone: '15.6' diff --git a/db/docs/pm_package_version_licenses.yml b/db/docs/pm_package_version_licenses.yml index 439162ecf9d..1e3f21c4383 100644 --- a/db/docs/pm_package_version_licenses.yml +++ b/db/docs/pm_package_version_licenses.yml @@ -3,7 +3,7 @@ table_name: pm_package_version_licenses classes: - PackageMetadata::PackageVersionLicense feature_categories: - - license_compliance + - software_composition_analysis description: Tracks licenses under which a given package version has been published. introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/102794 milestone: '15.6' diff --git a/db/docs/pm_package_versions.yml b/db/docs/pm_package_versions.yml index 7b015ddc174..b216d58ce99 100644 --- a/db/docs/pm_package_versions.yml +++ b/db/docs/pm_package_versions.yml @@ -3,7 +3,7 @@ table_name: pm_package_versions classes: - PackageMetadata::PackageVersion feature_categories: -- license_compliance +- software_composition_analysis description: Tracks package versions served by public package registries. introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/102794 milestone: '15.6' diff --git a/db/docs/pm_packages.yml b/db/docs/pm_packages.yml index 35932b37990..b114e75791f 100644 --- a/db/docs/pm_packages.yml +++ b/db/docs/pm_packages.yml @@ -3,7 +3,7 @@ table_name: pm_packages classes: - PackageMetadata::Package feature_categories: -- license_compliance +- software_composition_analysis description: Tracks packages served by public package registries. introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/102794 milestone: '15.6' diff --git a/db/docs/project_security_settings.yml b/db/docs/project_security_settings.yml index 99a767978fb..af559d11164 100644 --- a/db/docs/project_security_settings.yml +++ b/db/docs/project_security_settings.yml @@ -3,7 +3,7 @@ table_name: project_security_settings classes: - ProjectSecuritySetting feature_categories: -- dependency_scanning +- software_composition_analysis - container_scanning description: Project settings related to security features. introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/32577 diff --git a/db/docs/vulnerability_advisories.yml b/db/docs/vulnerability_advisories.yml index 18029e784b5..6ce7f30aa7c 100644 --- a/db/docs/vulnerability_advisories.yml +++ b/db/docs/vulnerability_advisories.yml @@ -4,8 +4,7 @@ classes: - Vulnerabilities::Advisory feature_categories: - container_scanning -- dependency_scanning -- license_compliance +- software_composition_analysis description: Stores vulnerability advisories introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/95622 milestone: '15.4' diff --git a/db/post_migrate/20230405072302_remove_p_ci_builds_metadata_partition_id_default.rb b/db/post_migrate/20230405072302_remove_p_ci_builds_metadata_partition_id_default.rb new file mode 100644 index 00000000000..303210d85c7 --- /dev/null +++ b/db/post_migrate/20230405072302_remove_p_ci_builds_metadata_partition_id_default.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class RemovePCiBuildsMetadataPartitionIdDefault < Gitlab::Database::Migration[2.1] + enable_lock_retries! + + def up + change_column_default :p_ci_builds_metadata, :partition_id, from: 100, to: nil + end + + def down + change_column_default :p_ci_builds_metadata, :partition_id, from: nil, to: 100 + end +end diff --git a/db/post_migrate/20230411153310_cleanup_bigint_conversion_for_sent_notifications.rb b/db/post_migrate/20230411153310_cleanup_bigint_conversion_for_sent_notifications.rb new file mode 100644 index 00000000000..e5f690a0a5a --- /dev/null +++ b/db/post_migrate/20230411153310_cleanup_bigint_conversion_for_sent_notifications.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class CleanupBigintConversionForSentNotifications < Gitlab::Database::Migration[2.1] + enable_lock_retries! + + TABLE = :sent_notifications + COLUMNS = [:id] + + def up + cleanup_conversion_of_integer_to_bigint(TABLE, COLUMNS) + end + + def down + restore_conversion_of_integer_to_bigint(TABLE, COLUMNS) + end +end diff --git a/db/schema_migrations/20230405072302 b/db/schema_migrations/20230405072302 new file mode 100644 index 00000000000..cfa7bf7f175 --- /dev/null +++ b/db/schema_migrations/20230405072302 @@ -0,0 +1 @@ +d93a103c002a536d11f75256f20e2b8708ec760286f65d89ab5abe446fe629d4
\ No newline at end of file diff --git a/db/schema_migrations/20230411153310 b/db/schema_migrations/20230411153310 new file mode 100644 index 00000000000..14f317ccf08 --- /dev/null +++ b/db/schema_migrations/20230411153310 @@ -0,0 +1 @@ +f4472433ac5b74296409a04790d64ed56551358c98428ebb2e5f15d2f3e2db31
\ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 4a656b18fec..b6e93ad1f95 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -309,15 +309,6 @@ BEGIN END; $$; -CREATE FUNCTION trigger_7f4fcd5aa322() RETURNS trigger - LANGUAGE plpgsql - AS $$ -BEGIN - NEW."id_convert_to_bigint" := NEW."id"; - RETURN NEW; -END; -$$; - CREATE FUNCTION trigger_909cf0a06094() RETURNS trigger LANGUAGE plpgsql AS $$ @@ -13054,7 +13045,7 @@ CREATE TABLE p_ci_builds_metadata ( id bigint NOT NULL, runtime_runner_features jsonb DEFAULT '{}'::jsonb NOT NULL, id_tokens jsonb DEFAULT '{}'::jsonb NOT NULL, - partition_id bigint DEFAULT 100 NOT NULL, + partition_id bigint NOT NULL, debug_trace_enabled boolean DEFAULT false NOT NULL ) PARTITION BY LIST (partition_id); @@ -13083,7 +13074,7 @@ CREATE TABLE ci_builds_metadata ( id bigint DEFAULT nextval('ci_builds_metadata_id_seq'::regclass) NOT NULL, runtime_runner_features jsonb DEFAULT '{}'::jsonb NOT NULL, id_tokens jsonb DEFAULT '{}'::jsonb NOT NULL, - partition_id bigint DEFAULT 100 NOT NULL, + partition_id bigint NOT NULL, debug_trace_enabled boolean DEFAULT false NOT NULL ); ALTER TABLE ONLY p_ci_builds_metadata ATTACH PARTITION ci_builds_metadata FOR VALUES IN ('100'); @@ -22292,7 +22283,6 @@ CREATE SEQUENCE self_managed_prometheus_alert_events_id_seq ALTER SEQUENCE self_managed_prometheus_alert_events_id_seq OWNED BY self_managed_prometheus_alert_events.id; CREATE TABLE sent_notifications ( - id_convert_to_bigint integer DEFAULT 0 NOT NULL, project_id integer, noteable_id integer, noteable_type character varying, @@ -34227,8 +34217,6 @@ CREATE TRIGGER trigger_482bac5ec48a BEFORE INSERT OR UPDATE ON system_note_metad CREATE TRIGGER trigger_775287b6d67a BEFORE INSERT OR UPDATE ON note_diff_files FOR EACH ROW EXECUTE FUNCTION trigger_775287b6d67a(); -CREATE TRIGGER trigger_7f4fcd5aa322 BEFORE INSERT OR UPDATE ON sent_notifications FOR EACH ROW EXECUTE FUNCTION trigger_7f4fcd5aa322(); - CREATE TRIGGER trigger_909cf0a06094 BEFORE INSERT OR UPDATE ON award_emoji FOR EACH ROW EXECUTE FUNCTION trigger_909cf0a06094(); CREATE TRIGGER trigger_bfc6e47be8cc BEFORE INSERT OR UPDATE ON snippet_user_mentions FOR EACH ROW EXECUTE FUNCTION trigger_bfc6e47be8cc(); diff --git a/doc/administration/audit_events.md b/doc/administration/audit_events.md index e48112af870..53630e915bb 100644 --- a/doc/administration/audit_events.md +++ b/doc/administration/audit_events.md @@ -324,30 +324,28 @@ GitLab generates audit events when a cluster agent token is created or revoked. ### Instance events **(PREMIUM SELF)** +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/16826) in GitLab 13.5, audit events for failed second-factor authentication attempt. +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/276250) in GitLab 13.6, audit events for when a user is approved using the Admin Area. +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/276921) in GitLab 13.6, audit events for when a user's personal access token is successfully or unsuccessfully created or revoked. +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/298783) in GitLab 13.9, audit events for when a user requests access to an instance or is rejected using the Admin Area. + The following user actions on a GitLab instance generate instance audit events: -- Sign-in events and the authentication type (such as standard, LDAP, or OmniAuth) -- Failed sign-ins -- Added SSH key -- Added or removed email -- Changed password -- Ask for password reset -- Grant OAuth access -- Started or stopped user impersonation -- Changed username. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/7797) in GitLab 12.8. -- User was deleted. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/251) in GitLab 12.8. -- User was added. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/251) in GitLab 12.8. -- User requests access to an instance. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/298783) in GitLab 13.9. -- User was approved using the Admin Area. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/276250) in GitLab 13.6. -- User was rejected using the Admin Area. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/298783) in GitLab 13.9. -- User was blocked using the Admin Area. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/251) in GitLab 12.8. -- User was blocked using the API. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/25872) in GitLab 12.9. -- Failed second-factor authentication attempt. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/16826) in - GitLab 13.5. -- A user's personal access token was successfully created or revoked. - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/276921) in GitLab 13.6. -- A failed attempt to create or revoke a user's personal access token. - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/276921) in GitLab 13.6. +- Sign-in events and the authentication type such as standard, LDAP, or OmniAuth. +- Failed sign-ins. +- Added SSH key. +- Added or removed email. +- Changed password. +- Ask for password reset. +- Grant OAuth access. +- Started or stopped user impersonation. +- Changed username. +- User was added or deleted. +- User requests access to an instance. +- User was approved, rejected, or blocked using the Admin Area. +- User was blocked using the API. +- Failed second-factor authentication attempt. +- A user's personal access token was successfully or unsuccessfully created or revoked. - Administrator added or removed. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/323905) in GitLab 14.1. - Removed SSH key. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/220127) in GitLab 14.1. - Added or removed GPG key. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/220127) in GitLab 14.1. diff --git a/doc/api/runners.md b/doc/api/runners.md index be69b762555..1a722163c41 100644 --- a/doc/api/runners.md +++ b/doc/api/runners.md @@ -774,7 +774,7 @@ Example response: } ``` -## Reset instance's runner registration token +## Reset instance's runner registration token (deprecated) > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/30942) in GitLab 14.3. > - [Deprecated](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/104691) in GitLab 15.7. @@ -793,7 +793,7 @@ curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" \ "https://gitlab.example.com/api/v4/runners/reset_registration_token" ``` -## Reset project's runner registration token +## Reset project's runner registration token (deprecated) > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/30942) in GitLab 14.3. > - [Deprecated](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/104691) in GitLab 15.7. @@ -812,7 +812,7 @@ curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" \ "https://gitlab.example.com/api/v4/projects/9/runners/reset_registration_token" ``` -## Reset group's runner registration token +## Reset group's runner registration token (deprecated) > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/30942) in GitLab 14.3. > - [Deprecated](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/104691) in GitLab 15.7. diff --git a/doc/integration/jira/dvcs/index.md b/doc/integration/jira/dvcs/index.md index 16b7f5d6217..7d10efa52fd 100644 --- a/doc/integration/jira/dvcs/index.md +++ b/doc/integration/jira/dvcs/index.md @@ -149,8 +149,8 @@ For more information, see [Install the GitLab for Jira Cloud app](../connect-app ### Feature comparison of DVCS and GitLab for Jira Cloud app -| Feature | DVCS (Jira Cloud) | GitLab for Jira Cloud app | -|--------------------|--------------------|---------------------------------------| +| Feature | DVCS | GitLab for Jira Cloud app | +|--------------------|------------------------|---------------------------| | Smart Commits | **{check-circle}** Yes | **{check-circle}** Yes | | Sync MRs | **{check-circle}** Yes | **{check-circle}** Yes | | Sync branches | **{check-circle}** Yes | **{check-circle}** Yes | diff --git a/doc/user/application_security/container_scanning/index.md b/doc/user/application_security/container_scanning/index.md index 22e36122b88..793c9a89ec0 100644 --- a/doc/user/application_security/container_scanning/index.md +++ b/doc/user/application_security/container_scanning/index.md @@ -22,6 +22,9 @@ vulnerabilities. By including an extra Container Scanning job in your pipeline t vulnerabilities and displays them in a merge request, you can use GitLab to audit your Docker-based apps. +<i class="fa fa-youtube-play youtube" aria-hidden="true"></i> +For an overview, see [Container Scanning](https://www.youtube.com/watch?v=C0jn2eN5MAs). + Container Scanning is often considered part of Software Composition Analysis (SCA). SCA can contain aspects of inspecting the items your code uses. These items typically include application and system dependencies that are almost always imported from external sources, rather than sourced from items diff --git a/doc/user/application_security/dependency_scanning/index.md b/doc/user/application_security/dependency_scanning/index.md index f9db8d4409e..6b2824e675e 100644 --- a/doc/user/application_security/dependency_scanning/index.md +++ b/doc/user/application_security/dependency_scanning/index.md @@ -35,6 +35,9 @@ vulnerability. ![Dependency scanning Widget](img/dependency_scanning_v13_2.png) +<i class="fa fa-youtube-play youtube" aria-hidden="true"></i> +For an overview, see [Dependency Scanning](https://www.youtube.com/watch?v=TBnfbGk4c4o). + ## Dependency Scanning compared to Container Scanning GitLab offers both Dependency Scanning and Container Scanning diff --git a/doc/user/project/web_ide_beta/index.md b/doc/user/project/web_ide_beta/index.md index 5aeb34f7aa7..3051ee3bbfd 100644 --- a/doc/user/project/web_ide_beta/index.md +++ b/doc/user/project/web_ide_beta/index.md @@ -48,9 +48,9 @@ To open the Web IDE Beta from a merge request: 1. Go to your merge request. 1. In the upper-right corner, select **Code > Open in Web IDE**. -The Web IDE Beta opens modified and created files in separate tabs and displays changes side by side with the original source. To optimize loading time, only the top 10 files (by number of lines changed) are opened automatically. +The Web IDE Beta opens new and modified files in separate tabs and displays changes side by side with the original source. To optimize loading time, only the top 10 files (by number of lines changed) are opened automatically. -In the file tree, any modified or created file in the merge request is indicated by an icon next to the filename. To view changes to a file, right-click the filename and select **Compare with merge request base**. +In the file tree, any new or modified file in the merge request is indicated by an icon next to the filename. To view changes to a file, right-click the filename and select **Compare with merge request base**. ## Open a file in the Web IDE Beta diff --git a/lib/api/ci/jobs.rb b/lib/api/ci/jobs.rb index 30d12864bf8..2e377f41b66 100644 --- a/lib/api/ci/jobs.rb +++ b/lib/api/ci/jobs.rb @@ -266,14 +266,14 @@ module API persisted_environment = current_authenticated_job.actual_persisted_environment environment = { tier: persisted_environment.tier, slug: persisted_environment.slug } if persisted_environment - agent_authorizations = ::Clusters::Agents::FilterAuthorizationsService.new( - ::Clusters::AgentAuthorizationsFinder.new(project).execute, + agent_authorizations = ::Clusters::Agents::Authorizations::CiAccess::FilterService.new( + ::Clusters::Agents::Authorizations::CiAccess::Finder.new(project).execute, environment: persisted_environment&.name ).execute # See https://gitlab.com/gitlab-org/cluster-integration/gitlab-agent/-/blob/master/doc/kubernetes_ci_access.md#apiv4joballowed_agents-api { - allowed_agents: Entities::Clusters::AgentAuthorization.represent(agent_authorizations), + allowed_agents: Entities::Clusters::Agents::Authorizations::CiAccess.represent(agent_authorizations), job: { id: current_authenticated_job.id }, pipeline: { id: pipeline.id }, project: { id: project.id, groups: project_groups }, diff --git a/lib/api/entities/clusters/agent_authorization.rb b/lib/api/entities/clusters/agent_authorization.rb deleted file mode 100644 index 7bbe0f1ec45..00000000000 --- a/lib/api/entities/clusters/agent_authorization.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -module API - module Entities - module Clusters - class AgentAuthorization < Grape::Entity - expose :agent_id, as: :id - expose :config_project, with: Entities::ProjectIdentity - expose :config, as: :configuration - end - end - end -end diff --git a/lib/api/entities/clusters/agents/authorizations/ci_access.rb b/lib/api/entities/clusters/agents/authorizations/ci_access.rb new file mode 100644 index 00000000000..2eefc4361b1 --- /dev/null +++ b/lib/api/entities/clusters/agents/authorizations/ci_access.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module API + module Entities + module Clusters + module Agents + module Authorizations + class CiAccess < Grape::Entity + expose :agent_id, as: :id + expose :config_project, with: Entities::ProjectIdentity + expose :config, as: :configuration + end + end + end + end + end +end diff --git a/lib/api/internal/kubernetes.rb b/lib/api/internal/kubernetes.rb index bf9612db6bf..94764d0dda3 100644 --- a/lib/api/internal/kubernetes.rb +++ b/lib/api/internal/kubernetes.rb @@ -129,7 +129,7 @@ module API post '/', feature_category: :kubernetes_management, urgency: :low do agent = ::Clusters::Agent.find(params[:agent_id]) - ::Clusters::Agents::RefreshAuthorizationService.new(agent, config: params[:agent_config]).execute + ::Clusters::Agents::Authorizations::CiAccess::RefreshService.new(agent, config: params[:agent_config]).execute no_content! end diff --git a/lib/gitlab/slug/environment.rb b/lib/gitlab/slug/environment.rb index fd70def8e7c..892d5350db5 100644 --- a/lib/gitlab/slug/environment.rb +++ b/lib/gitlab/slug/environment.rb @@ -21,7 +21,7 @@ module Gitlab slugified = name.to_s.downcase.gsub(/[^a-z0-9]/, '-') # Must start with a letter - slugified = 'env-' + slugified unless slugified.match?(/^[a-z]/) + slugified = "env-#{slugified}" unless slugified.match?(/^[a-z]/) # Repeated dashes are invalid (OpenShift limitation) slugified.squeeze!('-') diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 2aab5dd5da6..689a1464a62 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -15493,6 +15493,9 @@ msgstr "" msgid "Download artifacts" msgstr "" +msgid "Download as CSV" +msgstr "" + msgid "Download codes" msgstr "" @@ -39929,6 +39932,9 @@ msgstr "" msgid "SecurityReports|Configure security testing" msgstr "" +msgid "SecurityReports|Create Issue" +msgstr "" + msgid "SecurityReports|Create Jira issue" msgstr "" @@ -39998,6 +40004,9 @@ msgstr "" msgid "SecurityReports|Image" msgstr "" +msgid "SecurityReports|Investigate this vulnerability by creating an issue" +msgstr "" + msgid "SecurityReports|Issue" msgstr "" @@ -40151,6 +40160,9 @@ msgstr "" msgid "SecurityReports|There was an error creating the issue." msgstr "" +msgid "SecurityReports|There was an error creating the issue. Please try again." +msgstr "" + msgid "SecurityReports|There was an error creating the merge request." msgstr "" diff --git a/scripts/review_apps/automated_cleanup.rb b/scripts/review_apps/automated_cleanup.rb index 36472056e76..154a73462bb 100755 --- a/scripts/review_apps/automated_cleanup.rb +++ b/scripts/review_apps/automated_cleanup.rb @@ -183,12 +183,6 @@ module ReviewApps kubernetes.cleanup_namespaces_by_created_at(created_before: threshold_time(days: days)) unless dry_run end - def perform_stale_pvc_cleanup!(days:) - puts "Dry-run mode." if dry_run - - kubernetes.cleanup_pvcs_by_created_at(created_before: threshold_time(days: days)) unless dry_run - end - private attr_reader :api_endpoint, :dry_run, :gitlab_token, :project_path @@ -322,10 +316,4 @@ if $PROGRAM_NAME == __FILE__ timed('Stale Namespace cleanup') do automated_cleanup.perform_stale_namespace_cleanup!(days: 3) end - - puts - - timed('Stale PVC cleanup') do - automated_cleanup.perform_stale_pvc_cleanup!(days: 30) - end end diff --git a/scripts/review_apps/k8s-resources-count-checks.sh b/scripts/review_apps/k8s-resources-count-checks.sh index b63fa043065..7ce98f8d164 100755 --- a/scripts/review_apps/k8s-resources-count-checks.sh +++ b/scripts/review_apps/k8s-resources-count-checks.sh @@ -15,6 +15,9 @@ function k8s_resource_count() { SERVICES_COUNT_THRESHOLD=3000 REVIEW_APPS_COUNT_THRESHOLD=200 +# One review app currently deploys 4 PVCs +PVCS_COUNT_THRESHOLD=$((REVIEW_APPS_COUNT_THRESHOLD * 4)) + exit_with_error=false # In the current GKE cluster configuration, we should never go higher than 4096 services per cluster. @@ -36,6 +39,12 @@ if [ "$(echo $(($namespaces_count - $review_apps_count)) | sed 's/-//')" -gt 30 exit_with_error=true fi +pvcs_count=$(kubectl get pvc -A | wc -l | xargs) +if [ "${pvcs_count}" -gt "${PVCS_COUNT_THRESHOLD}" ]; then + >&2 echo "❌ [ERROR] PVCs are above ${PVCS_COUNT_THRESHOLD} (currently at ${pvcs_count})" + exit_with_error=true +fi + if [ "${exit_with_error}" = true ] ; then exit 1 fi diff --git a/spec/factories/clusters/agents/group_authorizations.rb b/spec/factories/clusters/agents/authorizations/ci_access/group_authorizations.rb index abe25794234..659114eef8e 100644 --- a/spec/factories/clusters/agents/group_authorizations.rb +++ b/spec/factories/clusters/agents/authorizations/ci_access/group_authorizations.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true FactoryBot.define do - factory :agent_group_authorization, class: 'Clusters::Agents::GroupAuthorization' do + factory :agent_ci_access_group_authorization, class: 'Clusters::Agents::Authorizations::CiAccess::GroupAuthorization' do association :agent, factory: :cluster_agent group diff --git a/spec/factories/clusters/agents/project_authorizations.rb b/spec/factories/clusters/agents/authorizations/ci_access/project_authorizations.rb index eecbfe95bfc..10d4f8fb946 100644 --- a/spec/factories/clusters/agents/project_authorizations.rb +++ b/spec/factories/clusters/agents/authorizations/ci_access/project_authorizations.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true FactoryBot.define do - factory :agent_project_authorization, class: 'Clusters::Agents::ProjectAuthorization' do + factory :agent_ci_access_project_authorization, class: 'Clusters::Agents::Authorizations::CiAccess::ProjectAuthorization' do association :agent, factory: :cluster_agent project diff --git a/spec/finders/clusters/agent_authorizations_finder_spec.rb b/spec/finders/clusters/agents/authorizations/ci_access/finder_spec.rb index f680792d6c4..c311b19139f 100644 --- a/spec/finders/clusters/agent_authorizations_finder_spec.rb +++ b/spec/finders/clusters/agents/authorizations/ci_access/finder_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Clusters::AgentAuthorizationsFinder do +RSpec.describe Clusters::Agents::Authorizations::CiAccess::Finder, feature_category: :kubernetes_management do describe '#execute' do let_it_be(:top_level_group) { create(:group) } let_it_be(:subgroup1) { create(:group, parent: top_level_group) } @@ -54,34 +54,34 @@ RSpec.describe Clusters::AgentAuthorizationsFinder do let(:unrelated_agent) { create(:cluster_agent) } before do - create(:agent_project_authorization, agent: unrelated_agent, project: requesting_project) + create(:agent_ci_access_project_authorization, agent: unrelated_agent, project: requesting_project) end it { is_expected.to be_empty } end context 'agent configuration project shares a root namespace, but does not belong to an ancestor of the given project' do - let!(:project_authorization) { create(:agent_project_authorization, agent: non_ancestor_agent, project: requesting_project) } + let!(:project_authorization) { create(:agent_ci_access_project_authorization, agent: non_ancestor_agent, project: requesting_project) } it { is_expected.to match_array([project_authorization]) } end context 'with project authorizations present' do - let!(:authorization) { create(:agent_project_authorization, agent: production_agent, project: requesting_project) } + let!(:authorization) { create(:agent_ci_access_project_authorization, agent: production_agent, project: requesting_project) } it { is_expected.to match_array [authorization] } end context 'with overlapping authorizations' do let!(:agent) { create(:cluster_agent, project: requesting_project) } - let!(:project_authorization) { create(:agent_project_authorization, agent: agent, project: requesting_project) } - let!(:group_authorization) { create(:agent_group_authorization, agent: agent, group: bottom_level_group) } + let!(:project_authorization) { create(:agent_ci_access_project_authorization, agent: agent, project: requesting_project) } + let!(:group_authorization) { create(:agent_ci_access_group_authorization, agent: agent, group: bottom_level_group) } it { is_expected.to match_array [project_authorization] } end it_behaves_like 'access_as' do - let!(:authorization) { create(:agent_project_authorization, agent: production_agent, project: requesting_project, config: config) } + let!(:authorization) { create(:agent_ci_access_project_authorization, agent: production_agent, project: requesting_project, config: config) } end end @@ -92,7 +92,7 @@ RSpec.describe Clusters::AgentAuthorizationsFinder do expect(subject.count).to eq(1) authorization = subject.first - expect(authorization).to be_a(Clusters::Agents::ImplicitAuthorization) + expect(authorization).to be_a(Clusters::Agents::Authorizations::CiAccess::ImplicitAuthorization) expect(authorization.agent).to eq(associated_agent) end end @@ -102,15 +102,15 @@ RSpec.describe Clusters::AgentAuthorizationsFinder do let(:unrelated_agent) { create(:cluster_agent) } before do - create(:agent_group_authorization, agent: unrelated_agent, group: top_level_group) + create(:agent_ci_access_group_authorization, agent: unrelated_agent, group: top_level_group) end it { is_expected.to be_empty } end context 'multiple agents are authorized for the same group' do - let!(:staging_auth) { create(:agent_group_authorization, agent: staging_agent, group: bottom_level_group) } - let!(:production_auth) { create(:agent_group_authorization, agent: production_agent, group: bottom_level_group) } + let!(:staging_auth) { create(:agent_ci_access_group_authorization, agent: staging_agent, group: bottom_level_group) } + let!(:production_auth) { create(:agent_ci_access_group_authorization, agent: production_agent, group: bottom_level_group) } it 'returns authorizations for all agents' do expect(subject).to contain_exactly(staging_auth, production_auth) @@ -118,8 +118,8 @@ RSpec.describe Clusters::AgentAuthorizationsFinder do end context 'a single agent is authorized to more than one matching group' do - let!(:bottom_level_auth) { create(:agent_group_authorization, agent: production_agent, group: bottom_level_group) } - let!(:top_level_auth) { create(:agent_group_authorization, agent: production_agent, group: top_level_group) } + let!(:bottom_level_auth) { create(:agent_ci_access_group_authorization, agent: production_agent, group: bottom_level_group) } + let!(:top_level_auth) { create(:agent_ci_access_group_authorization, agent: production_agent, group: top_level_group) } it 'picks the authorization for the closest group to the requesting project' do expect(subject).to contain_exactly(bottom_level_auth) @@ -127,13 +127,13 @@ RSpec.describe Clusters::AgentAuthorizationsFinder do end context 'agent configuration project does not belong to an ancestor of the authorized group' do - let!(:group_authorization) { create(:agent_group_authorization, agent: non_ancestor_agent, group: bottom_level_group) } + let!(:group_authorization) { create(:agent_ci_access_group_authorization, agent: non_ancestor_agent, group: bottom_level_group) } it { is_expected.to match_array([group_authorization]) } end it_behaves_like 'access_as' do - let!(:authorization) { create(:agent_group_authorization, agent: production_agent, group: top_level_group, config: config) } + let!(:authorization) { create(:agent_ci_access_group_authorization, agent: production_agent, group: top_level_group, config: config) } end end end diff --git a/spec/frontend/design_management/pages/design/__snapshots__/index_spec.js.snap b/spec/frontend/design_management/pages/design/__snapshots__/index_spec.js.snap index d86fbf81d20..18b63082e4a 100644 --- a/spec/frontend/design_management/pages/design/__snapshots__/index_spec.js.snap +++ b/spec/frontend/design_management/pages/design/__snapshots__/index_spec.js.snap @@ -2,7 +2,7 @@ exports[`Design management design index page renders design index 1`] = ` <div - class="design-detail js-design-detail fixed-top gl-w-full gl-bottom-0 gl-display-flex gl-justify-content-center gl-flex-direction-column gl-lg-flex-direction-row" + class="design-detail js-design-detail fixed-top gl-w-full gl-display-flex gl-justify-content-center gl-flex-direction-column gl-lg-flex-direction-row" > <div class="gl-display-flex gl-overflow-hidden gl-flex-grow-1 gl-flex-direction-column gl-relative" @@ -115,7 +115,7 @@ exports[`Design management design index page renders design index 1`] = ` exports[`Design management design index page with error GlAlert is rendered in correct position with correct content 1`] = ` <div - class="design-detail js-design-detail fixed-top gl-w-full gl-bottom-0 gl-display-flex gl-justify-content-center gl-flex-direction-column gl-lg-flex-direction-row" + class="design-detail js-design-detail fixed-top gl-w-full gl-display-flex gl-justify-content-center gl-flex-direction-column gl-lg-flex-direction-row" > <div class="gl-display-flex gl-overflow-hidden gl-flex-grow-1 gl-flex-direction-column gl-relative" diff --git a/spec/frontend/ml/experiment_tracking/routes/experiments/show/components/experiment_header_spec.js b/spec/frontend/ml/experiment_tracking/routes/experiments/show/components/experiment_header_spec.js new file mode 100644 index 00000000000..b56755043fb --- /dev/null +++ b/spec/frontend/ml/experiment_tracking/routes/experiments/show/components/experiment_header_spec.js @@ -0,0 +1,55 @@ +import { GlButton } from '@gitlab/ui'; +import { mountExtended } from 'helpers/vue_test_utils_helper'; +import ExperimentHeader from '~/ml/experiment_tracking/routes/experiments/show/components/experiment_header.vue'; +import DeleteButton from '~/ml/experiment_tracking/components/delete_button.vue'; +import setWindowLocation from 'helpers/set_window_location_helper'; +import * as urlHelpers from '~/lib/utils/url_utility'; +import { MOCK_EXPERIMENT } from '../mock_data'; + +const DELETE_INFO = { + deletePath: '/delete', + deleteConfirmationText: 'MODAL_BODY', + actionPrimaryText: 'Delete!', + modalTitle: 'MODAL_TITLE', +}; + +describe('~/ml/experiment_tracking/routes/experiments/show/components/experiment_header.vue', () => { + let wrapper; + + const createWrapper = () => { + wrapper = mountExtended(ExperimentHeader, { + propsData: { title: MOCK_EXPERIMENT.name, deleteInfo: DELETE_INFO }, + }); + }; + + const findDeleteButton = () => wrapper.findComponent(DeleteButton); + const findButton = () => wrapper.findComponent(GlButton); + + beforeEach(createWrapper); + + describe('Delete', () => { + it('shows delete button', () => { + expect(findDeleteButton().exists()).toBe(true); + }); + + it('passes the right props', () => { + expect(findDeleteButton().props()).toMatchObject(DELETE_INFO); + }); + }); + + describe('CSV download', () => { + it('shows download CSV button', () => { + expect(findDeleteButton().exists()).toBe(true); + }); + + it('calls the action to download the CSV', () => { + setWindowLocation('https://blah.com/something/1?name=query&orderBy=name'); + jest.spyOn(urlHelpers, 'visitUrl').mockImplementation(() => {}); + + findButton().vm.$emit('click'); + + expect(urlHelpers.visitUrl).toHaveBeenCalledTimes(1); + expect(urlHelpers.visitUrl).toHaveBeenCalledWith('/something/1.csv?name=query&orderBy=name'); + }); + }); +}); diff --git a/spec/frontend/ml/experiment_tracking/routes/experiments/show/ml_experiments_show_spec.js b/spec/frontend/ml/experiment_tracking/routes/experiments/show/ml_experiments_show_spec.js index da011feee66..38b3d96ed11 100644 --- a/spec/frontend/ml/experiment_tracking/routes/experiments/show/ml_experiments_show_spec.js +++ b/spec/frontend/ml/experiment_tracking/routes/experiments/show/ml_experiments_show_spec.js @@ -1,10 +1,10 @@ import { GlAlert, GlTableLite, GlLink, GlEmptyState } from '@gitlab/ui'; import { mountExtended } from 'helpers/vue_test_utils_helper'; import MlExperimentsShow from '~/ml/experiment_tracking/routes/experiments/show/ml_experiments_show.vue'; +import ExperimentHeader from '~/ml/experiment_tracking/routes/experiments/show/components/experiment_header.vue'; import RegistrySearch from '~/vue_shared/components/registry/registry_search.vue'; import Pagination from '~/vue_shared/components/incubation/pagination.vue'; import setWindowLocation from 'helpers/set_window_location_helper'; -import DeleteButton from '~/ml/experiment_tracking/components/delete_button.vue'; import * as urlHelpers from '~/lib/utils/url_utility'; import { MOCK_START_CURSOR, MOCK_PAGE_INFO, MOCK_CANDIDATES, MOCK_EXPERIMENT } from './mock_data'; @@ -36,7 +36,7 @@ describe('MlExperimentsShow', () => { const findTableRows = () => findTable().findAll('tbody > tr'); const findNthTableRow = (idx) => findTableRows().at(idx); const findColumnInRow = (row, col) => findNthTableRow(row).findAll('td').at(col); - const findDeleteButton = () => wrapper.findComponent(DeleteButton); + const findExperimentHeader = () => wrapper.findComponent(ExperimentHeader); const hrefInRowAndColumn = (row, col) => findColumnInRow(row, col).findComponent(GlLink).attributes().href; @@ -60,8 +60,12 @@ describe('MlExperimentsShow', () => { expect(findPagination().exists()).toBe(false); }); - it('shows delete button', () => { - expect(findDeleteButton().exists()).toBe(true); + it('shows experiment header', () => { + expect(findExperimentHeader().exists()).toBe(true); + }); + + it('passes the correct title to experiment header', () => { + expect(findExperimentHeader().props('title')).toBe(MOCK_EXPERIMENT.name); }); it('does not show table', () => { diff --git a/spec/frontend/search/sidebar/components/scope_navigation_spec.js b/spec/frontend/search/sidebar/components/scope_navigation_spec.js index 3b2d528e1d7..e8737384f27 100644 --- a/spec/frontend/search/sidebar/components/scope_navigation_spec.js +++ b/spec/frontend/search/sidebar/components/scope_navigation_spec.js @@ -121,4 +121,22 @@ describe('ScopeNavigation', () => { expect(findGlNavItemActive().findComponent(GlIcon).exists()).toBe(true); }); }); + + describe.each` + searchTherm | hasBeenCalled + ${null} | ${0} + ${'test'} | ${1} + `('fetchSidebarCount', ({ searchTherm, hasBeenCalled }) => { + beforeEach(() => { + createComponent({ + urlQuery: { + search: searchTherm, + }, + }); + }); + + it('is only called when search term is set', () => { + expect(actionSpies.fetchSidebarCount).toHaveBeenCalledTimes(hasBeenCalled); + }); + }); }); diff --git a/spec/frontend/super_sidebar/components/context_switcher_spec.js b/spec/frontend/super_sidebar/components/context_switcher_spec.js index 434f7263eb9..11da6ef1243 100644 --- a/spec/frontend/super_sidebar/components/context_switcher_spec.js +++ b/spec/frontend/super_sidebar/components/context_switcher_spec.js @@ -25,7 +25,9 @@ jest.mock('~/super_sidebar/utils', () => ({ })); const focusInputMock = jest.fn(); -const persistentLinks = [{ title: 'Explore', link: '/explore', icon: 'compass' }]; +const persistentLinks = [ + { title: 'Explore', link: '/explore', icon: 'compass', link_classes: 'persistent-link-class' }, +]; const username = 'root'; const projectsPath = 'projectsPath'; const groupsPath = 'groupsPath'; @@ -94,8 +96,13 @@ describe('ContextSwitcher component', () => { it('renders the persistent links', () => { const navItems = findNavItems(); + const firstNavItem = navItems.at(0); + expect(navItems.length).toBe(persistentLinks.length); - expect(navItems.at(0).props('item')).toBe(persistentLinks[0]); + expect(firstNavItem.props('item')).toBe(persistentLinks[0]); + expect(firstNavItem.props('linkClasses')).toEqual({ + [persistentLinks[0].link_classes]: persistentLinks[0].link_classes, + }); }); it('passes the placeholder to the search box', () => { diff --git a/spec/frontend/super_sidebar/components/groups_list_spec.js b/spec/frontend/super_sidebar/components/groups_list_spec.js index 4b61991a5db..4fa3303c12f 100644 --- a/spec/frontend/super_sidebar/components/groups_list_spec.js +++ b/spec/frontend/super_sidebar/components/groups_list_spec.js @@ -19,11 +19,14 @@ describe('GroupsList component', () => { const itRendersViewAllItem = () => { it('renders the "View all..." item', () => { - expect(findViewAllLink().props('item')).toEqual({ + const link = findViewAllLink(); + + expect(link.props('item')).toEqual({ icon: 'group', link: viewAllLink, title: s__('Navigation|View all your groups'), }); + expect(link.props('linkClasses')).toEqual({ 'dashboard-shortcuts-groups': true }); }); }; diff --git a/spec/frontend/super_sidebar/components/merge_request_menu_spec.js b/spec/frontend/super_sidebar/components/merge_request_menu_spec.js index 75998ee6c55..9c8fd0556f1 100644 --- a/spec/frontend/super_sidebar/components/merge_request_menu_spec.js +++ b/spec/frontend/super_sidebar/components/merge_request_menu_spec.js @@ -38,6 +38,7 @@ describe('MergeRequestMenu component', () => { expect(link.attributes('data-track-action')).toBe(extraAttrs['data-track-action']); expect(link.attributes('data-track-label')).toBe(extraAttrs['data-track-label']); expect(link.attributes('data-track-property')).toBe(extraAttrs['data-track-property']); + expect(link.attributes('class')).toContain(extraAttrs.class); }); it('renders item count string in badge', () => { diff --git a/spec/frontend/super_sidebar/components/projects_list_spec.js b/spec/frontend/super_sidebar/components/projects_list_spec.js index 284b88ef27e..93a414e1e8c 100644 --- a/spec/frontend/super_sidebar/components/projects_list_spec.js +++ b/spec/frontend/super_sidebar/components/projects_list_spec.js @@ -19,11 +19,14 @@ describe('ProjectsList component', () => { const itRendersViewAllItem = () => { it('renders the "View all..." item', () => { - expect(findViewAllLink().props('item')).toEqual({ + const link = findViewAllLink(); + + expect(link.props('item')).toEqual({ icon: 'project', link: viewAllLink, title: s__('Navigation|View all your projects'), }); + expect(link.props('linkClasses')).toEqual({ 'dashboard-shortcuts-projects': true }); }); }; diff --git a/spec/frontend/super_sidebar/components/super_sidebar_spec.js b/spec/frontend/super_sidebar/components/super_sidebar_spec.js index 6eae650a2a1..d39fa741574 100644 --- a/spec/frontend/super_sidebar/components/super_sidebar_spec.js +++ b/spec/frontend/super_sidebar/components/super_sidebar_spec.js @@ -67,8 +67,20 @@ describe('SuperSidebar component', () => { }); it("does not call the context switcher's focusInput method initially", () => { + createWrapper(); + expect(focusInputMock).not.toHaveBeenCalled(); }); + + it('renders hidden shortcut links', () => { + createWrapper(); + const [linkAttrs] = sidebarData.shortcut_links; + const link = wrapper.find(`.${linkAttrs.css_class}`); + + expect(link.exists()).toBe(true); + expect(link.attributes('href')).toBe(linkAttrs.href); + expect(link.attributes('class')).toContain('gl-display-none'); + }); }); describe('when opening the context switcher', () => { diff --git a/spec/frontend/super_sidebar/components/user_bar_spec.js b/spec/frontend/super_sidebar/components/user_bar_spec.js index 6da49b45829..2ff731aa102 100644 --- a/spec/frontend/super_sidebar/components/user_bar_spec.js +++ b/spec/frontend/super_sidebar/components/user_bar_spec.js @@ -73,6 +73,7 @@ describe('UserBar component', () => { expect(isuesCounter.attributes('data-track-action')).toBe('click_link'); expect(isuesCounter.attributes('data-track-label')).toBe('issues_link'); expect(isuesCounter.attributes('data-track-property')).toBe('nav_core_menu'); + expect(isuesCounter.attributes('class')).toContain('dashboard-shortcuts-issues'); }); it('renders merge requests counter', () => { @@ -92,6 +93,7 @@ describe('UserBar component', () => { expect(todosCounter.attributes('data-track-action')).toBe('click_link'); expect(todosCounter.attributes('data-track-label')).toBe('todos_link'); expect(todosCounter.attributes('data-track-property')).toBe('nav_core_menu'); + expect(todosCounter.attributes('class')).toContain('shortcuts-todos'); }); it('renders branding logo', () => { diff --git a/spec/frontend/super_sidebar/mock_data.js b/spec/frontend/super_sidebar/mock_data.js index 990a4110d4b..0c9449d98a9 100644 --- a/spec/frontend/super_sidebar/mock_data.js +++ b/spec/frontend/super_sidebar/mock_data.js @@ -53,6 +53,7 @@ export const mergeRequestMenuGroup = [ 'data-track-action': 'click_link', 'data-track-label': 'merge_requests_assigned', 'data-track-property': 'nav_core_menu', + class: 'dashboard-shortcuts-merge_requests', }, }, { @@ -63,6 +64,7 @@ export const mergeRequestMenuGroup = [ 'data-track-action': 'click_link', 'data-track-label': 'merge_requests_to_review', 'data-track-property': 'nav_core_menu', + class: 'dashboard-shortcuts-review_requests', }, }, ], @@ -104,6 +106,13 @@ export const sidebarData = { panel_type: 'your_work', update_pins_url: 'path/to/pins', stop_impersonation_path: '/admin/impersonation', + shortcut_links: [ + { + title: 'Shortcut link', + href: '/shortcut-link', + css_class: 'shortcut-link-class', + }, + ], }; export const userMenuMockStatus = { diff --git a/spec/frontend/whats_new/utils/notification_spec.js b/spec/frontend/whats_new/utils/notification_spec.js index dac02ee07bd..8b5663ee764 100644 --- a/spec/frontend/whats_new/utils/notification_spec.js +++ b/spec/frontend/whats_new/utils/notification_spec.js @@ -1,4 +1,5 @@ -import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures'; +import htmlWhatsNewNotification from 'test_fixtures_static/whats_new_notification.html'; +import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures'; import { useLocalStorageSpy } from 'helpers/local_storage_helper'; import { setNotification, getVersionDigest } from '~/whats_new/utils/notification'; @@ -12,7 +13,7 @@ describe('~/whats_new/utils/notification', () => { const getAppEl = () => wrapper.querySelector('.app'); beforeEach(() => { - loadHTMLFixture('static/whats_new_notification.html'); + setHTMLFixture(htmlWhatsNewNotification); wrapper = document.querySelector('.whats-new-notification-fixture-root'); }); diff --git a/spec/helpers/sidebars_helper_spec.rb b/spec/helpers/sidebars_helper_spec.rb index c7d5c0cfc4f..3ebfc2c2bb5 100644 --- a/spec/helpers/sidebars_helper_spec.rb +++ b/spec/helpers/sidebars_helper_spec.rb @@ -139,7 +139,24 @@ RSpec.describe SidebarsHelper, feature_category: :navigation do }, pinned_items: %w[foo bar], panel_type: panel_type, - update_pins_url: pins_url + update_pins_url: pins_url, + shortcut_links: [ + { + title: _('Milestones'), + href: dashboard_milestones_path, + css_class: 'dashboard-shortcuts-milestones' + }, + { + title: _('Snippets'), + href: dashboard_snippets_path, + css_class: 'dashboard-shortcuts-snippets' + }, + { + title: _('Activity'), + href: activity_dashboard_path, + css_class: 'dashboard-shortcuts-activity' + } + ] }) end @@ -155,7 +172,8 @@ RSpec.describe SidebarsHelper, feature_category: :navigation do extraAttrs: { 'data-track-action': 'click_link', 'data-track-label': 'merge_requests_assigned', - 'data-track-property': 'nav_core_menu' + 'data-track-property': 'nav_core_menu', + class: 'dashboard-shortcuts-merge_requests' } }, { @@ -165,7 +183,8 @@ RSpec.describe SidebarsHelper, feature_category: :navigation do extraAttrs: { 'data-track-action': 'click_link', 'data-track-label': 'merge_requests_to_review', - 'data-track-property': 'nav_core_menu' + 'data-track-property': 'nav_core_menu', + class: 'dashboard-shortcuts-review_requests' } } ] diff --git a/spec/lib/api/entities/clusters/agent_authorization_spec.rb b/spec/lib/api/entities/clusters/agents/authorizations/ci_access_spec.rb index 3a1deb43bf8..5f41ae6af4b 100644 --- a/spec/lib/api/entities/clusters/agent_authorization_spec.rb +++ b/spec/lib/api/entities/clusters/agents/authorizations/ci_access_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Entities::Clusters::AgentAuthorization do +RSpec.describe API::Entities::Clusters::Agents::Authorizations::CiAccess, feature_category: :kubernetes_management do subject { described_class.new(authorization).as_json } shared_examples 'generic authorization' do @@ -16,20 +16,20 @@ RSpec.describe API::Entities::Clusters::AgentAuthorization do end context 'project authorization' do - let(:authorization) { create(:agent_project_authorization) } + let(:authorization) { create(:agent_ci_access_project_authorization) } include_examples 'generic authorization' end context 'group authorization' do - let(:authorization) { create(:agent_group_authorization) } + let(:authorization) { create(:agent_ci_access_group_authorization) } include_examples 'generic authorization' end context 'implicit authorization' do let(:agent) { create(:cluster_agent) } - let(:authorization) { Clusters::Agents::ImplicitAuthorization.new(agent: agent) } + let(:authorization) { Clusters::Agents::Authorizations::CiAccess::ImplicitAuthorization.new(agent: agent) } include_examples 'generic authorization' end diff --git a/spec/lib/gitlab/slug/environment_spec.rb b/spec/lib/gitlab/slug/environment_spec.rb index e8f0fba27b2..8e23ad118d4 100644 --- a/spec/lib/gitlab/slug/environment_spec.rb +++ b/spec/lib/gitlab/slug/environment_spec.rb @@ -1,38 +1,41 @@ # frozen_string_literal: true require 'fast_spec_helper' +require 'rspec-parameterized' -RSpec.describe Gitlab::Slug::Environment do +RSpec.describe Gitlab::Slug::Environment, feature_category: :environment_management do describe '#generate' do - { - "staging-12345678901234567" => "staging-123456789-q517sa", - "9-staging-123456789012345" => "env-9-staging-123-q517sa", - "staging-1234567890123456" => "staging-1234567890123456", - "staging-1234567890123456-" => "staging-123456789-q517sa", - "production" => "production", - "PRODUCTION" => "production-q517sa", - "review/1-foo" => "review-1-foo-q517sa", - "1-foo" => "env-1-foo-q517sa", - "1/foo" => "env-1-foo-q517sa", - "foo-" => "foo", - "foo--bar" => "foo-bar-q517sa", - "foo**bar" => "foo-bar-q517sa", - "*-foo" => "env-foo-q517sa", - "staging-12345678-" => "staging-12345678", - "staging-12345678-01234567" => "staging-12345678-q517sa", - "" => "env-q517sa", - nil => "env-q517sa" - }.each do |name, matcher| - before do - # ('a' * 64).to_i(16).to_s(36).last(6) gives 'q517sa' - allow(Digest::SHA2).to receive(:hexdigest).with(name).and_return('a' * 64) - end + using RSpec::Parameterized::TableSyntax - it "returns a slug matching #{matcher}, given #{name}" do - slug = described_class.new(name).generate + subject { described_class.new(name).generate } - expect(slug).to match(/\A#{matcher}\z/) - end + before do + # ('a' * 64).to_i(16).to_s(36).last(6) gives 'q517sa' + allow(Digest::SHA2).to receive(:hexdigest).with(name.to_s).and_return('a' * 64) + end + + where(:name, :slug) do + "staging-12345678901234567" | "staging-123456789-q517sa" + "9-staging-123456789012345" | "env-9-staging-123-q517sa" + "staging-1234567890123456" | "staging-1234567890123456" + "staging-1234567890123456-" | "staging-123456789-q517sa" + "production" | "production" + "PRODUCTION" | "production-q517sa" + "review/1-foo" | "review-1-foo-q517sa" + "1-foo" | "env-1-foo-q517sa" + "1/foo" | "env-1-foo-q517sa" + "foo-" | "foo" + "foo--bar" | "foo-bar-q517sa" + "foo**bar" | "foo-bar-q517sa" + "*-foo" | "env-foo-q517sa" + "staging-12345678-" | "staging-12345678" + "staging-12345678-01234567" | "staging-12345678-q517sa" + "" | "env-q517sa" + nil | "env-q517sa" + end + + with_them do + it { is_expected.to eq(slug) } end end end diff --git a/spec/migrations/20230411153310_cleanup_bigint_conversion_for_sent_notifications_spec.rb b/spec/migrations/20230411153310_cleanup_bigint_conversion_for_sent_notifications_spec.rb new file mode 100644 index 00000000000..5780aa365da --- /dev/null +++ b/spec/migrations/20230411153310_cleanup_bigint_conversion_for_sent_notifications_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration!('cleanup_bigint_conversion_for_sent_notifications') + +RSpec.describe CleanupBigintConversionForSentNotifications, feature_category: :database do + let(:sent_notifications) { table(:sent_notifications) } + + it 'correctly migrates up and down' do + reversible_migration do |migration| + migration.before -> { + expect(sent_notifications.column_names).to include('id_convert_to_bigint') + } + + migration.after -> { + sent_notifications.reset_column_information + expect(sent_notifications.column_names).not_to include('id_convert_to_bigint') + } + end + end +end diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index ee1410ade91..afb39dec208 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -5468,11 +5468,11 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep, feature_category: describe '#cluster_agent_authorizations' do let(:pipeline) { create(:ci_empty_pipeline, :created) } - let(:authorization) { instance_double(Clusters::Agents::GroupAuthorization) } + let(:authorization) { instance_double(Clusters::Agents::Authorizations::CiAccess::GroupAuthorization) } let(:finder) { double(execute: [authorization]) } it 'retrieves authorization records from the finder and caches the result' do - expect(Clusters::AgentAuthorizationsFinder).to receive(:new).once + expect(Clusters::Agents::Authorizations::CiAccess::Finder).to receive(:new).once .with(pipeline.project) .and_return(finder) diff --git a/spec/models/clusters/agent_spec.rb b/spec/models/clusters/agent_spec.rb index de67bdb32aa..df8ad861aff 100644 --- a/spec/models/clusters/agent_spec.rb +++ b/spec/models/clusters/agent_spec.rb @@ -8,10 +8,10 @@ RSpec.describe Clusters::Agent do it { is_expected.to belong_to(:created_by_user).class_name('User').optional } it { is_expected.to belong_to(:project).class_name('::Project') } it { is_expected.to have_many(:agent_tokens).class_name('Clusters::AgentToken').order(Clusters::AgentToken.arel_table[:last_used_at].desc.nulls_last) } - it { is_expected.to have_many(:group_authorizations).class_name('Clusters::Agents::GroupAuthorization') } - it { is_expected.to have_many(:authorized_groups).through(:group_authorizations) } - it { is_expected.to have_many(:project_authorizations).class_name('Clusters::Agents::ProjectAuthorization') } - it { is_expected.to have_many(:authorized_projects).through(:project_authorizations).class_name('::Project') } + it { is_expected.to have_many(:ci_access_group_authorizations).class_name('Clusters::Agents::Authorizations::CiAccess::GroupAuthorization') } + it { is_expected.to have_many(:ci_access_authorized_groups).through(:ci_access_group_authorizations) } + it { is_expected.to have_many(:ci_access_project_authorizations).class_name('Clusters::Agents::Authorizations::CiAccess::ProjectAuthorization') } + it { is_expected.to have_many(:ci_access_authorized_projects).through(:ci_access_project_authorizations).class_name('::Project') } it { is_expected.to validate_presence_of(:name) } it { is_expected.to validate_length_of(:name).is_at_most(63) } diff --git a/spec/models/clusters/agents/group_authorization_spec.rb b/spec/models/clusters/agents/authorizations/ci_access/group_authorization_spec.rb index baeb8f5464e..2864d6583bd 100644 --- a/spec/models/clusters/agents/group_authorization_spec.rb +++ b/spec/models/clusters/agents/authorizations/ci_access/group_authorization_spec.rb @@ -2,14 +2,14 @@ require 'spec_helper' -RSpec.describe Clusters::Agents::GroupAuthorization do +RSpec.describe Clusters::Agents::Authorizations::CiAccess::GroupAuthorization, feature_category: :kubernetes_management do it { is_expected.to belong_to(:agent).class_name('Clusters::Agent').required } it { is_expected.to belong_to(:group).class_name('::Group').required } it { expect(described_class).to validate_jsonb_schema(['config']) } describe '#config_project' do - let(:record) { create(:agent_group_authorization) } + let(:record) { create(:agent_ci_access_group_authorization) } it { expect(record.config_project).to eq(record.agent.project) } end diff --git a/spec/models/clusters/agents/implicit_authorization_spec.rb b/spec/models/clusters/agents/authorizations/ci_access/implicit_authorization_spec.rb index 1f4c5b1ac9e..9a4f0c28687 100644 --- a/spec/models/clusters/agents/implicit_authorization_spec.rb +++ b/spec/models/clusters/agents/authorizations/ci_access/implicit_authorization_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Clusters::Agents::ImplicitAuthorization do +RSpec.describe Clusters::Agents::Authorizations::CiAccess::ImplicitAuthorization, feature_category: :kubernetes_management do let_it_be(:agent) { create(:cluster_agent) } subject { described_class.new(agent: agent) } diff --git a/spec/models/clusters/agents/project_authorization_spec.rb b/spec/models/clusters/agents/authorizations/ci_access/project_authorization_spec.rb index 9ba259356c7..9e2b25e415e 100644 --- a/spec/models/clusters/agents/project_authorization_spec.rb +++ b/spec/models/clusters/agents/authorizations/ci_access/project_authorization_spec.rb @@ -2,14 +2,14 @@ require 'spec_helper' -RSpec.describe Clusters::Agents::ProjectAuthorization do +RSpec.describe Clusters::Agents::Authorizations::CiAccess::ProjectAuthorization, feature_category: :kubernetes_management do it { is_expected.to belong_to(:agent).class_name('Clusters::Agent').required } it { is_expected.to belong_to(:project).class_name('Project').required } it { expect(described_class).to validate_jsonb_schema(['config']) } describe '#config_project' do - let(:record) { create(:agent_project_authorization) } + let(:record) { create(:agent_ci_access_project_authorization) } it { expect(record.config_project).to eq(record.agent.project) } end diff --git a/spec/models/concerns/clusters/agents/authorization_config_scopes_spec.rb b/spec/models/concerns/clusters/agents/authorization_config_scopes_spec.rb deleted file mode 100644 index a4d1a33b3d5..00000000000 --- a/spec/models/concerns/clusters/agents/authorization_config_scopes_spec.rb +++ /dev/null @@ -1,21 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Clusters::Agents::AuthorizationConfigScopes do - describe '.with_available_ci_access_fields' do - let(:project) { create(:project) } - - let!(:agent_authorization_0) { create(:agent_project_authorization, project: project) } - let!(:agent_authorization_1) { create(:agent_project_authorization, project: project, config: { access_as: {} }) } - let!(:agent_authorization_2) { create(:agent_project_authorization, project: project, config: { access_as: { agent: {} } }) } - let!(:impersonate_authorization) { create(:agent_project_authorization, project: project, config: { access_as: { impersonate: {} } }) } - let!(:ci_user_authorization) { create(:agent_project_authorization, project: project, config: { access_as: { ci_user: {} } }) } - let!(:ci_job_authorization) { create(:agent_project_authorization, project: project, config: { access_as: { ci_job: {} } }) } - let!(:unexpected_authorization) { create(:agent_project_authorization, project: project, config: { access_as: { unexpected: {} } }) } - - subject { Clusters::Agents::ProjectAuthorization.with_available_ci_access_fields(project) } - - it { is_expected.to contain_exactly(agent_authorization_0, agent_authorization_1, agent_authorization_2) } - end -end diff --git a/spec/models/concerns/clusters/agents/authorizations/ci_access/config_scopes_spec.rb b/spec/models/concerns/clusters/agents/authorizations/ci_access/config_scopes_spec.rb new file mode 100644 index 00000000000..c632c639273 --- /dev/null +++ b/spec/models/concerns/clusters/agents/authorizations/ci_access/config_scopes_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Clusters::Agents::Authorizations::CiAccess::ConfigScopes, feature_category: :kubernetes_management do + describe '.with_available_ci_access_fields' do + let(:project) { create(:project) } + + let!(:agent_authorization_0) { create(:agent_ci_access_project_authorization, project: project) } + let!(:agent_authorization_1) { create(:agent_ci_access_project_authorization, project: project, config: { access_as: {} }) } + let!(:agent_authorization_2) { create(:agent_ci_access_project_authorization, project: project, config: { access_as: { agent: {} } }) } + let!(:impersonate_authorization) { create(:agent_ci_access_project_authorization, project: project, config: { access_as: { impersonate: {} } }) } + let!(:ci_user_authorization) { create(:agent_ci_access_project_authorization, project: project, config: { access_as: { ci_user: {} } }) } + let!(:ci_job_authorization) { create(:agent_ci_access_project_authorization, project: project, config: { access_as: { ci_job: {} } }) } + let!(:unexpected_authorization) { create(:agent_ci_access_project_authorization, project: project, config: { access_as: { unexpected: {} } }) } + + subject { Clusters::Agents::Authorizations::CiAccess::ProjectAuthorization.with_available_ci_access_fields(project) } + + it { is_expected.to contain_exactly(agent_authorization_0, agent_authorization_1, agent_authorization_2) } + end +end diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index ed2bf26e129..bcfcfa05ddf 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Note do +RSpec.describe Note, feature_category: :team_planning do include RepoHelpers describe 'associations' do @@ -799,20 +799,22 @@ RSpec.describe Note do describe '#system_note_with_references?' do it 'falsey for user-generated notes' do - note = create(:note, system: false) + note = build_stubbed(:note, system: false) expect(note.system_note_with_references?).to be_falsy end context 'when the note might contain cross references' do SystemNoteMetadata.new.cross_reference_types.each do |type| - let(:note) { create(:note, :system) } - let!(:metadata) { create(:system_note_metadata, note: note, action: type) } + context "with #{type}" do + let(:note) { build_stubbed(:note, :system) } + let!(:metadata) { build_stubbed(:system_note_metadata, note: note, action: type) } - it 'delegates to the cross-reference regex' do - expect(note).to receive(:matches_cross_reference_regex?).and_return(false) + it 'delegates to the cross-reference regex' do + expect(note).to receive(:matches_cross_reference_regex?).and_return(false) - note.system_note_with_references? + note.system_note_with_references? + end end end end diff --git a/spec/requests/api/ci/jobs_spec.rb b/spec/requests/api/ci/jobs_spec.rb index 8b3ec59b785..25871beeb4f 100644 --- a/spec/requests/api/ci/jobs_spec.rb +++ b/spec/requests/api/ci/jobs_spec.rb @@ -198,22 +198,22 @@ RSpec.describe API::Ci::Jobs, feature_category: :continuous_integration do let_it_be(:agent_authorizations_without_env) do [ - create(:agent_group_authorization, agent: create(:cluster_agent, project: other_project), group: group), - create(:agent_project_authorization, agent: create(:cluster_agent, project: project), project: project), - Clusters::Agents::ImplicitAuthorization.new(agent: create(:cluster_agent, project: project)) + create(:agent_ci_access_group_authorization, agent: create(:cluster_agent, project: other_project), group: group), + create(:agent_ci_access_project_authorization, agent: create(:cluster_agent, project: project), project: project), + Clusters::Agents::Authorizations::CiAccess::ImplicitAuthorization.new(agent: create(:cluster_agent, project: project)) ] end let_it_be(:agent_authorizations_with_review_and_production_env) do [ create( - :agent_group_authorization, + :agent_ci_access_group_authorization, agent: create(:cluster_agent, project: other_project), group: group, environments: ['production', 'review/*'] ), create( - :agent_project_authorization, + :agent_ci_access_project_authorization, agent: create(:cluster_agent, project: project), project: project, environments: ['production', 'review/*'] @@ -224,13 +224,13 @@ RSpec.describe API::Ci::Jobs, feature_category: :continuous_integration do let_it_be(:agent_authorizations_with_staging_env) do [ create( - :agent_group_authorization, + :agent_ci_access_group_authorization, agent: create(:cluster_agent, project: other_project), group: group, environments: ['staging'] ), create( - :agent_project_authorization, + :agent_ci_access_project_authorization, agent: create(:cluster_agent, project: project), project: project, environments: ['staging'] diff --git a/spec/requests/api/internal/kubernetes_spec.rb b/spec/requests/api/internal/kubernetes_spec.rb index 547b9071f94..56d6f538026 100644 --- a/spec/requests/api/internal/kubernetes_spec.rb +++ b/spec/requests/api/internal/kubernetes_spec.rb @@ -158,8 +158,8 @@ RSpec.describe API::Internal::Kubernetes, feature_category: :kubernetes_manageme send_request(params: { agent_id: agent.id, agent_config: config }) expect(response).to have_gitlab_http_status(:no_content) - expect(agent.authorized_groups).to contain_exactly(group) - expect(agent.authorized_projects).to contain_exactly(project) + expect(agent.ci_access_authorized_groups).to contain_exactly(group) + expect(agent.ci_access_authorized_projects).to contain_exactly(project) end end diff --git a/spec/scripts/review_apps/automated_cleanup_spec.rb b/spec/scripts/review_apps/automated_cleanup_spec.rb index 40cf0a8bf75..a8b8353d2ef 100644 --- a/spec/scripts/review_apps/automated_cleanup_spec.rb +++ b/spec/scripts/review_apps/automated_cleanup_spec.rb @@ -30,7 +30,6 @@ RSpec.describe ReviewApps::AutomatedCleanup, feature_category: :tooling do allow(Tooling::Helm3Client).to receive(:new).and_return(helm_client) allow(Tooling::KubernetesClient).to receive(:new).and_return(kubernetes_client) - allow(kubernetes_client).to receive(:cleanup_pvcs_by_created_at) allow(kubernetes_client).to receive(:cleanup_namespaces_by_created_at) end @@ -124,28 +123,6 @@ RSpec.describe ReviewApps::AutomatedCleanup, feature_category: :tooling do end end - describe '#perform_stale_pvc_cleanup!' do - subject { instance.perform_stale_pvc_cleanup!(days: days) } - - let(:days) { 2 } - - it_behaves_like 'the days argument is an integer in the correct range' - - it 'performs Kubernetes cleanup by created at' do - expect(kubernetes_client).to receive(:cleanup_pvcs_by_created_at).with(created_before: two_days_ago) - - subject - end - - context 'when the dry-run flag is true' do - let(:dry_run) { true } - - it 'does not delete anything' do - expect(kubernetes_client).not_to receive(:cleanup_pvcs_by_created_at) - end - end - end - describe '#perform_stale_namespace_cleanup!' do subject { instance.perform_stale_namespace_cleanup!(days: days) } diff --git a/spec/services/ci/generate_kubeconfig_service_spec.rb b/spec/services/ci/generate_kubeconfig_service_spec.rb index da18dfe04c3..913aaf11d7d 100644 --- a/spec/services/ci/generate_kubeconfig_service_spec.rb +++ b/spec/services/ci/generate_kubeconfig_service_spec.rb @@ -13,12 +13,12 @@ RSpec.describe Ci::GenerateKubeconfigService, feature_category: :kubernetes_mana let_it_be(:project_agent_authorization) do agent = create(:cluster_agent, project: agent_project) - create(:agent_project_authorization, agent: agent, project: project) + create(:agent_ci_access_project_authorization, agent: agent, project: project) end let_it_be(:group_agent_authorization) do agent = create(:cluster_agent, project: agent_project) - create(:agent_group_authorization, agent: agent, group: group) + create(:agent_ci_access_group_authorization, agent: agent, group: group) end let(:template) do @@ -33,7 +33,7 @@ RSpec.describe Ci::GenerateKubeconfigService, feature_category: :kubernetes_mana let(:agent_authorizations) { [project_agent_authorization, group_agent_authorization] } let(:filter_service) do instance_double( - ::Clusters::Agents::FilterAuthorizationsService, + ::Clusters::Agents::Authorizations::CiAccess::FilterService, execute: agent_authorizations ) end @@ -42,7 +42,7 @@ RSpec.describe Ci::GenerateKubeconfigService, feature_category: :kubernetes_mana before do allow(Gitlab::Kubernetes::Kubeconfig::Template).to receive(:new).and_return(template) - allow(::Clusters::Agents::FilterAuthorizationsService).to receive(:new).and_return(filter_service) + allow(::Clusters::Agents::Authorizations::CiAccess::FilterService).to receive(:new).and_return(filter_service) end it 'returns a Kubeconfig Template' do @@ -59,7 +59,7 @@ RSpec.describe Ci::GenerateKubeconfigService, feature_category: :kubernetes_mana end it "filters the pipeline's agents by `nil` environment" do - expect(::Clusters::Agents::FilterAuthorizationsService).to receive(:new).with( + expect(::Clusters::Agents::Authorizations::CiAccess::FilterService).to receive(:new).with( pipeline.cluster_agent_authorizations, environment: nil ) @@ -89,7 +89,7 @@ RSpec.describe Ci::GenerateKubeconfigService, feature_category: :kubernetes_mana subject(:execute) { described_class.new(pipeline, token: build.token, environment: 'production').execute } it "filters the pipeline's agents by the specified environment" do - expect(::Clusters::Agents::FilterAuthorizationsService).to receive(:new).with( + expect(::Clusters::Agents::Authorizations::CiAccess::FilterService).to receive(:new).with( pipeline.cluster_agent_authorizations, environment: 'production' ) diff --git a/spec/services/clusters/agents/filter_authorizations_service_spec.rb b/spec/services/clusters/agents/authorizations/ci_access/filter_service_spec.rb index 62cff405d0c..45443cfd887 100644 --- a/spec/services/clusters/agents/filter_authorizations_service_spec.rb +++ b/spec/services/clusters/agents/authorizations/ci_access/filter_service_spec.rb @@ -2,16 +2,16 @@ require 'spec_helper' -RSpec.describe Clusters::Agents::FilterAuthorizationsService, feature_category: :continuous_integration do +RSpec.describe Clusters::Agents::Authorizations::CiAccess::FilterService, feature_category: :continuous_integration do describe '#execute' do let_it_be(:group) { create(:group) } let_it_be(:project) { create(:project, group: group) } let(:agent_authorizations_without_env) do [ - build(:agent_project_authorization, project: project, agent: build(:cluster_agent, project: project)), - build(:agent_group_authorization, group: group, agent: build(:cluster_agent, project: project)), - ::Clusters::Agents::ImplicitAuthorization.new(agent: build(:cluster_agent, project: project)) + build(:agent_ci_access_project_authorization, project: project, agent: build(:cluster_agent, project: project)), + build(:agent_ci_access_group_authorization, group: group, agent: build(:cluster_agent, project: project)), + ::Clusters::Agents::Authorizations::CiAccess::ImplicitAuthorization.new(agent: build(:cluster_agent, project: project)) ] end @@ -31,13 +31,13 @@ RSpec.describe Clusters::Agents::FilterAuthorizationsService, feature_category: let(:agent_authorizations_with_env) do [ build( - :agent_project_authorization, + :agent_ci_access_project_authorization, project: project, agent: build(:cluster_agent, project: project), environments: ['staging', 'review/*', 'production'] ), build( - :agent_group_authorization, + :agent_ci_access_group_authorization, group: group, agent: build(:cluster_agent, project: project), environments: ['staging', 'review/*', 'production'] @@ -48,13 +48,13 @@ RSpec.describe Clusters::Agents::FilterAuthorizationsService, feature_category: let(:agent_authorizations_with_different_env) do [ build( - :agent_project_authorization, + :agent_ci_access_project_authorization, project: project, agent: build(:cluster_agent, project: project), environments: ['staging'] ), build( - :agent_group_authorization, + :agent_ci_access_group_authorization, group: group, agent: build(:cluster_agent, project: project), environments: ['staging'] diff --git a/spec/services/clusters/agents/refresh_authorization_service_spec.rb b/spec/services/clusters/agents/authorizations/ci_access/refresh_service_spec.rb index 51c054ddc98..dc803c94ccb 100644 --- a/spec/services/clusters/agents/refresh_authorization_service_spec.rb +++ b/spec/services/clusters/agents/authorizations/ci_access/refresh_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Clusters::Agents::RefreshAuthorizationService, feature_category: :kubernetes_management do +RSpec.describe Clusters::Agents::Authorizations::CiAccess::RefreshService, feature_category: :kubernetes_management do describe '#execute' do let_it_be(:root_ancestor) { create(:group) } @@ -39,11 +39,11 @@ RSpec.describe Clusters::Agents::RefreshAuthorizationService, feature_category: before do default_config = { default_namespace: 'default' } - agent.group_authorizations.create!(group: removed_group, config: default_config) - agent.group_authorizations.create!(group: modified_group, config: default_config) + agent.ci_access_group_authorizations.create!(group: removed_group, config: default_config) + agent.ci_access_group_authorizations.create!(group: modified_group, config: default_config) - agent.project_authorizations.create!(project: removed_project, config: default_config) - agent.project_authorizations.create!(project: modified_project, config: default_config) + agent.ci_access_project_authorizations.create!(project: removed_project, config: default_config) + agent.ci_access_project_authorizations.create!(project: modified_project, config: default_config) end shared_examples 'removing authorization' do @@ -78,12 +78,12 @@ RSpec.describe Clusters::Agents::RefreshAuthorizationService, feature_category: describe 'group authorization' do it 'refreshes authorizations for the agent' do expect(subject).to be_truthy - expect(agent.authorized_groups).to contain_exactly(added_group, modified_group) + expect(agent.ci_access_authorized_groups).to contain_exactly(added_group, modified_group) - added_authorization = agent.group_authorizations.find_by(group: added_group) + added_authorization = agent.ci_access_group_authorizations.find_by(group: added_group) expect(added_authorization.config).to eq({ 'default_namespace' => 'default' }) - modified_authorization = agent.group_authorizations.find_by(group: modified_group) + modified_authorization = agent.ci_access_group_authorizations.find_by(group: modified_group) expect(modified_authorization.config).to eq({ 'default_namespace' => 'new-namespace' }) end @@ -94,24 +94,24 @@ RSpec.describe Clusters::Agents::RefreshAuthorizationService, feature_category: it 'authorizes groups up to the limit' do expect(subject).to be_truthy - expect(agent.authorized_groups).to contain_exactly(added_group) + expect(agent.ci_access_authorized_groups).to contain_exactly(added_group) end end include_examples 'removing authorization' do - let(:authorizations) { agent.authorized_groups } + let(:authorizations) { agent.ci_access_authorized_groups } end end describe 'project authorization' do it 'refreshes authorizations for the agent' do expect(subject).to be_truthy - expect(agent.authorized_projects).to contain_exactly(added_project, modified_project) + expect(agent.ci_access_authorized_projects).to contain_exactly(added_project, modified_project) - added_authorization = agent.project_authorizations.find_by(project: added_project) + added_authorization = agent.ci_access_project_authorizations.find_by(project: added_project) expect(added_authorization.config).to eq({ 'default_namespace' => 'default' }) - modified_authorization = agent.project_authorizations.find_by(project: modified_project) + modified_authorization = agent.ci_access_project_authorizations.find_by(project: modified_project) expect(modified_authorization.config).to eq({ 'default_namespace' => 'new-namespace' }) end @@ -121,7 +121,7 @@ RSpec.describe Clusters::Agents::RefreshAuthorizationService, feature_category: it 'creates an authorization record for the project' do expect(subject).to be_truthy - expect(agent.authorized_projects).to contain_exactly(added_project) + expect(agent.ci_access_authorized_projects).to contain_exactly(added_project) end end @@ -131,7 +131,7 @@ RSpec.describe Clusters::Agents::RefreshAuthorizationService, feature_category: it 'creates an authorization record for the project' do expect(subject).to be_truthy - expect(agent.authorized_projects).to contain_exactly(added_project) + expect(agent.ci_access_authorized_projects).to contain_exactly(added_project) end end @@ -142,12 +142,12 @@ RSpec.describe Clusters::Agents::RefreshAuthorizationService, feature_category: it 'authorizes projects up to the limit' do expect(subject).to be_truthy - expect(agent.authorized_projects).to contain_exactly(added_project) + expect(agent.ci_access_authorized_projects).to contain_exactly(added_project) end end include_examples 'removing authorization' do - let(:authorizations) { agent.authorized_projects } + let(:authorizations) { agent.ci_access_authorized_projects } end end end diff --git a/spec/services/security/ci_configuration/dependency_scanning_create_service_spec.rb b/spec/services/security/ci_configuration/dependency_scanning_create_service_spec.rb index 719a2cf24e9..7ac2249642a 100644 --- a/spec/services/security/ci_configuration/dependency_scanning_create_service_spec.rb +++ b/spec/services/security/ci_configuration/dependency_scanning_create_service_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe Security::CiConfiguration::DependencyScanningCreateService, :snowplow, - feature_category: :dependency_scanning do + feature_category: :software_composition_analysis do subject(:result) { described_class.new(project, user).execute } let(:branch_name) { 'set-dependency-scanning-config-1' } diff --git a/spec/support/finder_collection_allowlist.yml b/spec/support/finder_collection_allowlist.yml index 1b1c98af80d..25084ece58d 100644 --- a/spec/support/finder_collection_allowlist.yml +++ b/spec/support/finder_collection_allowlist.yml @@ -24,7 +24,7 @@ - Ci::CommitStatusesFinder - Ci::DailyBuildGroupReportResultsFinder - ClusterAncestorsFinder -- Clusters::AgentAuthorizationsFinder +- Clusters::Agents::Authorizations::CiAccess::Finder - Clusters::KubernetesNamespaceFinder - ComplianceManagement::MergeRequests::ComplianceViolationsFinder - ContainerRepositoriesFinder diff --git a/spec/support/rspec_order_todo.yml b/spec/support/rspec_order_todo.yml index a767ae69653..1c26a34010c 100644 --- a/spec/support/rspec_order_todo.yml +++ b/spec/support/rspec_order_todo.yml @@ -7205,7 +7205,6 @@ - './spec/lib/gitlab/slash_commands/presenters/issue_show_spec.rb' - './spec/lib/gitlab/slash_commands/presenters/run_spec.rb' - './spec/lib/gitlab/slash_commands/run_spec.rb' -- './spec/lib/gitlab/slug/environment_spec.rb' - './spec/lib/gitlab/snippet_search_results_spec.rb' - './spec/lib/gitlab/sourcegraph_spec.rb' - './spec/lib/gitlab/spamcheck/client_spec.rb' diff --git a/spec/tooling/lib/tooling/kubernetes_client_spec.rb b/spec/tooling/lib/tooling/kubernetes_client_spec.rb index 20eb78c2f4f..8d127f1345b 100644 --- a/spec/tooling/lib/tooling/kubernetes_client_spec.rb +++ b/spec/tooling/lib/tooling/kubernetes_client_spec.rb @@ -14,88 +14,6 @@ RSpec.describe Tooling::KubernetesClient do allow(instance).to receive(:run_command) end - describe '#cleanup_pvcs_by_created_at' do - let(:pvc_1_created_at) { three_days_ago } - let(:pvc_2_created_at) { three_days_ago } - let(:pvc_1_namespace) { 'review-first-review-app' } - let(:pvc_2_namespace) { 'review-second-review-app' } - let(:kubectl_pvcs_json) do - <<~JSON - { - "apiVersion": "v1", - "items": [ - { - "apiVersion": "v1", - "kind": "PersistentVolumeClaim", - "metadata": { - "creationTimestamp": "#{pvc_1_created_at.utc.iso8601}", - "name": "pvc1", - "namespace": "#{pvc_1_namespace}" - } - }, - { - "apiVersion": "v1", - "kind": "PersistentVolumeClaim", - "metadata": { - "creationTimestamp": "#{pvc_2_created_at.utc.iso8601}", - "name": "pvc2", - "namespace": "#{pvc_2_namespace}" - } - } - ] - } - JSON - end - - subject { instance.cleanup_pvcs_by_created_at(created_before: two_days_ago) } - - before do - allow(instance).to receive(:run_command).with( - "kubectl get pvc --all-namespaces --sort-by='{.metadata.creationTimestamp}' -o json" - ).and_return(kubectl_pvcs_json) - end - - context 'when no pvcs are stale' do - let(:pvc_1_created_at) { one_day_ago } - let(:pvc_2_created_at) { one_day_ago } - - it 'does not delete any PVC' do - expect(instance).not_to receive(:run_command).with(/kubectl delete pvc/) - - subject - end - end - - context 'when some pvcs are stale' do - let(:pvc_1_created_at) { three_days_ago } - let(:pvc_2_created_at) { three_days_ago } - - context 'when some pvcs are not in a review app namespaces' do - let(:pvc_1_namespace) { 'review-my-review-app' } - let(:pvc_2_namespace) { 'review-apps' } # This is not a review apps namespace, so we should not delete PVCs inside it - - it 'deletes the stale pvcs inside of review-apps namespaces only' do - expect(instance).to receive(:run_command).with("kubectl delete pvc --namespace=#{pvc_1_namespace} --now --ignore-not-found pvc1") - expect(instance).not_to receive(:run_command).with(/kubectl delete pvc --namespace=#{pvc_2_namespace}/) - - subject - end - end - - context 'when all pvcs are in review-apps namespaces' do - let(:pvc_1_namespace) { 'review-my-review-app' } - let(:pvc_2_namespace) { 'review-another-review-app' } - - it 'deletes all of the stale pvcs' do - expect(instance).to receive(:run_command).with("kubectl delete pvc --namespace=#{pvc_1_namespace} --now --ignore-not-found pvc1") - expect(instance).to receive(:run_command).with("kubectl delete pvc --namespace=#{pvc_2_namespace} --now --ignore-not-found pvc2") - - subject - end - end - end - end - describe '#cleanup_namespaces_by_created_at' do let(:namespace_1_created_at) { three_days_ago } let(:namespace_2_created_at) { three_days_ago } @@ -174,32 +92,6 @@ RSpec.describe Tooling::KubernetesClient do end end - describe '#delete_pvc' do - let(:pvc_name) { 'my-pvc' } - - subject { instance.delete_pvc(pvc_name, pvc_namespace) } - - context 'when the namespace is not a review app namespace' do - let(:pvc_namespace) { 'not-a-review-app-namespace' } - - it 'does not delete the pvc' do - expect(instance).not_to receive(:run_command).with(/kubectl delete pvc/) - - subject - end - end - - context 'when the namespace is a review app namespace' do - let(:pvc_namespace) { 'review-apple-test' } - - it 'deletes the pvc' do - expect(instance).to receive(:run_command).with("kubectl delete pvc --namespace=#{pvc_namespace} --now --ignore-not-found #{pvc_name}") - - subject - end - end - end - describe '#delete_namespaces' do subject { instance.delete_namespaces(namespaces) } @@ -224,70 +116,11 @@ RSpec.describe Tooling::KubernetesClient do end end - describe '#pvcs_created_before' do - subject { instance.pvcs_created_before(created_before: two_days_ago) } - - let(:pvc_1_created_at) { three_days_ago } - let(:pvc_2_created_at) { three_days_ago } - let(:pvc_1_namespace) { 'review-first-review-app' } - let(:pvc_2_namespace) { 'review-second-review-app' } - let(:kubectl_pvcs_json) do - <<~JSON - { - "apiVersion": "v1", - "items": [ - { - "apiVersion": "v1", - "kind": "PersistentVolumeClaim", - "metadata": { - "creationTimestamp": "#{pvc_1_created_at.utc.iso8601}", - "name": "pvc1", - "namespace": "#{pvc_1_namespace}" - } - }, - { - "apiVersion": "v1", - "kind": "PersistentVolumeClaim", - "metadata": { - "creationTimestamp": "#{pvc_2_created_at.utc.iso8601}", - "name": "pvc2", - "namespace": "#{pvc_2_namespace}" - } - } - ] - } - JSON - end - - it 'calls #resource_created_before with the correct parameters' do - expect(instance).to receive(:resource_created_before).with(resource_type: 'pvc', created_before: two_days_ago) - - subject - end - - it 'returns a hash with two keys' do - allow(instance).to receive(:run_command).with( - "kubectl get pvc --all-namespaces --sort-by='{.metadata.creationTimestamp}' -o json" - ).and_return(kubectl_pvcs_json) - - expect(subject).to match_array([ - { - resource_name: 'pvc1', - namespace: 'review-first-review-app' - }, - { - resource_name: 'pvc2', - namespace: 'review-second-review-app' - } - ]) - end - end - describe '#namespaces_created_before' do subject { instance.namespaces_created_before(created_before: two_days_ago) } let(:namespace_1_created_at) { three_days_ago } - let(:namespace_2_created_at) { three_days_ago } + let(:namespace_2_created_at) { one_day_ago } let(:namespace_1_name) { 'review-first-review-app' } let(:namespace_2_name) { 'review-second-review-app' } let(:kubectl_namespaces_json) do @@ -316,18 +149,12 @@ RSpec.describe Tooling::KubernetesClient do JSON end - it 'calls #resource_created_before with the correct parameters' do - expect(instance).to receive(:resource_created_before).with(resource_type: 'namespace', created_before: two_days_ago) - - subject - end - it 'returns an array of namespaces' do allow(instance).to receive(:run_command).with( "kubectl get namespace --all-namespaces --sort-by='{.metadata.creationTimestamp}' -o json" ).and_return(kubectl_namespaces_json) - expect(subject).to match_array(%w[review-first-review-app review-second-review-app]) + expect(subject).to match_array(%w[review-first-review-app]) end end diff --git a/spec/views/search/_results.html.haml_spec.rb b/spec/views/search/_results.html.haml_spec.rb index ed71a03c7e0..832cc5b7cf3 100644 --- a/spec/views/search/_results.html.haml_spec.rb +++ b/spec/views/search/_results.html.haml_spec.rb @@ -97,12 +97,6 @@ RSpec.describe 'search/_results', feature_category: :global_search do expect(rendered).not_to have_selector('[data-track-property=search_result]') end end - - it 'does render the sidebar' do - render - - expect(rendered).to have_selector('#js-search-sidebar') - end end end diff --git a/spec/views/search/show.html.haml_spec.rb b/spec/views/search/show.html.haml_spec.rb index db06adfeb6b..0158a9049b9 100644 --- a/spec/views/search/show.html.haml_spec.rb +++ b/spec/views/search/show.html.haml_spec.rb @@ -41,6 +41,12 @@ RSpec.describe 'search/show', feature_category: :global_search do expect(rendered).not_to render_template('search/_results') end + + it 'does render the sidebar' do + render + + expect(rendered).to have_selector('#js-search-sidebar') + end end context 'unfurling support' do diff --git a/tooling/lib/tooling/kubernetes_client.rb b/tooling/lib/tooling/kubernetes_client.rb index b373f5d6980..5579f130a84 100644 --- a/tooling/lib/tooling/kubernetes_client.rb +++ b/tooling/lib/tooling/kubernetes_client.rb @@ -9,20 +9,6 @@ module Tooling K8S_ALLOWED_NAMESPACES_REGEX = /^review-(?!apps).+/.freeze CommandFailedError = Class.new(StandardError) - def cleanup_pvcs_by_created_at(created_before:) - stale_pvcs = pvcs_created_before(created_before: created_before) - - # `kubectl` doesn't allow us to filter namespaces with a regexp. We therefore do the filtering in Ruby. - review_apps_stale_pvcs = stale_pvcs.select do |stale_pvc_hash| - K8S_ALLOWED_NAMESPACES_REGEX.match?(stale_pvc_hash[:namespace]) - end - return if review_apps_stale_pvcs.empty? - - review_apps_stale_pvcs.each do |pvc_hash| - delete_pvc(pvc_hash[:resource_name], pvc_hash[:namespace]) - end - end - def cleanup_namespaces_by_created_at(created_before:) stale_namespaces = namespaces_created_before(created_before: created_before) @@ -33,12 +19,6 @@ module Tooling delete_namespaces(review_apps_stale_namespaces) end - def delete_pvc(pvc, namespace) - return unless K8S_ALLOWED_NAMESPACES_REGEX.match?(namespace) - - run_command("kubectl delete pvc --namespace=#{namespace} --now --ignore-not-found #{pvc}") - end - def delete_namespaces(namespaces) return if namespaces.any? { |ns| !K8S_ALLOWED_NAMESPACES_REGEX.match?(ns) } @@ -58,29 +38,14 @@ module Tooling run_command(command) end - def pvcs_created_before(created_before:) - resource_created_before(resource_type: 'pvc', created_before: created_before) do |item| - { - resource_name: item.dig('metadata', 'name'), - namespace: item.dig('metadata', 'namespace') - } - end - end - def namespaces_created_before(created_before:) - resource_created_before(resource_type: 'namespace', created_before: created_before) do |item| - item.dig('metadata', 'name') - end - end - - def resource_created_before(resource_type:, created_before:) - response = run_command("kubectl get #{resource_type} --all-namespaces --sort-by='{.metadata.creationTimestamp}' -o json") + response = run_command("kubectl get namespace --all-namespaces --sort-by='{.metadata.creationTimestamp}' -o json") items = JSON.parse(response)['items'] # rubocop:disable Gitlab/Json items.filter_map do |item| item_created_at = Time.parse(item.dig('metadata', 'creationTimestamp')) - yield item if item_created_at < created_before + item.dig('metadata', 'name') if item_created_at < created_before end rescue ::JSON::ParserError => ex puts "Ignoring this JSON parsing error: #{ex}\n\nResponse was:\n#{response}" |