summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-03-08 12:11:30 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2023-03-08 12:11:30 +0000
commit012f9a4b9ec4a78d9593d882b38f95e376c2cfe2 (patch)
tree07f1df76cb12d94744e8069eec540d36092bbaba
parent3c050fb24b757425987a7df4cb3497e1d792be8e (diff)
downloadgitlab-ce-012f9a4b9ec4a78d9593d882b38f95e376c2cfe2.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab/CODEOWNERS71
-rw-r--r--app/assets/javascripts/add_context_commits_modal/components/add_context_commits_modal_wrapper.vue2
-rw-r--r--app/assets/javascripts/add_context_commits_modal/store/actions.js2
-rw-r--r--app/assets/javascripts/blob_edit/blob_bundle.js2
-rw-r--r--app/assets/javascripts/blob_edit/edit_blob.js2
-rw-r--r--app/assets/javascripts/ci/runner/admin_register_runner/admin_register_runner_app.vue28
-rw-r--r--app/assets/javascripts/ci/runner/components/registration/platforms_drawer.vue131
-rw-r--r--app/assets/javascripts/ci/runner/components/registration/registration_instructions.vue10
-rw-r--r--app/assets/javascripts/ci/runner/components/registration/registration_token_reset_dropdown_item.vue2
-rw-r--r--app/assets/javascripts/ci/runner/components/registration/scripts/linux/install.sh12
-rw-r--r--app/assets/javascripts/ci/runner/components/registration/scripts/osx/install.sh11
-rw-r--r--app/assets/javascripts/ci/runner/components/registration/scripts/windows/install.ps113
-rw-r--r--app/assets/javascripts/ci/runner/components/registration/utils.js39
-rw-r--r--app/assets/javascripts/ci/runner/components/runner_bulk_delete.vue2
-rw-r--r--app/assets/javascripts/ci/runner/components/runner_delete_button.vue2
-rw-r--r--app/assets/javascripts/ci/runner/components/runner_jobs.vue2
-rw-r--r--app/assets/javascripts/ci/runner/components/runner_pause_button.vue2
-rw-r--r--app/assets/javascripts/ci/runner/components/runner_projects.vue2
-rw-r--r--app/assets/javascripts/ci/runner/components/runner_update_form.vue2
-rw-r--r--app/assets/javascripts/ci/runner/components/search_tokens/tag_token.vue2
-rw-r--r--app/assets/javascripts/ci/runner/constants.js49
-rw-r--r--app/assets/javascripts/ci/runner/local_storage_alert/show_alert_from_local_storage.js2
-rw-r--r--app/assets/javascripts/content_editor/components/content_editor.vue2
-rw-r--r--app/assets/javascripts/content_editor/extensions/paste_markdown.js2
-rw-r--r--app/assets/javascripts/content_editor/extensions/table.js2
-rw-r--r--app/assets/javascripts/content_editor/services/upload_helpers.js2
-rw-r--r--app/assets/javascripts/graphql_shared/possible_types.json3
-rw-r--r--app/assets/javascripts/invite_members/utils/trigger_successful_invite_alert.js2
-rw-r--r--app/assets/javascripts/merge_request.js2
-rw-r--r--app/assets/javascripts/merge_request_tabs.js2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue19
-rw-r--r--app/graphql/mutations/metrics/dashboard/annotations/create.rb2
-rw-r--r--app/graphql/mutations/metrics/dashboard/annotations/delete.rb2
-rw-r--r--app/graphql/resolvers/work_items_resolver.rb3
-rw-r--r--app/graphql/types/work_items/widget_interface.rb5
-rw-r--r--app/graphql/types/work_items/widgets/notifications_type.rb26
-rw-r--r--app/models/concerns/subscribable.rb13
-rw-r--r--app/models/work_items/widget_definition.rb3
-rw-r--r--app/models/work_items/widgets/notifications.rb9
-rw-r--r--app/policies/group_policy.rb4
-rw-r--r--app/policies/project_policy.rb13
-rw-r--r--app/services/metrics/dashboard/annotations/create_service.rb4
-rw-r--r--app/services/metrics/dashboard/annotations/delete_service.rb2
-rw-r--r--app/views/clusters/clusters/_integrations.html.haml2
-rw-r--r--config/webpack.config.js1
-rw-r--r--db/docs/work_item_types.yml1
-rw-r--r--db/docs/work_item_widget_definitions.yml1
-rw-r--r--db/migrate/20230228142350_add_notifications_work_item_widget.rb57
-rw-r--r--db/schema_migrations/202302281423501
-rw-r--r--doc/administration/terraform_state.md20
-rw-r--r--doc/api/graphql/reference/index.md13
-rw-r--r--jest.config.base.js2
-rw-r--r--lib/api/metrics/dashboard/annotations.rb2
-rw-r--r--lib/gitlab/database/background_migration/batched_job.rb2
-rw-r--r--lib/gitlab/database/background_migration/batched_migration.rb2
-rw-r--r--lib/gitlab/database_importers/work_items/base_type_importer.rb24
-rw-r--r--lib/tasks/gitlab/tw/codeowners.rake3
-rw-r--r--locale/gitlab.pot51
-rw-r--r--qa/Gemfile.lock18
-rwxr-xr-xscripts/trigger-build.rb1
-rw-r--r--spec/frontend/blob/components/blob_header_viewer_switcher_spec.js53
-rw-r--r--spec/frontend/ci/runner/admin_register_runner/admin_register_runner_app_spec.js64
-rw-r--r--spec/frontend/ci/runner/components/registration/__snapshots__/utils_spec.js.snap178
-rw-r--r--spec/frontend/ci/runner/components/registration/platforms_drawer_spec.js92
-rw-r--r--spec/frontend/ci/runner/components/registration/registration_instructions_spec.js27
-rw-r--r--spec/frontend/ci/runner/components/registration/utils_spec.js73
-rw-r--r--spec/frontend/monitoring/components/variables/text_field_spec.js6
-rw-r--r--spec/frontend/vue_shared/issuable/show/components/issuable_title_spec.js26
-rw-r--r--spec/graphql/types/work_items/widget_interface_spec.rb11
-rw-r--r--spec/graphql/types/work_items/widgets/notifications_type_spec.rb12
-rw-r--r--spec/lib/sidebars/projects/menus/infrastructure_menu_spec.rb16
-rw-r--r--spec/migrations/20230228142350_add_notifications_work_item_widget_spec.rb27
-rw-r--r--spec/models/concerns/subscribable_spec.rb26
-rw-r--r--spec/models/work_items/widget_definition_spec.rb3
-rw-r--r--spec/models/work_items/widgets/notifications_spec.rb19
-rw-r--r--spec/policies/metrics/dashboard/annotation_policy_spec.rb16
-rw-r--r--spec/policies/project_policy_spec.rb31
-rw-r--r--spec/requests/api/graphql/mutations/metrics/dashboard/annotations/create_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/metrics/dashboard/annotations/delete_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/work_items_spec.rb28
-rw-r--r--spec/requests/api/graphql/work_item_spec.rb26
-rw-r--r--spec/requests/api/terraform/state_spec.rb8
-rw-r--r--spec/requests/api/terraform/state_version_spec.rb8
-rw-r--r--spec/support/shared_contexts/policies/group_policy_shared_context.rb4
-rw-r--r--spec/support/shared_contexts/policies/project_policy_shared_context.rb11
-rw-r--r--spec/support/shared_contexts/rack_attack_shared_context.rb3
-rw-r--r--spec/support/shared_examples/lib/api/terraform_state_enabled_shared_examples.rb29
87 files changed, 1254 insertions, 260 deletions
diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS
index b852df49c24..5b8c4f201ca 100644
--- a/.gitlab/CODEOWNERS
+++ b/.gitlab/CODEOWNERS
@@ -509,23 +509,23 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab
/doc/api/custom_attributes.md @msedlakjakubowski
/doc/api/dependencies.md @rdickenson
/doc/api/dependency_proxy.md @marcel.amirault
-/doc/api/deploy_keys.md @rdickenson
-/doc/api/deploy_tokens.md @rdickenson
-/doc/api/deployments.md @rdickenson
+/doc/api/deploy_keys.md @phillipwells
+/doc/api/deploy_tokens.md @phillipwells
+/doc/api/deployments.md @phillipwells
/doc/api/discussions.md @aqualls
/doc/api/dora/ @lciutacu
/doc/api/draft_notes.md @aqualls
-/doc/api/environments.md @rdickenson
+/doc/api/environments.md @phillipwells
/doc/api/epic_issues.md @msedlakjakubowski
/doc/api/epic_links.md @msedlakjakubowski
/doc/api/epics.md @msedlakjakubowski
/doc/api/error_tracking.md @drcatherinepope
/doc/api/events.md @eread
/doc/api/experiments.md @phillipwells
-/doc/api/feature_flag_user_lists.md @rdickenson
-/doc/api/feature_flags.md @rdickenson
-/doc/api/features.md @rdickenson
-/doc/api/freeze_periods.md @rdickenson
+/doc/api/feature_flag_user_lists.md @phillipwells
+/doc/api/feature_flags.md @phillipwells
+/doc/api/features.md @phillipwells
+/doc/api/freeze_periods.md @phillipwells
/doc/api/geo_nodes.md @axil
/doc/api/graphql/audit_report.md @eread
/doc/api/graphql/branch_rules.md @aqualls
@@ -547,9 +547,10 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab
/doc/api/group_labels.md @msedlakjakubowski
/doc/api/group_level_variables.md @marcel.amirault
/doc/api/group_milestones.md @msedlakjakubowski
-/doc/api/group_protected_environments.md @rdickenson
+/doc/api/group_protected_branches.md @aqualls
+/doc/api/group_protected_environments.md @phillipwells
/doc/api/group_relations_export.md @eread
-/doc/api/group_releases.md @rdickenson
+/doc/api/group_releases.md @phillipwells
/doc/api/group_repository_storage_moves.md @ashrafkhamis
/doc/api/group_wikis.md @ashrafkhamis
/doc/api/groups.md @lciutacu
@@ -611,14 +612,14 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab
/doc/api/project_vulnerabilities.md @aqualls
/doc/api/projects.md @lciutacu
/doc/api/protected_branches.md @aqualls
-/doc/api/protected_environments.md @rdickenson
+/doc/api/protected_environments.md @phillipwells
/doc/api/protected_tags.md @aqualls
-/doc/api/releases/ @rdickenson
+/doc/api/releases/ @phillipwells
/doc/api/remote_mirrors.md @aqualls
/doc/api/repositories.md @aqualls
/doc/api/repository_files.md @aqualls
/doc/api/repository_submodules.md @aqualls
-/doc/api/resource_groups.md @rdickenson
+/doc/api/resource_groups.md @phillipwells
/doc/api/resource_iteration_events.md @msedlakjakubowski
/doc/api/resource_label_events.md @eread
/doc/api/resource_milestone_events.md @msedlakjakubowski
@@ -649,22 +650,22 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab
/doc/api/users.md @jglassman1
/doc/api/version.md @phillipwells
/doc/api/visual_review_discussions.md @drcatherinepope
-/doc/api/vulnerabilities.md @dianalogan
-/doc/api/vulnerability_exports.md @dianalogan
-/doc/api/vulnerability_findings.md @dianalogan
+/doc/api/vulnerabilities.md @rdickenson
+/doc/api/vulnerability_exports.md @rdickenson
+/doc/api/vulnerability_findings.md @rdickenson
/doc/api/wikis.md @ashrafkhamis
/doc/architecture/blueprints/database/scalability/patterns/ @aqualls
/doc/architecture/blueprints/database_scaling/ @aqualls
/doc/ci/ @drcatherinepope
/doc/ci/caching/ @marcel.amirault
/doc/ci/chatops/ @phillipwells
-/doc/ci/cloud_deployment/ @rdickenson
+/doc/ci/cloud_deployment/ @phillipwells
/doc/ci/cloud_services/ @marcel.amirault
/doc/ci/directed_acyclic_graph/ @marcel.amirault
/doc/ci/docker/using_docker_images.md @fneill
-/doc/ci/environments/ @rdickenson
+/doc/ci/environments/ @phillipwells
/doc/ci/examples/authenticating-with-hashicorp-vault/ @marcel.amirault
-/doc/ci/examples/deployment/ @rdickenson
+/doc/ci/examples/deployment/ @phillipwells
/doc/ci/examples/semantic-release.md @marcel.amirault
/doc/ci/interactive_web_terminal/ @fneill
/doc/ci/introduction/ @marcel.amirault
@@ -679,7 +680,7 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab
/doc/ci/pipelines/pipeline_architectures.md @marcel.amirault
/doc/ci/pipelines/pipeline_artifacts.md @marcel.amirault
/doc/ci/quick_start/ @marcel.amirault
-/doc/ci/resource_groups/ @rdickenson
+/doc/ci/resource_groups/ @phillipwells
/doc/ci/runners/ @fneill
/doc/ci/secrets/ @marcel.amirault
/doc/ci/secure_files/ @marcel.amirault
@@ -692,6 +693,7 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab
/doc/ci/variables/ @marcel.amirault
/doc/ci/yaml/ @marcel.amirault
/doc/ci/yaml/artifacts_reports.md @drcatherinepope
+/doc/development/advanced_search.md @ashrafkhamis
/doc/development/application_limits.md @axil
/doc/development/audit_event_guide/ @eread
/doc/development/auto_devops.md @phillipwells
@@ -714,8 +716,6 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab
/doc/development/development_processes.md @sselhorn
/doc/development/distributed_tracing.md @msedlakjakubowski
/doc/development/documentation/ @sselhorn
-/doc/development/elasticsearch.md @ashrafkhamis
-/doc/development/search/advanced_search_migration_styleguide.md @ashrafkhamis
/doc/development/experiment_guide/ @phillipwells
/doc/development/export_csv.md @eread
/doc/development/fe_guide/content_editor.md @ashrafkhamis
@@ -749,6 +749,7 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab
/doc/development/issuable-like-models.md @msedlakjakubowski
/doc/development/issue_types.md @msedlakjakubowski
/doc/development/kubernetes.md @phillipwells
+/doc/development/labels/ @sselhorn
/doc/development/lfs.md @aqualls
/doc/development/logging.md @msedlakjakubowski
/doc/development/maintenance_mode.md @axil
@@ -765,8 +766,8 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab
/doc/development/prometheus_metrics.md @msedlakjakubowski
/doc/development/real_time.md @msedlakjakubowski
/doc/development/rubocop_development_guide.md @sselhorn
+/doc/development/search/ @ashrafkhamis
/doc/development/sec/ @rdickenson
-/doc/development/sec/security_report_ingestion_overview.md @dianalogan
/doc/development/secure_coding_guidelines.md @sselhorn
/doc/development/service_ping/ @lciutacu
/doc/development/snowplow/ @lciutacu
@@ -806,11 +807,10 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab
/doc/integration/trello_power_up.md @ashrafkhamis
/doc/integration/vault.md @phillipwells
/doc/operations/error_tracking.md @drcatherinepope
-/doc/operations/feature_flags.md @rdickenson
+/doc/operations/feature_flags.md @phillipwells
/doc/operations/incident_management/ @msedlakjakubowski
/doc/operations/index.md @msedlakjakubowski
/doc/operations/metrics/ @msedlakjakubowski
-/doc/operations/quickstart-guide.md @drcatherinepope
/doc/policy/ @axil
/doc/raketasks/ @axil
/doc/raketasks/generate_sample_prometheus_data.md @msedlakjakubowski
@@ -873,21 +873,18 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab
/doc/user/admin_area/settings/usage_statistics.md @lciutacu
/doc/user/admin_area/settings/visibility_and_access_controls.md @aqualls
/doc/user/analytics/ @lciutacu
-/doc/user/analytics/ci_cd_analytics.md @rdickenson
+/doc/user/analytics/ci_cd_analytics.md @phillipwells
/doc/user/application_security/ @rdickenson
-/doc/user/application_security/cve_id_request.md @dianalogan
-/doc/user/application_security/generate_test_vulnerabilities/ @dianalogan
/doc/user/application_security/policies/ @dianalogan
-/doc/user/application_security/security_dashboard/ @dianalogan
-/doc/user/application_security/vulnerabilities/ @dianalogan
-/doc/user/application_security/vulnerability_report/ @dianalogan
/doc/user/asciidoc.md @aqualls
/doc/user/award_emojis.md @msedlakjakubowski
/doc/user/clusters/ @phillipwells
-/doc/user/compliance/ @dianalogan
/doc/user/compliance/compliance_report/ @eread
/doc/user/compliance/index.md @eread
+/doc/user/compliance/license_approval_policies.md @dianalogan
+/doc/user/compliance/license_check_rules.md @dianalogan
/doc/user/compliance/license_compliance/ @rdickenson
+/doc/user/compliance/license_list.md @rdickenson
/doc/user/compliance/license_scanning_of_cyclonedx_files/ @rdickenson
/doc/user/crm/ @msedlakjakubowski
/doc/user/discussions/ @aqualls
@@ -913,7 +910,7 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab
/doc/user/markdown.md @msedlakjakubowski
/doc/user/namespace/ @lciutacu
/doc/user/okrs.md @msedlakjakubowski
-/doc/user/operations_dashboard/ @rdickenson
+/doc/user/operations_dashboard/ @phillipwells
/doc/user/packages/ @marcel.amirault
/doc/user/permissions.md @jglassman1
/doc/user/product_analytics/ @lciutacu
@@ -926,9 +923,9 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab
/doc/user/project/clusters/ @phillipwells
/doc/user/project/code_intelligence.md @aqualls
/doc/user/project/code_owners.md @aqualls
-/doc/user/project/deploy_boards.md @rdickenson
-/doc/user/project/deploy_keys/ @rdickenson
-/doc/user/project/deploy_tokens/ @rdickenson
+/doc/user/project/deploy_boards.md @phillipwells
+/doc/user/project/deploy_keys/ @phillipwells
+/doc/user/project/deploy_tokens/ @phillipwells
/doc/user/project/description_templates.md @msedlakjakubowski
/doc/user/project/file_lock.md @aqualls
/doc/user/project/git_attributes.md @aqualls
@@ -955,7 +952,7 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab
/doc/user/project/protected_tags.md @aqualls
/doc/user/project/push_options.md @aqualls
/doc/user/project/quick_actions.md @msedlakjakubowski
-/doc/user/project/releases/ @rdickenson
+/doc/user/project/releases/ @phillipwells
/doc/user/project/remote_development/ @ashrafkhamis
/doc/user/project/repository/ @aqualls
/doc/user/project/repository/file_finder.md @ashrafkhamis
diff --git a/app/assets/javascripts/add_context_commits_modal/components/add_context_commits_modal_wrapper.vue b/app/assets/javascripts/add_context_commits_modal/components/add_context_commits_modal_wrapper.vue
index a41ff42df20..c66b595ffdc 100644
--- a/app/assets/javascripts/add_context_commits_modal/components/add_context_commits_modal_wrapper.vue
+++ b/app/assets/javascripts/add_context_commits_modal/components/add_context_commits_modal_wrapper.vue
@@ -2,7 +2,7 @@
import { GlModal, GlTabs, GlTab, GlSearchBoxByType, GlSprintf, GlBadge } from '@gitlab/ui';
import { mapState, mapActions } from 'vuex';
import ReviewTabContainer from '~/add_context_commits_modal/components/review_tab_container.vue';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { BV_SHOW_MODAL } from '~/lib/utils/constants';
import { s__ } from '~/locale';
import eventHub from '../event_hub';
diff --git a/app/assets/javascripts/add_context_commits_modal/store/actions.js b/app/assets/javascripts/add_context_commits_modal/store/actions.js
index d4c9db2fa33..de9c7488ace 100644
--- a/app/assets/javascripts/add_context_commits_modal/store/actions.js
+++ b/app/assets/javascripts/add_context_commits_modal/store/actions.js
@@ -1,6 +1,6 @@
import _ from 'lodash';
import Api from '~/api';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import axios from '~/lib/utils/axios_utils';
import { s__ } from '~/locale';
import * as types from './mutation_types';
diff --git a/app/assets/javascripts/blob_edit/blob_bundle.js b/app/assets/javascripts/blob_edit/blob_bundle.js
index 509d399273d..01d35a0980f 100644
--- a/app/assets/javascripts/blob_edit/blob_bundle.js
+++ b/app/assets/javascripts/blob_edit/blob_bundle.js
@@ -2,7 +2,7 @@
import $ from 'jquery';
import initPopover from '~/blob/suggest_gitlab_ci_yml';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { setCookie } from '~/lib/utils/common_utils';
import Tracking from '~/tracking';
import NewCommitForm from '../new_commit_form';
diff --git a/app/assets/javascripts/blob_edit/edit_blob.js b/app/assets/javascripts/blob_edit/edit_blob.js
index db47b11cca1..f021553ae98 100644
--- a/app/assets/javascripts/blob_edit/edit_blob.js
+++ b/app/assets/javascripts/blob_edit/edit_blob.js
@@ -4,7 +4,7 @@ import { SourceEditorExtension } from '~/editor/extensions/source_editor_extensi
import { FileTemplateExtension } from '~/editor/extensions/source_editor_file_template_ext';
import { ToolbarExtension } from '~/editor/extensions/source_editor_toolbar_ext';
import SourceEditor from '~/editor/source_editor';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import axios from '~/lib/utils/axios_utils';
import { addEditorMarkdownListeners } from '~/lib/utils/text_markdown';
import { insertFinalNewline } from '~/lib/utils/text_utility';
diff --git a/app/assets/javascripts/ci/runner/admin_register_runner/admin_register_runner_app.vue b/app/assets/javascripts/ci/runner/admin_register_runner/admin_register_runner_app.vue
index b291be41203..1ae119da959 100644
--- a/app/assets/javascripts/ci/runner/admin_register_runner/admin_register_runner_app.vue
+++ b/app/assets/javascripts/ci/runner/admin_register_runner/admin_register_runner_app.vue
@@ -2,12 +2,13 @@
import { GlButton } from '@gitlab/ui';
import { s__, sprintf } from '~/locale';
import { createAlert } from '~/flash';
-import { getParameterByName } from '~/lib/utils/url_utility';
+import { getParameterByName, updateHistory, mergeUrlParams } from '~/lib/utils/url_utility';
import { convertToGraphQLId } from '~/graphql_shared/utils';
import { TYPENAME_CI_RUNNER } from '~/graphql_shared/constants';
import runnerForRegistrationQuery from '../graphql/register/runner_for_registration.query.graphql';
import { I18N_FETCH_ERROR, PARAM_KEY_PLATFORM, DEFAULT_PLATFORM } from '../constants';
import RegistrationInstructions from '../components/registration/registration_instructions.vue';
+import PlatformsDrawer from '../components/registration/platforms_drawer.vue';
import { captureException } from '../sentry_utils';
export default {
@@ -15,6 +16,7 @@ export default {
components: {
GlButton,
RegistrationInstructions,
+ PlatformsDrawer,
},
props: {
runnerId: {
@@ -30,6 +32,7 @@ export default {
return {
platform: getParameterByName(PARAM_KEY_PLATFORM) || DEFAULT_PLATFORM,
runner: null,
+ isDrawerOpen: false,
};
},
apollo: {
@@ -62,6 +65,21 @@ export default {
return this.runner?.ephemeralAuthenticationToken;
},
},
+ watch: {
+ platform(platform) {
+ updateHistory({
+ url: mergeUrlParams({ [PARAM_KEY_PLATFORM]: platform }, window.location.href),
+ });
+ },
+ },
+ methods: {
+ onSelectPlatform(platform) {
+ this.platform = platform;
+ },
+ onToggleDrawer(val = !this.isDrawerOpen) {
+ this.isDrawerOpen = val;
+ },
+ },
};
</script>
<template>
@@ -72,6 +90,14 @@ export default {
:loading="$apollo.queries.runner.loading"
:platform="platform"
:token="ephemeralAuthenticationToken"
+ @toggleDrawer="onToggleDrawer"
+ />
+
+ <platforms-drawer
+ :platform="platform"
+ :open="isDrawerOpen"
+ @selectPlatform="onSelectPlatform"
+ @close="onToggleDrawer(false)"
/>
<gl-button :href="runnersPath" variant="confirm">{{
diff --git a/app/assets/javascripts/ci/runner/components/registration/platforms_drawer.vue b/app/assets/javascripts/ci/runner/components/registration/platforms_drawer.vue
new file mode 100644
index 00000000000..a56302898ca
--- /dev/null
+++ b/app/assets/javascripts/ci/runner/components/registration/platforms_drawer.vue
@@ -0,0 +1,131 @@
+<script>
+import { GlDrawer, GlFormGroup, GlFormSelect, GlLink, GlSprintf } from '@gitlab/ui';
+import { getContentWrapperHeight } from '~/lib/utils/dom_utils';
+import { DRAWER_Z_INDEX } from '~/lib/utils/constants';
+
+import {
+ DEFAULT_PLATFORM,
+ MACOS_PLATFORM,
+ LINUX_PLATFORM,
+ WINDOWS_PLATFORM,
+ INSTALL_HELP_URL,
+} from '../../constants';
+import { installScript, platformArchitectures } from './utils';
+
+import CliCommand from './cli_command.vue';
+
+export default {
+ components: {
+ GlDrawer,
+ GlFormGroup,
+ GlFormSelect,
+ GlLink,
+ GlSprintf,
+ CliCommand,
+ },
+ props: {
+ open: {
+ type: Boolean,
+ required: true,
+ },
+ platform: {
+ type: String,
+ required: false,
+ default: DEFAULT_PLATFORM,
+ },
+ },
+ data() {
+ return {
+ selectedPlatform: this.platform,
+ selectedArchitecture: null,
+ };
+ },
+ computed: {
+ drawerHeightOffset() {
+ return getContentWrapperHeight('.content-wrapper');
+ },
+ architectureOptions() {
+ return platformArchitectures({ platform: this.selectedPlatform });
+ },
+ script() {
+ return installScript({
+ platform: this.selectedPlatform,
+ architecture: this.selectedArchitecture,
+ });
+ },
+ },
+ watch: {
+ selectedPlatform() {
+ this.selectedArchitecture =
+ this.architectureOptions.find((value) => value === this.selectedArchitecture) ||
+ this.architectureOptions[0];
+
+ this.$emit('selectPlatform', this.selectedPlatform);
+ },
+ },
+ created() {
+ [this.selectedArchitecture] = this.architectureOptions;
+ },
+ methods: {
+ onClose() {
+ this.$emit('close');
+ },
+ },
+ platformOptions: [
+ /* eslint-disable @gitlab/require-i18n-strings */
+ { value: LINUX_PLATFORM, text: 'Linux' },
+ { value: MACOS_PLATFORM, text: 'macOS' },
+ { value: WINDOWS_PLATFORM, text: 'Windows' },
+ /* eslint-enable @gitlab/require-i18n-strings */
+ ],
+ INSTALL_HELP_URL,
+ DRAWER_Z_INDEX,
+};
+</script>
+<template>
+ <gl-drawer
+ :open="open"
+ :header-height="drawerHeightOffset"
+ :z-index="$options.DRAWER_Z_INDEX"
+ @close="onClose"
+ >
+ <template #title>
+ <h2 class="gl-my-0 gl-font-size-h2 gl-line-height-24">
+ {{ s__('Runners|Install GitLab Runner') }}
+ </h2>
+ </template>
+ <div>
+ <p>{{ s__('Runners|Select platform specifications to install GitLab Runner.') }}</p>
+
+ <gl-form-group :label="s__('Runners|Environment')" label-for="runner-environment-select">
+ <gl-form-select
+ id="runner-environment-select"
+ v-model="selectedPlatform"
+ :options="$options.platformOptions"
+ />
+ </gl-form-group>
+
+ <gl-form-group :label="s__('Runners|Architecture')" label-for="runner-architecture-select">
+ <gl-form-select
+ id="runner-architecture-select"
+ v-model="selectedArchitecture"
+ :options="architectureOptions"
+ />
+ </gl-form-group>
+
+ <cli-command :command="script" />
+
+ <p>
+ <gl-sprintf
+ :message="
+ s__('Runners|See more %{linkStart}installation methods and architectures%{linkEnd}.')
+ "
+ >
+ <template #link="{ content }">
+ <gl-link :href="$options.INSTALL_HELP_URL">{{ content }}</gl-link>
+ </template>
+ </gl-sprintf>
+ </p>
+ </div>
+ </gl-drawer>
+</template>
diff --git a/app/assets/javascripts/ci/runner/components/registration/registration_instructions.vue b/app/assets/javascripts/ci/runner/components/registration/registration_instructions.vue
index e01d8b64839..e611cae1f35 100644
--- a/app/assets/javascripts/ci/runner/components/registration/registration_instructions.vue
+++ b/app/assets/javascripts/ci/runner/components/registration/registration_instructions.vue
@@ -2,7 +2,7 @@
import { GlIcon, GlLink, GlSprintf, GlSkeletonLoader } from '@gitlab/ui';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
-import { INSTALL_HELP_URL, EXECUTORS_HELP_URL, SERVICE_COMMANDS_HELP_URL } from '../../constants';
+import { EXECUTORS_HELP_URL, SERVICE_COMMANDS_HELP_URL } from '../../constants';
import CliCommand from './cli_command.vue';
import { commandPrompt, registerCommand, runCommand } from './utils';
@@ -42,7 +42,11 @@ export default {
return runCommand({ platform: this.platform });
},
},
- INSTALL_HELP_URL,
+ methods: {
+ toggleDrawer(val) {
+ this.$emit('toggleDrawer', val);
+ },
+ },
EXECUTORS_HELP_URL,
SERVICE_COMMANDS_HELP_URL,
};
@@ -58,7 +62,7 @@ export default {
"
>
<template #link="{ content }">
- <gl-link :href="$options.INSTALL_HELP_URL">{{ content }}</gl-link>
+ <gl-link @click="toggleDrawer()">{{ content }}</gl-link>
</template>
</gl-sprintf>
</p>
diff --git a/app/assets/javascripts/ci/runner/components/registration/registration_token_reset_dropdown_item.vue b/app/assets/javascripts/ci/runner/components/registration/registration_token_reset_dropdown_item.vue
index ac2793654c8..db8c953ce3c 100644
--- a/app/assets/javascripts/ci/runner/components/registration/registration_token_reset_dropdown_item.vue
+++ b/app/assets/javascripts/ci/runner/components/registration/registration_token_reset_dropdown_item.vue
@@ -1,6 +1,6 @@
<script>
import { GlDropdownItem, GlLoadingIcon, GlModal, GlModalDirective } from '@gitlab/ui';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { TYPENAME_GROUP, TYPENAME_PROJECT } from '~/graphql_shared/constants';
import { convertToGraphQLId } from '~/graphql_shared/utils';
import { __, s__ } from '~/locale';
diff --git a/app/assets/javascripts/ci/runner/components/registration/scripts/linux/install.sh b/app/assets/javascripts/ci/runner/components/registration/scripts/linux/install.sh
new file mode 100644
index 00000000000..a8ba2592128
--- /dev/null
+++ b/app/assets/javascripts/ci/runner/components/registration/scripts/linux/install.sh
@@ -0,0 +1,12 @@
+# Download the binary for your system
+sudo curl -L --output /usr/local/bin/gitlab-runner ${GITLAB_CI_RUNNER_DOWNLOAD_LOCATION}
+
+# Give it permission to execute
+sudo chmod +x /usr/local/bin/gitlab-runner
+
+# Create a GitLab Runner user
+sudo useradd --comment 'GitLab Runner' --create-home gitlab-runner --shell /bin/bash
+
+# Install and run as a service
+sudo gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner
+sudo gitlab-runner start
diff --git a/app/assets/javascripts/ci/runner/components/registration/scripts/osx/install.sh b/app/assets/javascripts/ci/runner/components/registration/scripts/osx/install.sh
new file mode 100644
index 00000000000..76c893bacfc
--- /dev/null
+++ b/app/assets/javascripts/ci/runner/components/registration/scripts/osx/install.sh
@@ -0,0 +1,11 @@
+# Download the binary for your system
+sudo curl --output /usr/local/bin/gitlab-runner ${GITLAB_CI_RUNNER_DOWNLOAD_LOCATION}
+
+# Give it permission to execute
+sudo chmod +x /usr/local/bin/gitlab-runner
+
+# The rest of the commands execute as the user who will run the runner
+# Register the runner (steps below), then run
+cd ~
+gitlab-runner install
+gitlab-runner start
diff --git a/app/assets/javascripts/ci/runner/components/registration/scripts/windows/install.ps1 b/app/assets/javascripts/ci/runner/components/registration/scripts/windows/install.ps1
new file mode 100644
index 00000000000..019363fc3f7
--- /dev/null
+++ b/app/assets/javascripts/ci/runner/components/registration/scripts/windows/install.ps1
@@ -0,0 +1,13 @@
+# Run PowerShell: https://docs.microsoft.com/en-us/powershell/scripting/windows-powershell/starting-windows-powershell?view=powershell-7#with-administrative-privileges-run-as-administrator
+# Create a folder somewhere on your system, for example: C:\GitLab-Runner
+New-Item -Path 'C:\GitLab-Runner' -ItemType Directory
+
+# Change to the folder
+cd 'C:\GitLab-Runner'
+
+# Download binary
+Invoke-WebRequest -Uri "${GITLAB_CI_RUNNER_DOWNLOAD_LOCATION}" -OutFile "gitlab-runner.exe"
+
+# Register the runner (steps below), then run
+.\gitlab-runner.exe install
+.\gitlab-runner.exe start
diff --git a/app/assets/javascripts/ci/runner/components/registration/utils.js b/app/assets/javascripts/ci/runner/components/registration/utils.js
index 32fb8eac5e9..621963349ae 100644
--- a/app/assets/javascripts/ci/runner/components/registration/utils.js
+++ b/app/assets/javascripts/ci/runner/components/registration/utils.js
@@ -1,11 +1,15 @@
+/* eslint-disable @gitlab/require-i18n-strings */
import {
DEFAULT_PLATFORM,
LINUX_PLATFORM,
MACOS_PLATFORM,
WINDOWS_PLATFORM,
+ DOWNLOAD_LOCATIONS,
} from '../../constants';
+import linuxInstall from './scripts/linux/install.sh?raw';
+import osxInstall from './scripts/osx/install.sh?raw';
+import windowsInstall from './scripts/windows/install.ps1?raw';
-/* eslint-disable @gitlab/require-i18n-strings */
const OS = {
[LINUX_PLATFORM]: {
commandPrompt: '$',
@@ -33,11 +37,40 @@ export const registerCommand = ({ platform, url = gon.gitlab_url, registrationTo
return [
`${executable({ platform })} register`,
` --url ${url}`,
- ` --registration-token ${registrationToken}`,
+ ...(registrationToken ? [` --registration-token ${registrationToken}`] : []),
];
};
export const runCommand = ({ platform }) => {
return `${executable({ platform })} run`;
};
-/* eslint-enable @gitlab/require-i18n-strings */
+
+const importInstallScript = ({ platform = DEFAULT_PLATFORM }) => {
+ switch (platform) {
+ case LINUX_PLATFORM:
+ return linuxInstall;
+ case MACOS_PLATFORM:
+ return osxInstall;
+ case WINDOWS_PLATFORM:
+ return windowsInstall;
+ default:
+ return '';
+ }
+};
+
+export const platformArchitectures = ({ platform }) => {
+ return DOWNLOAD_LOCATIONS[platform].map(({ arch }) => arch);
+};
+
+export const installScript = ({ platform, architecture }) => {
+ const downloadLocation = DOWNLOAD_LOCATIONS[platform].find(({ arch }) => arch === architecture)
+ .url;
+
+ return importInstallScript({ platform })
+ .replace(
+ // eslint-disable-next-line no-template-curly-in-string
+ '${GITLAB_CI_RUNNER_DOWNLOAD_LOCATION}',
+ downloadLocation,
+ )
+ .trim();
+};
diff --git a/app/assets/javascripts/ci/runner/components/runner_bulk_delete.vue b/app/assets/javascripts/ci/runner/components/runner_bulk_delete.vue
index 8dde3ac4e19..e7b26ec6d4e 100644
--- a/app/assets/javascripts/ci/runner/components/runner_bulk_delete.vue
+++ b/app/assets/javascripts/ci/runner/components/runner_bulk_delete.vue
@@ -1,6 +1,6 @@
<script>
import { GlButton, GlModalDirective, GlModal, GlSprintf } from '@gitlab/ui';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { __, s__, n__, sprintf } from '~/locale';
import checkedRunnerIdsQuery from '../graphql/list/checked_runner_ids.query.graphql';
import BulkRunnerDelete from '../graphql/list/bulk_runner_delete.mutation.graphql';
diff --git a/app/assets/javascripts/ci/runner/components/runner_delete_button.vue b/app/assets/javascripts/ci/runner/components/runner_delete_button.vue
index f02e6bce5c3..020487fc727 100644
--- a/app/assets/javascripts/ci/runner/components/runner_delete_button.vue
+++ b/app/assets/javascripts/ci/runner/components/runner_delete_button.vue
@@ -1,7 +1,7 @@
<script>
import { GlButton, GlModalDirective, GlTooltipDirective } from '@gitlab/ui';
import runnerDeleteMutation from '~/ci/runner/graphql/shared/runner_delete.mutation.graphql';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { sprintf, s__ } from '~/locale';
import { captureException } from '~/ci/runner/sentry_utils';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
diff --git a/app/assets/javascripts/ci/runner/components/runner_jobs.vue b/app/assets/javascripts/ci/runner/components/runner_jobs.vue
index 9003eba3636..f5287f597ab 100644
--- a/app/assets/javascripts/ci/runner/components/runner_jobs.vue
+++ b/app/assets/javascripts/ci/runner/components/runner_jobs.vue
@@ -1,6 +1,6 @@
<script>
import { GlSkeletonLoader } from '@gitlab/ui';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import runnerJobsQuery from '../graphql/show/runner_jobs.query.graphql';
import { I18N_FETCH_ERROR, I18N_NO_JOBS_FOUND, RUNNER_DETAILS_JOBS_PAGE_SIZE } from '../constants';
import { captureException } from '../sentry_utils';
diff --git a/app/assets/javascripts/ci/runner/components/runner_pause_button.vue b/app/assets/javascripts/ci/runner/components/runner_pause_button.vue
index 2c80518e772..a27af232e97 100644
--- a/app/assets/javascripts/ci/runner/components/runner_pause_button.vue
+++ b/app/assets/javascripts/ci/runner/components/runner_pause_button.vue
@@ -1,7 +1,7 @@
<script>
import { GlButton, GlTooltipDirective } from '@gitlab/ui';
import runnerToggleActiveMutation from '~/ci/runner/graphql/shared/runner_toggle_active.mutation.graphql';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { captureException } from '~/ci/runner/sentry_utils';
import { I18N_PAUSE, I18N_PAUSE_TOOLTIP, I18N_RESUME, I18N_RESUME_TOOLTIP } from '../constants';
diff --git a/app/assets/javascripts/ci/runner/components/runner_projects.vue b/app/assets/javascripts/ci/runner/components/runner_projects.vue
index 4a6e90b44a9..4cfc57340f5 100644
--- a/app/assets/javascripts/ci/runner/components/runner_projects.vue
+++ b/app/assets/javascripts/ci/runner/components/runner_projects.vue
@@ -1,7 +1,7 @@
<script>
import { GlSearchBoxByType, GlSkeletonLoader } from '@gitlab/ui';
import { sprintf, formatNumber } from '~/locale';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import runnerProjectsQuery from '../graphql/show/runner_projects.query.graphql';
import {
I18N_ASSIGNED_PROJECTS,
diff --git a/app/assets/javascripts/ci/runner/components/runner_update_form.vue b/app/assets/javascripts/ci/runner/components/runner_update_form.vue
index a9790d06ca7..dd8e965cecd 100644
--- a/app/assets/javascripts/ci/runner/components/runner_update_form.vue
+++ b/app/assets/javascripts/ci/runner/components/runner_update_form.vue
@@ -13,7 +13,7 @@ import {
modelToUpdateMutationVariables,
runnerToModel,
} from 'ee_else_ce/ci/runner/runner_update_form_utils';
-import { createAlert, VARIANT_SUCCESS } from '~/flash';
+import { createAlert, VARIANT_SUCCESS } from '~/alert';
import { redirectTo } from '~/lib/utils/url_utility';
import { __ } from '~/locale';
import { captureException } from '~/ci/runner/sentry_utils';
diff --git a/app/assets/javascripts/ci/runner/components/search_tokens/tag_token.vue b/app/assets/javascripts/ci/runner/components/search_tokens/tag_token.vue
index 6e7c41885f8..1de7775090a 100644
--- a/app/assets/javascripts/ci/runner/components/search_tokens/tag_token.vue
+++ b/app/assets/javascripts/ci/runner/components/search_tokens/tag_token.vue
@@ -1,6 +1,6 @@
<script>
import { GlFilteredSearchSuggestion, GlToken } from '@gitlab/ui';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import axios from '~/lib/utils/axios_utils';
import { s__ } from '~/locale';
diff --git a/app/assets/javascripts/ci/runner/constants.js b/app/assets/javascripts/ci/runner/constants.js
index 054e6b954d0..8e31b7f2d44 100644
--- a/app/assets/javascripts/ci/runner/constants.js
+++ b/app/assets/javascripts/ci/runner/constants.js
@@ -183,6 +183,55 @@ export const MACOS_PLATFORM = 'osx';
export const WINDOWS_PLATFORM = 'windows';
export const AWS_PLATFORM = 'aws';
+export const DOWNLOAD_LOCATIONS = {
+ [LINUX_PLATFORM]: [
+ {
+ arch: 'amd64',
+ url:
+ 'https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-amd64',
+ },
+ {
+ arch: '386',
+ url:
+ 'https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-386',
+ },
+ {
+ arch: 'arm',
+ url:
+ 'https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-arm',
+ },
+ {
+ arch: 'arm64',
+ url:
+ 'https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-arm64',
+ },
+ ],
+ [MACOS_PLATFORM]: [
+ {
+ arch: 'amd64',
+ url:
+ 'https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-darwin-amd64',
+ },
+ {
+ arch: 'arm64',
+ url:
+ 'https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-darwin-arm64',
+ },
+ ],
+ [WINDOWS_PLATFORM]: [
+ {
+ arch: 'amd64',
+ url:
+ 'https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-windows-amd64.exe',
+ },
+ {
+ arch: '386',
+ url:
+ 'https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-windows-386.exe',
+ },
+ ],
+};
+
export const DEFAULT_PLATFORM = LINUX_PLATFORM;
// Runner docs are in a separate repository and are not shipped with GitLab
diff --git a/app/assets/javascripts/ci/runner/local_storage_alert/show_alert_from_local_storage.js b/app/assets/javascripts/ci/runner/local_storage_alert/show_alert_from_local_storage.js
index d768a06494a..bad3ca6024e 100644
--- a/app/assets/javascripts/ci/runner/local_storage_alert/show_alert_from_local_storage.js
+++ b/app/assets/javascripts/ci/runner/local_storage_alert/show_alert_from_local_storage.js
@@ -7,7 +7,7 @@ export const showAlertFromLocalStorage = async () => {
if (alertOptions) {
try {
- const { createAlert } = await import('~/flash');
+ const { createAlert } = await import('~/alert');
createAlert(JSON.parse(alertOptions));
} catch {
// ignore when the alert data cannot be parsed
diff --git a/app/assets/javascripts/content_editor/components/content_editor.vue b/app/assets/javascripts/content_editor/components/content_editor.vue
index 237808983ee..bb777cb09eb 100644
--- a/app/assets/javascripts/content_editor/components/content_editor.vue
+++ b/app/assets/javascripts/content_editor/components/content_editor.vue
@@ -1,7 +1,7 @@
<script>
import { EditorContent as TiptapEditorContent } from '@tiptap/vue-2';
import { __ } from '~/locale';
-import { VARIANT_DANGER } from '~/flash';
+import { VARIANT_DANGER } from '~/alert';
import { createContentEditor } from '../services/create_content_editor';
import { ALERT_EVENT, TIPTAP_AUTOFOCUS_OPTIONS } from '../constants';
import ContentEditorAlert from './content_editor_alert.vue';
diff --git a/app/assets/javascripts/content_editor/extensions/paste_markdown.js b/app/assets/javascripts/content_editor/extensions/paste_markdown.js
index fa465160598..0a9a0d8d4c1 100644
--- a/app/assets/javascripts/content_editor/extensions/paste_markdown.js
+++ b/app/assets/javascripts/content_editor/extensions/paste_markdown.js
@@ -1,7 +1,7 @@
import { Extension } from '@tiptap/core';
import { Plugin, PluginKey } from '@tiptap/pm/state';
import { __ } from '~/locale';
-import { VARIANT_DANGER } from '~/flash';
+import { VARIANT_DANGER } from '~/alert';
import createMarkdownDeserializer from '../services/gl_api_markdown_deserializer';
import { ALERT_EVENT, EXTENSION_PRIORITY_HIGHEST } from '../constants';
import CodeBlockHighlight from './code_block_highlight';
diff --git a/app/assets/javascripts/content_editor/extensions/table.js b/app/assets/javascripts/content_editor/extensions/table.js
index d7456ab4094..de8170eff93 100644
--- a/app/assets/javascripts/content_editor/extensions/table.js
+++ b/app/assets/javascripts/content_editor/extensions/table.js
@@ -1,6 +1,6 @@
import { Table } from '@tiptap/extension-table';
import { debounce } from 'lodash';
-import { VARIANT_WARNING } from '~/flash';
+import { VARIANT_WARNING } from '~/alert';
import { __ } from '~/locale';
import { getMarkdownSource } from '../services/markdown_sourcemap';
import { shouldRenderHTMLTable } from '../services/serialization_helpers';
diff --git a/app/assets/javascripts/content_editor/services/upload_helpers.js b/app/assets/javascripts/content_editor/services/upload_helpers.js
index abfb73183dd..de1a187b246 100644
--- a/app/assets/javascripts/content_editor/services/upload_helpers.js
+++ b/app/assets/javascripts/content_editor/services/upload_helpers.js
@@ -1,4 +1,4 @@
-import { VARIANT_DANGER } from '~/flash';
+import { VARIANT_DANGER } from '~/alert';
import axios from '~/lib/utils/axios_utils';
import { __ } from '~/locale';
import { extractFilename, readFileAsDataURL } from './utils';
diff --git a/app/assets/javascripts/graphql_shared/possible_types.json b/app/assets/javascripts/graphql_shared/possible_types.json
index 4a5536986bd..22629dfb7d8 100644
--- a/app/assets/javascripts/graphql_shared/possible_types.json
+++ b/app/assets/javascripts/graphql_shared/possible_types.json
@@ -152,6 +152,7 @@
"WorkItemWidgetLabels",
"WorkItemWidgetMilestone",
"WorkItemWidgetNotes",
+ "WorkItemWidgetNotifications",
"WorkItemWidgetProgress",
"WorkItemWidgetRequirementLegacy",
"WorkItemWidgetStartAndDueDate",
@@ -159,4 +160,4 @@
"WorkItemWidgetTestReports",
"WorkItemWidgetWeight"
]
-} \ No newline at end of file
+}
diff --git a/app/assets/javascripts/invite_members/utils/trigger_successful_invite_alert.js b/app/assets/javascripts/invite_members/utils/trigger_successful_invite_alert.js
index 4d3a7951265..e556582742b 100644
--- a/app/assets/javascripts/invite_members/utils/trigger_successful_invite_alert.js
+++ b/app/assets/javascripts/invite_members/utils/trigger_successful_invite_alert.js
@@ -1,4 +1,4 @@
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import AccessorUtilities from '~/lib/utils/accessor';
import { TOAST_MESSAGE_LOCALSTORAGE_KEY, TOAST_MESSAGE_SUCCESSFUL } from '../constants';
diff --git a/app/assets/javascripts/merge_request.js b/app/assets/javascripts/merge_request.js
index 61abdca0a5b..b045da93fd8 100644
--- a/app/assets/javascripts/merge_request.js
+++ b/app/assets/javascripts/merge_request.js
@@ -1,7 +1,7 @@
/* eslint-disable func-names, no-underscore-dangle, consistent-return */
import $ from 'jquery';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import toast from '~/vue_shared/plugins/global_toast';
import { __ } from '~/locale';
import eventHub from '~/vue_merge_request_widget/event_hub';
diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js
index 46ee8fecfc5..d55e942dafa 100644
--- a/app/assets/javascripts/merge_request_tabs.js
+++ b/app/assets/javascripts/merge_request_tabs.js
@@ -1,7 +1,7 @@
/* eslint-disable class-methods-use-this */
import $ from 'jquery';
import Vue from 'vue';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { getCookie, isMetaClick, parseBoolean, scrollToElement } from '~/lib/utils/common_utils';
import { parseUrlPathname } from '~/lib/utils/url_utility';
import createEventHub from '~/helpers/event_hub_factory';
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue b/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue
index 213c9ca7f54..74922dd922c 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue
@@ -1,5 +1,5 @@
<script>
-import { GlButton, GlSprintf, GlLink } from '@gitlab/ui';
+import { GlButton, GlSprintf } from '@gitlab/ui';
import { createAlert } from '~/alert';
import { BV_SHOW_MODAL } from '~/lib/utils/constants';
import { HTTP_STATUS_UNAUTHORIZED } from '~/lib/utils/http_status';
@@ -14,7 +14,6 @@ import { INVALID_RULES_DOCS_PATH } from '../../constants';
import ApprovalsSummary from './approvals_summary.vue';
import ApprovalsSummaryOptional from './approvals_summary_optional.vue';
import { FETCH_LOADING, APPROVE_ERROR, UNAPPROVE_ERROR } from './messages';
-import { humanizeInvalidApproversRules } from './humanized_text';
export default {
name: 'MRWidgetApprovals',
@@ -25,7 +24,6 @@ export default {
ApprovalsSummaryOptional,
GlButton,
GlSprintf,
- GlLink,
},
mixins: [approvalsMixin, glFeatureFlagsMixin()],
props: {
@@ -84,7 +82,7 @@ export default {
return this.mr.mergeRequestApproversAvailable && this.invalidRules.length;
},
invalidRulesText() {
- return humanizeInvalidApproversRules(this.invalidRules);
+ return this.invalidRules.length;
},
approvedBy() {
return this.approvals.approvedBy?.nodes || [];
@@ -200,10 +198,10 @@ export default {
linkToInvalidRules: INVALID_RULES_DOCS_PATH,
i18n: {
invalidRuleSingular: s__(
- 'mrWidget|Approval rule %{rules} is invalid. GitLab has approved this rule automatically to unblock the merge request. %{link}',
+ 'mrWidget|%{rules} invalid rule has been approved automatically, as no one can approve it.',
),
invalidRulesPlural: s__(
- 'mrWidget|Approval rules %{rules} are invalid. GitLab has approved these rules automatically to unblock the merge request. %{link}',
+ 'mrWidget|%{rules} invalid rules have been approved automatically, as no one can approve them.',
),
learnMore: __('Learn more.'),
},
@@ -241,14 +239,7 @@ export default {
</div>
<div v-if="hasInvalidRules" class="gl-text-gray-400 gl-mt-2" data-testid="invalid-rules">
<gl-sprintf :message="pluralizedRuleText">
- <template #rules>
- {{ invalidRulesText }}
- </template>
- <template #link>
- <gl-link :href="$options.linkToInvalidRules" target="_blank">
- {{ $options.i18n.learnMore }}
- </gl-link>
- </template>
+ <template #rules>{{ invalidRulesText }}</template>
</gl-sprintf>
</div>
</div>
diff --git a/app/graphql/mutations/metrics/dashboard/annotations/create.rb b/app/graphql/mutations/metrics/dashboard/annotations/create.rb
index 2e7c0c5a2f9..d458bdcf82b 100644
--- a/app/graphql/mutations/metrics/dashboard/annotations/create.rb
+++ b/app/graphql/mutations/metrics/dashboard/annotations/create.rb
@@ -10,7 +10,7 @@ module Mutations
ANNOTATION_SOURCE_ARGUMENT_ERROR = 'Either a cluster or environment global id is required'
INVALID_ANNOTATION_SOURCE_ERROR = 'Invalid cluster or environment id'
- authorize :create_metrics_dashboard_annotation
+ authorize :admin_metrics_dashboard_annotation
field :annotation,
Types::Metrics::Dashboards::AnnotationType,
diff --git a/app/graphql/mutations/metrics/dashboard/annotations/delete.rb b/app/graphql/mutations/metrics/dashboard/annotations/delete.rb
index e0fadff13d4..0ee2791f78b 100644
--- a/app/graphql/mutations/metrics/dashboard/annotations/delete.rb
+++ b/app/graphql/mutations/metrics/dashboard/annotations/delete.rb
@@ -7,7 +7,7 @@ module Mutations
class Delete < Base
graphql_name 'DeleteAnnotation'
- authorize :delete_metrics_dashboard_annotation
+ authorize :admin_metrics_dashboard_annotation
argument :id, ::Types::GlobalIDType[::Metrics::Dashboard::Annotation],
required: true,
diff --git a/app/graphql/resolvers/work_items_resolver.rb b/app/graphql/resolvers/work_items_resolver.rb
index 0c9aac80274..fc086f35ba6 100644
--- a/app/graphql/resolvers/work_items_resolver.rb
+++ b/app/graphql/resolvers/work_items_resolver.rb
@@ -66,7 +66,8 @@ module Resolvers
parent: :work_item_parent,
children: { work_item_children_by_relative_position: [:author, { project: :project_feature }] },
labels: :labels,
- milestone: { milestone: [:project, :group] }
+ milestone: { milestone: [:project, :group] },
+ subscribed: [:assignees, :award_emoji, { notes: [:author, :award_emoji] }]
}
end
diff --git a/app/graphql/types/work_items/widget_interface.rb b/app/graphql/types/work_items/widget_interface.rb
index 672a78f12e1..50f8e4f7d8a 100644
--- a/app/graphql/types/work_items/widget_interface.rb
+++ b/app/graphql/types/work_items/widget_interface.rb
@@ -18,7 +18,8 @@ module Types
::Types::WorkItems::Widgets::AssigneesType,
::Types::WorkItems::Widgets::StartAndDueDateType,
::Types::WorkItems::Widgets::MilestoneType,
- ::Types::WorkItems::Widgets::NotesType
+ ::Types::WorkItems::Widgets::NotesType,
+ ::Types::WorkItems::Widgets::NotificationsType
].freeze
def self.ce_orphan_types
@@ -44,6 +45,8 @@ module Types
::Types::WorkItems::Widgets::MilestoneType
when ::WorkItems::Widgets::Notes
::Types::WorkItems::Widgets::NotesType
+ when ::WorkItems::Widgets::Notifications
+ ::Types::WorkItems::Widgets::NotificationsType
else
raise "Unknown GraphQL type for widget #{object}"
end
diff --git a/app/graphql/types/work_items/widgets/notifications_type.rb b/app/graphql/types/work_items/widgets/notifications_type.rb
new file mode 100644
index 00000000000..85928817d07
--- /dev/null
+++ b/app/graphql/types/work_items/widgets/notifications_type.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module Types
+ module WorkItems
+ module Widgets
+ # Disabling widget level authorization as it might be too granular
+ # and we already authorize the parent work item
+ # rubocop:disable Graphql/AuthorizeTypes
+ class NotificationsType < BaseObject
+ graphql_name 'WorkItemWidgetNotifications'
+ description 'Represents the notifications widget'
+
+ implements Types::WorkItems::WidgetInterface
+
+ field :subscribed, GraphQL::Types::Boolean,
+ null: false,
+ description: 'Whether the current user is subscribed to notifications on the work item.'
+
+ def subscribed
+ object.work_item.subscribed?(current_user, object.work_item.project)
+ end
+ end
+ # rubocop:enable Graphql/AuthorizeTypes
+ end
+ end
+end
diff --git a/app/models/concerns/subscribable.rb b/app/models/concerns/subscribable.rb
index 5a10ea7a248..fe47393c554 100644
--- a/app/models/concerns/subscribable.rb
+++ b/app/models/concerns/subscribable.rb
@@ -27,8 +27,6 @@ module Subscribable
def lazy_subscription(user, project = nil)
return unless user
- # handle project and group labels as well as issuable subscriptions
- subscribable_type = self.class.ancestors.include?(Label) ? 'Label' : self.class.name
BatchLoader.for(id: id, subscribable_type: subscribable_type, project_id: project&.id).batch do |items, loader|
values = items.each_with_object({ ids: Set.new, subscribable_types: Set.new, project_ids: Set.new }) do |item, result|
result[:ids] << item[:id]
@@ -121,4 +119,15 @@ module Subscribable
subscriptions
.where(t[:project_id].eq(nil).or(t[:project_id].eq(project.try(:id))))
end
+
+ def subscribable_type
+ # handle project and group labels as well as issuable subscriptions
+ if self.class.ancestors.include?(Label)
+ 'Label'
+ elsif self.class.ancestors.include?(Issue)
+ 'Issue'
+ else
+ self.class.name
+ end
+ end
end
diff --git a/app/models/work_items/widget_definition.rb b/app/models/work_items/widget_definition.rb
index 5d4414e95d8..9e8c421d740 100644
--- a/app/models/work_items/widget_definition.rb
+++ b/app/models/work_items/widget_definition.rb
@@ -28,7 +28,8 @@ module WorkItems
progress: 10, # EE-only
status: 11, # EE-only
requirement_legacy: 12, # EE-only
- test_reports: 13 # EE-only
+ test_reports: 13, # EE-only
+ notifications: 14
}
def self.available_widgets
diff --git a/app/models/work_items/widgets/notifications.rb b/app/models/work_items/widgets/notifications.rb
new file mode 100644
index 00000000000..9a13e5ebbea
--- /dev/null
+++ b/app/models/work_items/widgets/notifications.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module WorkItems
+ module Widgets
+ class Notifications < Base
+ delegate :subscribed?, to: :work_item
+ end
+ end
+end
diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb
index ba6e760fd2b..c08bc8744ba 100644
--- a/app/policies/group_policy.rb
+++ b/app/policies/group_policy.rb
@@ -159,9 +159,7 @@ class GroupPolicy < Namespaces::GroupProjectNamespaceSharedPolicy
rule { has_access }.enable :read_namespace
rule { developer }.policy do
- enable :create_metrics_dashboard_annotation
- enable :delete_metrics_dashboard_annotation
- enable :update_metrics_dashboard_annotation
+ enable :admin_metrics_dashboard_annotation
enable :create_custom_emoji
enable :create_package
enable :developer_access
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index 2bdd8b23c62..fcb93e44191 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -234,6 +234,10 @@ class ProjectPolicy < BasePolicy
Gitlab.config.packages.enabled
end
+ condition :terraform_state_disabled do
+ !Gitlab.config.terraform_state.enabled
+ end
+
condition(:create_runner_workflow_enabled) do
Feature.enabled?(:create_runner_workflow_for_namespace, project.namespace)
end
@@ -404,13 +408,16 @@ class ProjectPolicy < BasePolicy
end
rule { infrastructure_disabled }.policy do
- prevent(*create_read_update_admin_destroy(:terraform_state))
prevent(*create_read_update_admin_destroy(:cluster))
prevent(:read_pod_logs)
prevent(:read_prometheus)
prevent(:admin_project_google_cloud)
end
+ rule { infrastructure_disabled | terraform_state_disabled }.policy do
+ prevent(*create_read_update_admin_destroy(:terraform_state))
+ end
+
rule { can?(:metrics_dashboard) }.policy do
enable :read_prometheus
enable :read_deployment
@@ -458,9 +465,7 @@ class ProjectPolicy < BasePolicy
enable :create_release
enable :update_release
enable :destroy_release
- enable :create_metrics_dashboard_annotation
- enable :delete_metrics_dashboard_annotation
- enable :update_metrics_dashboard_annotation
+ enable :admin_metrics_dashboard_annotation
enable :read_alert_management_alert
enable :update_alert_management_alert
enable :create_design
diff --git a/app/services/metrics/dashboard/annotations/create_service.rb b/app/services/metrics/dashboard/annotations/create_service.rb
index b86fa82a5e8..47e9afa36b9 100644
--- a/app/services/metrics/dashboard/annotations/create_service.rb
+++ b/app/services/metrics/dashboard/annotations/create_service.rb
@@ -26,7 +26,7 @@ module Metrics
attr_reader :user, :params
def authorize_environment_access(options)
- if environment.nil? || Ability.allowed?(user, :create_metrics_dashboard_annotation, project)
+ if environment.nil? || Ability.allowed?(user, :admin_metrics_dashboard_annotation, project)
options[:environment] = environment
success(options)
else
@@ -35,7 +35,7 @@ module Metrics
end
def authorize_cluster_access(options)
- if cluster.nil? || Ability.allowed?(user, :create_metrics_dashboard_annotation, cluster)
+ if cluster.nil? || Ability.allowed?(user, :admin_metrics_dashboard_annotation, cluster)
options[:cluster] = cluster
success(options)
else
diff --git a/app/services/metrics/dashboard/annotations/delete_service.rb b/app/services/metrics/dashboard/annotations/delete_service.rb
index 3cb22f8d3da..34918c89304 100644
--- a/app/services/metrics/dashboard/annotations/delete_service.rb
+++ b/app/services/metrics/dashboard/annotations/delete_service.rb
@@ -24,7 +24,7 @@ module Metrics
attr_reader :user, :annotation
def authorize_action(_options)
- if Ability.allowed?(user, :delete_metrics_dashboard_annotation, annotation)
+ if Ability.allowed?(user, :admin_metrics_dashboard_annotation, annotation)
success
else
error(s_('MetricsDashboardAnnotation|You are not authorized to delete this annotation'))
diff --git a/app/views/clusters/clusters/_integrations.html.haml b/app/views/clusters/clusters/_integrations.html.haml
index 4a3062def8c..0f62b640b97 100644
--- a/app/views/clusters/clusters/_integrations.html.haml
+++ b/app/views/clusters/clusters/_integrations.html.haml
@@ -6,7 +6,7 @@
- if can?(current_user, :admin_cluster, @cluster)
.sub-section.form-group
= gitlab_ui_form_for @prometheus_integration, as: :integration, namespace: :prometheus, url: @cluster.integrations_path, method: :post, html: { class: 'js-cluster-integrations-form' } do |prometheus_form|
- = prometheus_form.hidden_field :application_type
+ = prometheus_form.hidden_field :application_type, value: @prometheus_integration.application_type
.form-group.gl-form-group
- help_text = s_('ClusterIntegration|Allows GitLab to query a specifically configured in-cluster Prometheus for metrics.')
- help_link = link_to(_('More information.'), help_page_path("user/clusters/integrations", anchor: "prometheus-cluster-integration"), target: '_blank', rel: 'noopener noreferrer')
diff --git a/config/webpack.config.js b/config/webpack.config.js
index 6a40d3e5299..7506b2b494b 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -456,7 +456,6 @@ module.exports = {
},
},
{
- test: /\.(yml|yaml)$/,
resourceQuery: /raw/,
loader: 'raw-loader',
},
diff --git a/db/docs/work_item_types.yml b/db/docs/work_item_types.yml
index 37d2c47de25..9c3da6a1b9d 100644
--- a/db/docs/work_item_types.yml
+++ b/db/docs/work_item_types.yml
@@ -1,6 +1,7 @@
---
table_name: work_item_types
classes:
+- AddNotificationsWorkItemWidget::WorkItemType
- AddWidgetsForWorkItemTypes::WorkItemType
- WorkItems::Type
feature_categories:
diff --git a/db/docs/work_item_widget_definitions.yml b/db/docs/work_item_widget_definitions.yml
index 59cbca14908..a11fbfd34ae 100644
--- a/db/docs/work_item_widget_definitions.yml
+++ b/db/docs/work_item_widget_definitions.yml
@@ -1,6 +1,7 @@
---
table_name: work_item_widget_definitions
classes:
+- AddNotificationsWorkItemWidget::WidgetDefinition
- AddWidgetsForWorkItemTypes::WidgetDefinition
- WorkItems::WidgetDefinition
feature_categories:
diff --git a/db/migrate/20230228142350_add_notifications_work_item_widget.rb b/db/migrate/20230228142350_add_notifications_work_item_widget.rb
new file mode 100644
index 00000000000..1bc89a6875c
--- /dev/null
+++ b/db/migrate/20230228142350_add_notifications_work_item_widget.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+class AddNotificationsWorkItemWidget < Gitlab::Database::Migration[2.1]
+ class WorkItemType < MigrationRecord
+ self.table_name = 'work_item_types'
+ end
+
+ class WidgetDefinition < MigrationRecord
+ self.table_name = 'work_item_widget_definitions'
+ end
+
+ restrict_gitlab_migration gitlab_schema: :gitlab_main
+ disable_ddl_transaction!
+
+ WIDGET_NAME = 'Notifications'
+ WIDGET_ENUM_VALUE = 14
+ WORK_ITEM_TYPES = [
+ 'Issue',
+ 'Incident',
+ 'Test Case',
+ 'Requirement',
+ 'Task',
+ 'Objective',
+ 'Key Result'
+ ].freeze
+
+ def up
+ widgets = []
+
+ WORK_ITEM_TYPES.each do |type_name|
+ type = WorkItemType.find_by_name_and_namespace_id(type_name, nil)
+
+ unless type
+ Gitlab::AppLogger.warn("type #{type_name} is missing, not adding widget")
+
+ next
+ end
+
+ widgets << {
+ work_item_type_id: type.id,
+ name: WIDGET_NAME,
+ widget_type: WIDGET_ENUM_VALUE
+ }
+ end
+
+ return if widgets.empty?
+
+ WidgetDefinition.upsert_all(
+ widgets,
+ unique_by: :index_work_item_widget_definitions_on_default_witype_and_name
+ )
+ end
+
+ def down
+ WidgetDefinition.where(name: WIDGET_NAME).delete_all
+ end
+end
diff --git a/db/schema_migrations/20230228142350 b/db/schema_migrations/20230228142350
new file mode 100644
index 00000000000..cd783d44b2a
--- /dev/null
+++ b/db/schema_migrations/20230228142350
@@ -0,0 +1 @@
+ae4c6d6d477f073981f9f4e431bcb93289cfb54569a3dc982434a2e805c7801b \ No newline at end of file
diff --git a/doc/administration/terraform_state.md b/doc/administration/terraform_state.md
index d3b941bd129..46b5a6c8f90 100644
--- a/doc/administration/terraform_state.md
+++ b/doc/administration/terraform_state.md
@@ -22,9 +22,23 @@ Use [external object storage](https://docs.gitlab.com/charts/advanced/external-o
## Disabling Terraform state
-To disable terraform state site-wide, follow the steps below.
-A GitLab administrator may want to disable Terraform state to reduce disk space or if Terraform is not used in your instance.
-To do so, follow the steps below according to your installation's type.
+You can disable Terraform state across the entire instance. You might want to disable Terraform to reduce disk space,
+or because your instance doesn't use Terraform.
+
+When Terraform state administration is disabled:
+
+- On the left sidebar, you cannot select **Infrastructure > Terraform**.
+- Any CI/CD jobs that access the Terraform state fail with this error:
+
+ ```shell
+ Error refreshing state: HTTP remote state endpoint invalid auth
+ ```
+
+To disable Terraform administration, follow the steps below according to your installation.
+
+Prerequisite:
+
+- You must be an administrator.
**In Omnibus installations:**
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 36f4cfec9d4..6817e43a510 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -22086,6 +22086,17 @@ four standard [pagination arguments](#connection-pagination-arguments):
| ---- | ---- | ----------- |
| <a id="workitemwidgetnotesdiscussionsfilter"></a>`filter` | [`NotesFilterType`](#notesfiltertype) | Type of notes collection: ALL_NOTES, ONLY_COMMENTS, ONLY_ACTIVITY. |
+### `WorkItemWidgetNotifications`
+
+Represents the notifications widget.
+
+#### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="workitemwidgetnotificationssubscribed"></a>`subscribed` | [`Boolean!`](#boolean) | Whether the current user is subscribed to notifications on the work item. |
+| <a id="workitemwidgetnotificationstype"></a>`type` | [`WorkItemWidgetType`](#workitemwidgettype) | Widget type. |
+
### `WorkItemWidgetProgress`
Represents a progress widget.
@@ -24390,6 +24401,7 @@ Type of a work item widget.
| <a id="workitemwidgettypelabels"></a>`LABELS` | Labels widget. |
| <a id="workitemwidgettypemilestone"></a>`MILESTONE` | Milestone widget. |
| <a id="workitemwidgettypenotes"></a>`NOTES` | Notes widget. |
+| <a id="workitemwidgettypenotifications"></a>`NOTIFICATIONS` | Notifications widget. |
| <a id="workitemwidgettypeprogress"></a>`PROGRESS` | Progress widget. |
| <a id="workitemwidgettyperequirement_legacy"></a>`REQUIREMENT_LEGACY` | Requirement Legacy widget. |
| <a id="workitemwidgettypestart_and_due_date"></a>`START_AND_DUE_DATE` | Start And Due Date widget. |
@@ -25751,6 +25763,7 @@ Implementations:
- [`WorkItemWidgetLabels`](#workitemwidgetlabels)
- [`WorkItemWidgetMilestone`](#workitemwidgetmilestone)
- [`WorkItemWidgetNotes`](#workitemwidgetnotes)
+- [`WorkItemWidgetNotifications`](#workitemwidgetnotifications)
- [`WorkItemWidgetProgress`](#workitemwidgetprogress)
- [`WorkItemWidgetRequirementLegacy`](#workitemwidgetrequirementlegacy)
- [`WorkItemWidgetStartAndDueDate`](#workitemwidgetstartandduedate)
diff --git a/jest.config.base.js b/jest.config.base.js
index cc275c3ed0f..5feca808a7a 100644
--- a/jest.config.base.js
+++ b/jest.config.base.js
@@ -188,7 +188,7 @@ module.exports = (path, options = {}) => {
'^.+\\.vue$': '@vue/vue2-jest',
'spec/frontend/editor/schema/ci/yaml_tests/.+\\.(yml|yaml)$':
'./spec/frontend/__helpers__/yaml_transformer.js',
- '^.+\\.(md|zip|png|yml|yaml)$': './spec/frontend/__helpers__/raw_transformer.js',
+ '^.+\\.(md|zip|png|yml|yaml|sh|ps1)$': './spec/frontend/__helpers__/raw_transformer.js',
},
transformIgnorePatterns: [`node_modules/(?!(${transformIgnoreNodeModules.join('|')}))`],
fakeTimers: {
diff --git a/lib/api/metrics/dashboard/annotations.rb b/lib/api/metrics/dashboard/annotations.rb
index 6ba154191be..db667b0291b 100644
--- a/lib/api/metrics/dashboard/annotations.rb
+++ b/lib/api/metrics/dashboard/annotations.rb
@@ -42,7 +42,7 @@ module API
post ':id/metrics_dashboard/annotations' do
annotations_source_object = annotations_source[:class].find(params[:id])
- forbidden! unless can?(current_user, :create_metrics_dashboard_annotation, annotations_source_object)
+ forbidden! unless can?(current_user, :admin_metrics_dashboard_annotation, annotations_source_object)
create_service_params = declared(params).merge(
annotations_source[:create_service_param_key] => annotations_source_object
diff --git a/lib/gitlab/database/background_migration/batched_job.rb b/lib/gitlab/database/background_migration/batched_job.rb
index 6b7ff308c7e..5f922d87c6f 100644
--- a/lib/gitlab/database/background_migration/batched_job.rb
+++ b/lib/gitlab/database/background_migration/batched_job.rb
@@ -100,8 +100,6 @@ module Gitlab
delegate :job_class, :table_name, :column_name, :job_arguments, :job_class_name,
to: :batched_migration, prefix: :migration
- attribute :pause_ms, :integer, default: 100
-
def time_efficiency
return unless succeeded?
return unless finished_at && started_at
diff --git a/lib/gitlab/database/background_migration/batched_migration.rb b/lib/gitlab/database/background_migration/batched_migration.rb
index 61a660ad14c..429dc79e170 100644
--- a/lib/gitlab/database/background_migration/batched_migration.rb
+++ b/lib/gitlab/database/background_migration/batched_migration.rb
@@ -83,8 +83,6 @@ module Gitlab
end
end
- attribute :pause_ms, :integer, default: 100
-
def self.valid_status
state_machine.states.map(&:name)
end
diff --git a/lib/gitlab/database_importers/work_items/base_type_importer.rb b/lib/gitlab/database_importers/work_items/base_type_importer.rb
index 9796a5905e3..85ac816f712 100644
--- a/lib/gitlab/database_importers/work_items/base_type_importer.rb
+++ b/lib/gitlab/database_importers/work_items/base_type_importer.rb
@@ -18,7 +18,8 @@ module Gitlab
progress: 'Progress',
status: 'Status',
requirement_legacy: 'Requirement legacy',
- test_reports: 'Test reports'
+ test_reports: 'Test reports',
+ notifications: 'Notifications'
}.freeze
WIDGETS_FOR_TYPE = {
@@ -32,23 +33,27 @@ module Gitlab
:notes,
:iteration,
:weight,
- :health_status
+ :health_status,
+ :notifications
],
incident: [
:description,
:hierarchy,
- :notes
+ :notes,
+ :notifications
],
test_case: [
:description,
- :notes
+ :notes,
+ :notifications
],
requirement: [
:description,
:notes,
:status,
:requirement_legacy,
- :test_reports
+ :test_reports,
+ :notifications
],
task: [
:assignees,
@@ -59,7 +64,8 @@ module Gitlab
:milestone,
:notes,
:iteration,
- :weight
+ :weight,
+ :notifications
],
objective: [
:assignees,
@@ -69,7 +75,8 @@ module Gitlab
:milestone,
:notes,
:health_status,
- :progress
+ :progress,
+ :notifications
],
key_result: [
:assignees,
@@ -79,7 +86,8 @@ module Gitlab
:start_and_due_date,
:notes,
:health_status,
- :progress
+ :progress,
+ :notifications
]
}.freeze
diff --git a/lib/tasks/gitlab/tw/codeowners.rake b/lib/tasks/gitlab/tw/codeowners.rake
index f5c1942a263..a111cbba46d 100644
--- a/lib/tasks/gitlab/tw/codeowners.rake
+++ b/lib/tasks/gitlab/tw/codeowners.rake
@@ -23,7 +23,6 @@ namespace :tw do
CodeOwnerRule.new('Acquisition', '@phillipwells'),
CodeOwnerRule.new('Anti-Abuse', '@phillipwells'),
CodeOwnerRule.new('Authentication and Authorization', '@jglassman1'),
- CodeOwnerRule.new('Certify', '@msedlakjakubowski'),
CodeOwnerRule.new('Code Review', '@aqualls'),
CodeOwnerRule.new('Compliance', '@eread'),
CodeOwnerRule.new('Composition Analysis', '@rdickenson'),
@@ -49,14 +48,12 @@ namespace :tw do
CodeOwnerRule.new('Integrations', '@ashrafkhamis'),
CodeOwnerRule.new('Knowledge', '@aqualls'),
CodeOwnerRule.new('Application Performance', '@jglassman1'),
- CodeOwnerRule.new('Monitor', '@msedlakjakubowski'),
CodeOwnerRule.new('Observability', '@drcatherinepope'),
CodeOwnerRule.new('Optimize', '@lciutacu'),
CodeOwnerRule.new('Package Registry', '@marcel.amirault'),
CodeOwnerRule.new('Pipeline Authoring', '@marcel.amirault'),
CodeOwnerRule.new('Pipeline Execution', '@drcatherinepope'),
CodeOwnerRule.new('Pipeline Security', '@marcel.amirault'),
- CodeOwnerRule.new('Portfolio Management', '@msedlakjakubowski'),
CodeOwnerRule.new('Product Analytics', '@lciutacu'),
CodeOwnerRule.new('Product Intelligence', '@lciutacu'),
CodeOwnerRule.new('Product Planning', '@msedlakjakubowski'),
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index baa0fc03117..cf5a22b70a4 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -850,6 +850,9 @@ msgstr ""
msgid "%{linkStart} Learn more%{linkEnd}."
msgstr ""
+msgid "%{linkStart}Approval settings%{linkEnd} prevent approvals by its eligible approvers."
+msgstr ""
+
msgid "%{listToShow}, and %{awardsListLength} more"
msgstr ""
@@ -23302,7 +23305,7 @@ msgstr ""
msgid "Invalid repository path"
msgstr ""
-msgid "Invalid rule"
+msgid "Invalid rules are automatically approved to unblock the merge request."
msgstr ""
msgid "Invalid server response"
@@ -23931,6 +23934,9 @@ msgstr ""
msgid "Issue|Title"
msgstr ""
+msgid "It doesn't have any %{linkStart}eligible approvers%{linkEnd}."
+msgstr ""
+
msgid "It looks like you're attempting to activate your subscription. Use %{a_start}the Subscription page%{a_end} instead."
msgstr ""
@@ -35003,6 +35009,16 @@ msgstr ""
msgid "ProtectedBranch|default"
msgstr ""
+msgid "ProtectedEnvironments|%d Approval Rule"
+msgid_plural "ProtectedEnvironments|%d Approval Rules"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ProtectedEnvironments|%d Deployment Rule"
+msgid_plural "ProtectedEnvironments|%d Deployment Rules"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "ProtectedEnvironments|Add approval rules"
msgstr ""
@@ -37278,6 +37294,9 @@ msgstr ""
msgid "Runners|Enter the number of seconds. This timeout takes precedence over lower timeouts set for the project."
msgstr ""
+msgid "Runners|Environment"
+msgstr ""
+
msgid "Runners|Executor"
msgstr ""
@@ -37314,6 +37333,9 @@ msgstr ""
msgid "Runners|If both settings are disabled, new runners cannot be registered."
msgstr ""
+msgid "Runners|Install GitLab Runner"
+msgstr ""
+
msgid "Runners|Install a runner"
msgstr ""
@@ -37595,9 +37617,15 @@ msgstr ""
msgid "Runners|Security or compatibility upgrades are recommended."
msgstr ""
+msgid "Runners|See more %{linkStart}installation methods and architectures%{linkEnd}."
+msgstr ""
+
msgid "Runners|Select all"
msgstr ""
+msgid "Runners|Select platform specifications to install GitLab Runner."
+msgstr ""
+
msgid "Runners|Select projects to assign to this runner"
msgstr ""
@@ -44554,6 +44582,9 @@ msgstr ""
msgid "This repository was last checked %{last_check_timestamp}. The check passed."
msgstr ""
+msgid "This rule is invalid because no one can approve it for one or more of these reasons:"
+msgstr ""
+
msgid "This runner will only run on pipelines triggered on protected branches"
msgstr ""
@@ -48635,6 +48666,9 @@ msgstr ""
msgid "Why are you signing up? (optional)"
msgstr ""
+msgid "Why is this rule invalid?"
+msgstr ""
+
msgid "Wiki"
msgstr ""
@@ -51644,6 +51678,12 @@ msgstr ""
msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage is %{emphasisStart} unchanged %{emphasisEnd} at %{memoryFrom}MB"
msgstr ""
+msgid "mrWidget|%{rules} invalid rule has been approved automatically, as no one can approve it."
+msgstr ""
+
+msgid "mrWidget|%{rules} invalid rules have been approved automatically, as no one can approve them."
+msgstr ""
+
msgid "mrWidget|A merge train is a queued list of merge requests waiting to be merged into the target branch. The changes in each merge request are combined with the changes in earlier merge requests and tested before merge."
msgstr ""
@@ -51671,12 +51711,6 @@ msgstr ""
msgid "mrWidget|Approval password is invalid."
msgstr ""
-msgid "mrWidget|Approval rule %{rules} is invalid. GitLab has approved this rule automatically to unblock the merge request. %{link}"
-msgstr ""
-
-msgid "mrWidget|Approval rules %{rules} are invalid. GitLab has approved these rules automatically to unblock the merge request. %{link}"
-msgstr ""
-
msgid "mrWidget|Approve"
msgstr ""
@@ -51786,9 +51820,6 @@ msgstr ""
msgid "mrWidget|More information"
msgstr ""
-msgid "mrWidget|No users match the rule's criteria."
-msgstr ""
-
msgid "mrWidget|Please restore it or use a different %{type} branch."
msgstr ""
diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock
index a5d8b1b0f35..e6d7cb72bbc 100644
--- a/qa/Gemfile.lock
+++ b/qa/Gemfile.lock
@@ -51,7 +51,7 @@ GEM
chemlab (~> 0.4)
coderay (1.1.2)
colorize (0.8.1)
- concurrent-ruby (1.1.10)
+ concurrent-ruby (1.2.2)
confiner (0.4.0)
gitlab (>= 4.17)
zeitwerk (>= 2.5, < 3)
@@ -102,7 +102,7 @@ GEM
gitlab (4.18.0)
httparty (~> 0.18)
terminal-table (>= 1.5.1)
- gitlab-qa (9.1.1)
+ gitlab-qa (9.1.2)
activesupport (~> 6.1)
gitlab (~> 4.18.0)
http (~> 5.0)
@@ -152,8 +152,8 @@ GEM
http-cookie (1.0.5)
domain_name (~> 0.5)
http-form_data (2.3.0)
- httparty (0.20.0)
- mime-types (~> 3.0)
+ httparty (0.21.0)
+ mini_mime (>= 1.0.0)
multi_xml (>= 0.5.2)
httpclient (2.8.3)
i18n (1.12.0)
@@ -174,10 +174,10 @@ GEM
method_source (1.0.0)
mime-types (3.4.1)
mime-types-data (~> 3.2015)
- mime-types-data (3.2022.0105)
+ mime-types-data (3.2023.0218.1)
mini_mime (1.1.0)
mini_portile2 (2.8.1)
- minitest (5.17.0)
+ minitest (5.18.0)
multi_json (1.15.0)
multi_xml (0.6.0)
netrc (0.11.0)
@@ -204,7 +204,7 @@ GEM
pry-byebug (3.10.1)
byebug (~> 11.0)
pry (>= 0.13, < 0.15)
- public_suffix (5.0.0)
+ public_suffix (5.0.1)
racc (1.6.2)
rack (2.2.3.1)
rack-test (1.1.0)
@@ -275,13 +275,13 @@ GEM
terminal-table (3.0.2)
unicode-display_width (>= 1.1.1, < 3)
trailblazer-option (0.1.2)
- tzinfo (2.0.5)
+ tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
uber (0.1.0)
unf (0.1.4)
unf_ext
unf_ext (0.0.8.2)
- unicode-display_width (2.3.0)
+ unicode-display_width (2.4.2)
unparser (0.6.5)
diff-lcs (~> 1.3)
parser (>= 3.1.0)
diff --git a/scripts/trigger-build.rb b/scripts/trigger-build.rb
index 69eea7488fb..c7c09557ff9 100755
--- a/scripts/trigger-build.rb
+++ b/scripts/trigger-build.rb
@@ -278,6 +278,7 @@ module Trigger
def extra_variables
{
"BRANCH_#{project_slug.upcase}" => ENV['CI_COMMIT_REF_NAME'],
+ "MERGE_REQUEST_IID_#{project_slug.upcase}" => ENV['CI_MERGE_REQUEST_IID'],
"REVIEW_SLUG" => review_slug
}
end
diff --git a/spec/frontend/blob/components/blob_header_viewer_switcher_spec.js b/spec/frontend/blob/components/blob_header_viewer_switcher_spec.js
index 1eac0733646..2ef87f6664b 100644
--- a/spec/frontend/blob/components/blob_header_viewer_switcher_spec.js
+++ b/spec/frontend/blob/components/blob_header_viewer_switcher_spec.js
@@ -18,14 +18,14 @@ describe('Blob Header Viewer Switcher', () => {
});
}
- afterEach(() => {
- wrapper.destroy();
- });
+ const findSimpleViewerButton = () => wrapper.findComponent('[data-viewer="simple"]');
+ const findRichViewerButton = () => wrapper.findComponent('[data-viewer="rich"]');
describe('intiialization', () => {
it('is initialized with simple viewer as active', () => {
createComponent();
- expect(wrapper.vm.value).toBe(SIMPLE_BLOB_VIEWER);
+ expect(findSimpleViewerButton().props('selected')).toBe(true);
+ expect(findRichViewerButton().props('selected')).toBe(false);
});
});
@@ -52,45 +52,34 @@ describe('Blob Header Viewer Switcher', () => {
});
describe('viewer changes', () => {
- let buttons;
- let simpleBtn;
- let richBtn;
+ it('does not switch the viewer if the selected one is already active', async () => {
+ createComponent();
+ expect(findSimpleViewerButton().props('selected')).toBe(true);
- function factory(propsData = {}) {
- createComponent(propsData);
- buttons = wrapper.findAllComponents(GlButton);
- simpleBtn = buttons.at(0);
- richBtn = buttons.at(1);
-
- jest.spyOn(wrapper.vm, '$emit');
- }
-
- it('does not switch the viewer if the selected one is already active', () => {
- factory();
- expect(wrapper.vm.value).toBe(SIMPLE_BLOB_VIEWER);
- simpleBtn.vm.$emit('click');
- expect(wrapper.vm.value).toBe(SIMPLE_BLOB_VIEWER);
- expect(wrapper.vm.$emit).not.toHaveBeenCalled();
+ findSimpleViewerButton().vm.$emit('click');
+ await nextTick();
+
+ expect(findSimpleViewerButton().props('selected')).toBe(true);
+ expect(wrapper.emitted('input')).toBe(undefined);
});
it('emits an event when a Rich Viewer button is clicked', async () => {
- factory();
- expect(wrapper.vm.value).toBe(SIMPLE_BLOB_VIEWER);
-
- richBtn.vm.$emit('click');
+ createComponent();
+ expect(findSimpleViewerButton().props('selected')).toBe(true);
+ findRichViewerButton().vm.$emit('click');
await nextTick();
- expect(wrapper.vm.$emit).toHaveBeenCalledWith('input', RICH_BLOB_VIEWER);
+
+ expect(wrapper.emitted('input')).toEqual([[RICH_BLOB_VIEWER]]);
});
it('emits an event when a Simple Viewer button is clicked', async () => {
- factory({
- value: RICH_BLOB_VIEWER,
- });
- simpleBtn.vm.$emit('click');
+ createComponent({ value: RICH_BLOB_VIEWER });
+ findSimpleViewerButton().vm.$emit('click');
await nextTick();
- expect(wrapper.vm.$emit).toHaveBeenCalledWith('input', SIMPLE_BLOB_VIEWER);
+
+ expect(wrapper.emitted('input')).toEqual([[SIMPLE_BLOB_VIEWER]]);
});
});
});
diff --git a/spec/frontend/ci/runner/admin_register_runner/admin_register_runner_app_spec.js b/spec/frontend/ci/runner/admin_register_runner/admin_register_runner_app_spec.js
index 42ca28eebc7..1ba3d9c317f 100644
--- a/spec/frontend/ci/runner/admin_register_runner/admin_register_runner_app_spec.js
+++ b/spec/frontend/ci/runner/admin_register_runner/admin_register_runner_app_spec.js
@@ -1,4 +1,4 @@
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import { GlButton } from '@gitlab/ui';
@@ -7,12 +7,15 @@ import createMockApollo from 'helpers/mock_apollo_helper';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import setWindowLocation from 'helpers/set_window_location_helper';
+import { TEST_HOST } from 'helpers/test_constants';
import { s__ } from '~/locale';
+import { updateHistory } from '~/lib/utils/url_utility';
import runnerForRegistrationQuery from '~/ci/runner/graphql/register/runner_for_registration.query.graphql';
import { PARAM_KEY_PLATFORM, DEFAULT_PLATFORM, WINDOWS_PLATFORM } from '~/ci/runner/constants';
import AdminRegisterRunnerApp from '~/ci/runner/admin_register_runner/admin_register_runner_app.vue';
import RegistrationInstructions from '~/ci/runner/components/registration/registration_instructions.vue';
+import PlatformsDrawer from '~/ci/runner/components/registration/platforms_drawer.vue';
import { runnerForRegistration } from '../mock_data';
const mockRunner = runnerForRegistration.data.runner;
@@ -22,11 +25,17 @@ const MOCK_TOKEN = 'MOCK_TOKEN';
Vue.use(VueApollo);
+jest.mock('~/lib/utils/url_utility', () => ({
+ ...jest.requireActual('~/lib/utils/url_utility'),
+ updateHistory: jest.fn(),
+}));
+
describe('AdminRegisterRunnerApp', () => {
let wrapper;
let mockRunnerQuery;
const findRegistrationInstructions = () => wrapper.findComponent(RegistrationInstructions);
+ const findPlatformsDrawer = () => wrapper.findComponent(PlatformsDrawer);
const findBtn = () => wrapper.findComponent(GlButton);
const createComponent = () => {
@@ -69,6 +78,13 @@ describe('AdminRegisterRunnerApp', () => {
});
});
+ it('configures platform drawer', () => {
+ expect(findPlatformsDrawer().props()).toEqual({
+ open: false,
+ platform: DEFAULT_PLATFORM,
+ });
+ });
+
it('shows runner list button', () => {
expect(findBtn().attributes('href')).toEqual(mockRunnersPath);
expect(findBtn().props('variant')).toEqual('confirm');
@@ -88,6 +104,52 @@ describe('AdminRegisterRunnerApp', () => {
});
});
+ describe('When opening install instructions', () => {
+ beforeEach(async () => {
+ createComponent();
+ await waitForPromises();
+
+ findRegistrationInstructions().vm.$emit('toggleDrawer');
+ await nextTick();
+ });
+
+ it('opens platform drawer', () => {
+ expect(findPlatformsDrawer().props('open')).toEqual(true);
+ });
+
+ it('closes platform drawer', async () => {
+ findRegistrationInstructions().vm.$emit('toggleDrawer');
+ await nextTick();
+
+ expect(findPlatformsDrawer().props('open')).toEqual(false);
+ });
+
+ it('closes platform drawer from drawer', async () => {
+ findPlatformsDrawer().vm.$emit('close');
+ await nextTick();
+
+ expect(findPlatformsDrawer().props('open')).toEqual(false);
+ });
+
+ describe('when selecting a platform', () => {
+ beforeEach(async () => {
+ findPlatformsDrawer().vm.$emit('selectPlatform', WINDOWS_PLATFORM);
+ await nextTick();
+ });
+
+ it('updates the url', () => {
+ expect(updateHistory).toHaveBeenCalledTimes(1);
+ expect(updateHistory).toHaveBeenCalledWith({
+ url: `${TEST_HOST}/?${PARAM_KEY_PLATFORM}=${WINDOWS_PLATFORM}`,
+ });
+ });
+
+ it('updates the registration instructions', () => {
+ expect(findRegistrationInstructions().props('platform')).toBe(WINDOWS_PLATFORM);
+ });
+ });
+ });
+
describe('When runner is loading', () => {
beforeEach(async () => {
createComponent();
diff --git a/spec/frontend/ci/runner/components/registration/__snapshots__/utils_spec.js.snap b/spec/frontend/ci/runner/components/registration/__snapshots__/utils_spec.js.snap
index c1cc6aa07a2..94b87d31ed0 100644
--- a/spec/frontend/ci/runner/components/registration/__snapshots__/utils_spec.js.snap
+++ b/spec/frontend/ci/runner/components/registration/__snapshots__/utils_spec.js.snap
@@ -1,10 +1,77 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`registration utils for "linux" platform commandPrompt matches snapshot 1`] = `"$"`;
+exports[`registration utils for "linux" platform commandPrompt is correct 1`] = `"$"`;
-exports[`registration utils for "linux" platform commandPrompt matches snapshot 2`] = `"$"`;
+exports[`registration utils for "linux" platform installScript is correct for "386" architecture 1`] = `
+"# Download the binary for your system
+sudo curl -L --output /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-386
-exports[`registration utils for "linux" platform registerCommand matches snapshot 1`] = `
+# Give it permission to execute
+sudo chmod +x /usr/local/bin/gitlab-runner
+
+# Create a GitLab Runner user
+sudo useradd --comment 'GitLab Runner' --create-home gitlab-runner --shell /bin/bash
+
+# Install and run as a service
+sudo gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner
+sudo gitlab-runner start"
+`;
+
+exports[`registration utils for "linux" platform installScript is correct for "amd64" architecture 1`] = `
+"# Download the binary for your system
+sudo curl -L --output /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-amd64
+
+# Give it permission to execute
+sudo chmod +x /usr/local/bin/gitlab-runner
+
+# Create a GitLab Runner user
+sudo useradd --comment 'GitLab Runner' --create-home gitlab-runner --shell /bin/bash
+
+# Install and run as a service
+sudo gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner
+sudo gitlab-runner start"
+`;
+
+exports[`registration utils for "linux" platform installScript is correct for "arm" architecture 1`] = `
+"# Download the binary for your system
+sudo curl -L --output /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-arm
+
+# Give it permission to execute
+sudo chmod +x /usr/local/bin/gitlab-runner
+
+# Create a GitLab Runner user
+sudo useradd --comment 'GitLab Runner' --create-home gitlab-runner --shell /bin/bash
+
+# Install and run as a service
+sudo gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner
+sudo gitlab-runner start"
+`;
+
+exports[`registration utils for "linux" platform installScript is correct for "arm64" architecture 1`] = `
+"# Download the binary for your system
+sudo curl -L --output /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-arm64
+
+# Give it permission to execute
+sudo chmod +x /usr/local/bin/gitlab-runner
+
+# Create a GitLab Runner user
+sudo useradd --comment 'GitLab Runner' --create-home gitlab-runner --shell /bin/bash
+
+# Install and run as a service
+sudo gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner
+sudo gitlab-runner start"
+`;
+
+exports[`registration utils for "linux" platform platformArchitectures returns correct list of architectures 1`] = `
+Array [
+ "amd64",
+ "386",
+ "arm",
+ "arm64",
+]
+`;
+
+exports[`registration utils for "linux" platform registerCommand is correct 1`] = `
Array [
"gitlab-runner register",
" --url http://test.host",
@@ -12,21 +79,53 @@ Array [
]
`;
-exports[`registration utils for "linux" platform registerCommand matches snapshot 2`] = `
+exports[`registration utils for "linux" platform registerCommand is correct 2`] = `
Array [
"gitlab-runner register",
" --url http://test.host",
- " --registration-token REGISTRATION_TOKEN",
]
`;
-exports[`registration utils for "linux" platform runCommand matches snapshot 1`] = `"gitlab-runner run"`;
+exports[`registration utils for "linux" platform runCommand is correct 1`] = `"gitlab-runner run"`;
+
+exports[`registration utils for "osx" platform commandPrompt is correct 1`] = `"$"`;
+
+exports[`registration utils for "osx" platform installScript is correct for "amd64" architecture 1`] = `
+"# Download the binary for your system
+sudo curl --output /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-darwin-amd64
+
+# Give it permission to execute
+sudo chmod +x /usr/local/bin/gitlab-runner
+
+# The rest of the commands execute as the user who will run the runner
+# Register the runner (steps below), then run
+cd ~
+gitlab-runner install
+gitlab-runner start"
+`;
+
+exports[`registration utils for "osx" platform installScript is correct for "arm64" architecture 1`] = `
+"# Download the binary for your system
+sudo curl --output /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-darwin-arm64
+
+# Give it permission to execute
+sudo chmod +x /usr/local/bin/gitlab-runner
-exports[`registration utils for "linux" platform runCommand matches snapshot 2`] = `"gitlab-runner run"`;
+# The rest of the commands execute as the user who will run the runner
+# Register the runner (steps below), then run
+cd ~
+gitlab-runner install
+gitlab-runner start"
+`;
-exports[`registration utils for "null" platform commandPrompt matches snapshot 1`] = `"$"`;
+exports[`registration utils for "osx" platform platformArchitectures returns correct list of architectures 1`] = `
+Array [
+ "amd64",
+ "arm64",
+]
+`;
-exports[`registration utils for "null" platform registerCommand matches snapshot 1`] = `
+exports[`registration utils for "osx" platform registerCommand is correct 1`] = `
Array [
"gitlab-runner register",
" --url http://test.host",
@@ -34,23 +133,57 @@ Array [
]
`;
-exports[`registration utils for "null" platform runCommand matches snapshot 1`] = `"gitlab-runner run"`;
-
-exports[`registration utils for "osx" platform commandPrompt matches snapshot 1`] = `"$"`;
-
-exports[`registration utils for "osx" platform registerCommand matches snapshot 1`] = `
+exports[`registration utils for "osx" platform registerCommand is correct 2`] = `
Array [
"gitlab-runner register",
" --url http://test.host",
- " --registration-token REGISTRATION_TOKEN",
]
`;
-exports[`registration utils for "osx" platform runCommand matches snapshot 1`] = `"gitlab-runner run"`;
+exports[`registration utils for "osx" platform runCommand is correct 1`] = `"gitlab-runner run"`;
+
+exports[`registration utils for "windows" platform commandPrompt is correct 1`] = `">"`;
+
+exports[`registration utils for "windows" platform installScript is correct for "386" architecture 1`] = `
+"# Run PowerShell: https://docs.microsoft.com/en-us/powershell/scripting/windows-powershell/starting-windows-powershell?view=powershell-7#with-administrative-privileges-run-as-administrator
+# Create a folder somewhere on your system, for example: C:\\\\GitLab-Runner
+New-Item -Path 'C:\\\\GitLab-Runner' -ItemType Directory
+
+# Change to the folder
+cd 'C:\\\\GitLab-Runner'
+
+# Download binary
+Invoke-WebRequest -Uri \\"https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-windows-386.exe\\" -OutFile \\"gitlab-runner.exe\\"
+
+# Register the runner (steps below), then run
+.\\\\gitlab-runner.exe install
+.\\\\gitlab-runner.exe start"
+`;
-exports[`registration utils for "windows" platform commandPrompt matches snapshot 1`] = `">"`;
+exports[`registration utils for "windows" platform installScript is correct for "amd64" architecture 1`] = `
+"# Run PowerShell: https://docs.microsoft.com/en-us/powershell/scripting/windows-powershell/starting-windows-powershell?view=powershell-7#with-administrative-privileges-run-as-administrator
+# Create a folder somewhere on your system, for example: C:\\\\GitLab-Runner
+New-Item -Path 'C:\\\\GitLab-Runner' -ItemType Directory
-exports[`registration utils for "windows" platform registerCommand matches snapshot 1`] = `
+# Change to the folder
+cd 'C:\\\\GitLab-Runner'
+
+# Download binary
+Invoke-WebRequest -Uri \\"https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-windows-amd64.exe\\" -OutFile \\"gitlab-runner.exe\\"
+
+# Register the runner (steps below), then run
+.\\\\gitlab-runner.exe install
+.\\\\gitlab-runner.exe start"
+`;
+
+exports[`registration utils for "windows" platform platformArchitectures returns correct list of architectures 1`] = `
+Array [
+ "amd64",
+ "386",
+]
+`;
+
+exports[`registration utils for "windows" platform registerCommand is correct 1`] = `
Array [
".\\\\gitlab-runner.exe register",
" --url http://test.host",
@@ -58,4 +191,11 @@ Array [
]
`;
-exports[`registration utils for "windows" platform runCommand matches snapshot 1`] = `".\\\\gitlab-runner.exe run"`;
+exports[`registration utils for "windows" platform registerCommand is correct 2`] = `
+Array [
+ ".\\\\gitlab-runner.exe register",
+ " --url http://test.host",
+]
+`;
+
+exports[`registration utils for "windows" platform runCommand is correct 1`] = `".\\\\gitlab-runner.exe run"`;
diff --git a/spec/frontend/ci/runner/components/registration/platforms_drawer_spec.js b/spec/frontend/ci/runner/components/registration/platforms_drawer_spec.js
new file mode 100644
index 00000000000..c1879efd45a
--- /dev/null
+++ b/spec/frontend/ci/runner/components/registration/platforms_drawer_spec.js
@@ -0,0 +1,92 @@
+import { nextTick } from 'vue';
+import { GlDrawer } from '@gitlab/ui';
+import { s__ } from '~/locale';
+import { shallowMountExtended, mountExtended } from 'helpers/vue_test_utils_helper';
+
+import PlatformsDrawer from '~/ci/runner/components/registration/platforms_drawer.vue';
+import CliCommand from '~/ci/runner/components/registration/cli_command.vue';
+import { LINUX_PLATFORM, MACOS_PLATFORM, WINDOWS_PLATFORM } from '~/ci/runner/constants';
+import { installScript, platformArchitectures } from '~/ci/runner/components/registration/utils';
+
+const MOCK_WRAPPER_HEIGHT = '99px';
+const LINUX_ARCHS = platformArchitectures({ platform: LINUX_PLATFORM });
+const MACOS_ARCHS = platformArchitectures({ platform: MACOS_PLATFORM });
+
+jest.mock('~/lib/utils/dom_utils', () => ({
+ getContentWrapperHeight: () => MOCK_WRAPPER_HEIGHT,
+}));
+
+describe('RegistrationInstructions', () => {
+ let wrapper;
+
+ const findDrawer = () => wrapper.findComponent(GlDrawer);
+ const findEnvironmentOptions = () =>
+ wrapper.findByLabelText(s__('Runners|Environment')).findAll('option');
+ const findArchitectureOptions = () =>
+ wrapper.findByLabelText(s__('Runners|Architecture')).findAll('option');
+ const findCliCommand = () => wrapper.findComponent(CliCommand);
+
+ const createComponent = ({ props = {}, mountFn = shallowMountExtended } = {}) => {
+ wrapper = mountFn(PlatformsDrawer, {
+ propsData: {
+ open: true,
+ ...props,
+ },
+ });
+ };
+
+ it('shows drawer', () => {
+ createComponent();
+
+ expect(findDrawer().props()).toMatchObject({
+ open: true,
+ headerHeight: MOCK_WRAPPER_HEIGHT,
+ });
+ });
+
+ it('closes drawer', () => {
+ createComponent();
+ findDrawer().vm.$emit('close');
+
+ expect(wrapper.emitted('close')).toHaveLength(1);
+ });
+
+ it('shows selection options', () => {
+ createComponent({ mountFn: mountExtended });
+
+ expect(findEnvironmentOptions().wrappers.map((w) => w.attributes('value'))).toEqual([
+ LINUX_PLATFORM,
+ MACOS_PLATFORM,
+ WINDOWS_PLATFORM,
+ ]);
+
+ expect(findArchitectureOptions().wrappers.map((w) => w.attributes('value'))).toEqual(
+ LINUX_ARCHS,
+ );
+ });
+
+ it('shows script', () => {
+ createComponent();
+
+ expect(findCliCommand().props('command')).toBe(
+ installScript({ platform: LINUX_PLATFORM, architecture: LINUX_ARCHS[0] }),
+ );
+ });
+
+ it('shows selection options for another platform', async () => {
+ createComponent({ mountFn: mountExtended });
+
+ findEnvironmentOptions().at(1).setSelected(); // macos
+ await nextTick();
+
+ expect(wrapper.emitted('selectPlatform')).toEqual([[MACOS_PLATFORM]]);
+
+ expect(findArchitectureOptions().wrappers.map((w) => w.attributes('value'))).toEqual(
+ MACOS_ARCHS,
+ );
+
+ expect(findCliCommand().props('command')).toBe(
+ installScript({ platform: MACOS_PLATFORM, architecture: MACOS_ARCHS[0] }),
+ );
+ });
+});
diff --git a/spec/frontend/ci/runner/components/registration/registration_instructions_spec.js b/spec/frontend/ci/runner/components/registration/registration_instructions_spec.js
index 858b0732782..f81f96ceffb 100644
--- a/spec/frontend/ci/runner/components/registration/registration_instructions_spec.js
+++ b/spec/frontend/ci/runner/components/registration/registration_instructions_spec.js
@@ -1,11 +1,15 @@
-import { GlSprintf, GlLink, GlSkeletonLoader } from '@gitlab/ui';
+import { GlSprintf, GlSkeletonLoader } from '@gitlab/ui';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
-import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import { extendedWrapper, shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { TEST_HOST } from 'helpers/test_constants';
import RegistrationInstructions from '~/ci/runner/components/registration/registration_instructions.vue';
import CliCommand from '~/ci/runner/components/registration/cli_command.vue';
-import { DEFAULT_PLATFORM, INSTALL_HELP_URL, EXECUTORS_HELP_URL } from '~/ci/runner/constants';
+import {
+ DEFAULT_PLATFORM,
+ EXECUTORS_HELP_URL,
+ SERVICE_COMMANDS_HELP_URL,
+} from '~/ci/runner/constants';
const REGISTRATION_TOKEN = 'REGISTRATION_TOKEN';
const DUMMY_GON = {
@@ -16,9 +20,8 @@ describe('RegistrationInstructions', () => {
let wrapper;
let originalGon;
- const findStepAt = (i) => wrapper.findAll('section').at(i);
- const findLink = (href, container = wrapper) =>
- container.findAllComponents(GlLink).filter((w) => w.attributes('href') === href);
+ const findStepAt = (i) => extendedWrapper(wrapper.findAll('section').at(i));
+ const findByText = (text, container = wrapper) => container.findByText(text);
const createComponent = (props) => {
wrapper = shallowMountExtended(RegistrationInstructions, {
@@ -47,7 +50,9 @@ describe('RegistrationInstructions', () => {
});
it('renders legacy instructions', () => {
- expect(findLink(INSTALL_HELP_URL).exists()).toBe(true);
+ findByText('How do I install GitLab Runner?').vm.$emit('click');
+
+ expect(wrapper.emitted('toggleDrawer')).toHaveLength(1);
});
it('renders step 1', () => {
@@ -80,7 +85,9 @@ describe('RegistrationInstructions', () => {
it('renders step 2', () => {
const step2 = findStepAt(1);
- expect(findLink(EXECUTORS_HELP_URL, step2).exists()).toBe(true);
+ expect(findByText('Not sure which one to select?', step2).attributes('href')).toBe(
+ EXECUTORS_HELP_URL,
+ );
});
it('renders step 3', () => {
@@ -90,5 +97,9 @@ describe('RegistrationInstructions', () => {
command: 'gitlab-runner run',
prompt: '$',
});
+
+ expect(findByText('system or user service', step3).attributes('href')).toBe(
+ SERVICE_COMMANDS_HELP_URL,
+ );
});
});
diff --git a/spec/frontend/ci/runner/components/registration/utils_spec.js b/spec/frontend/ci/runner/components/registration/utils_spec.js
index aeb489ca7de..6c4bf65eaa6 100644
--- a/spec/frontend/ci/runner/components/registration/utils_spec.js
+++ b/spec/frontend/ci/runner/components/registration/utils_spec.js
@@ -10,6 +10,8 @@ import {
commandPrompt,
registerCommand,
runCommand,
+ installScript,
+ platformArchitectures,
} from '~/ci/runner/components/registration/utils';
const REGISTRATION_TOKEN = 'REGISTRATION_TOKEN';
@@ -29,24 +31,69 @@ describe('registration utils', () => {
window.gon = originalGon;
});
- describe.each([DEFAULT_PLATFORM, LINUX_PLATFORM, MACOS_PLATFORM, WINDOWS_PLATFORM, null])(
+ describe.each([LINUX_PLATFORM, MACOS_PLATFORM, WINDOWS_PLATFORM])(
'for "%s" platform',
(platform) => {
- describe('commandPrompt', () => {
- it('matches snapshot', () => {
- expect(commandPrompt({ platform })).toMatchSnapshot();
- });
+ it('commandPrompt is correct', () => {
+ expect(commandPrompt({ platform })).toMatchSnapshot();
+ });
+ it('registerCommand is correct', () => {
+ expect(
+ registerCommand({ platform, registrationToken: REGISTRATION_TOKEN }),
+ ).toMatchSnapshot();
+
+ expect(registerCommand({ platform })).toMatchSnapshot();
+ });
+ it('runCommand is correct', () => {
+ expect(runCommand({ platform })).toMatchSnapshot();
+ });
+ },
+ );
+
+ describe('for missing platform', () => {
+ it('commandPrompt uses the default', () => {
+ const expected = commandPrompt({ platform: DEFAULT_PLATFORM });
+
+ expect(commandPrompt({ platform: null })).toEqual(expected);
+ expect(commandPrompt({ platform: undefined })).toEqual(expected);
+ });
+
+ it('registerCommand uses the default', () => {
+ const expected = registerCommand({
+ platform: DEFAULT_PLATFORM,
+ registrationToken: REGISTRATION_TOKEN,
});
- describe('registerCommand', () => {
- it('matches snapshot', () => {
- expect(
- registerCommand({ platform, registrationToken: REGISTRATION_TOKEN }),
- ).toMatchSnapshot();
+
+ expect(registerCommand({ platform: null, registrationToken: REGISTRATION_TOKEN })).toEqual(
+ expected,
+ );
+ expect(
+ registerCommand({ platform: undefined, registrationToken: REGISTRATION_TOKEN }),
+ ).toEqual(expected);
+ });
+
+ it('runCommand uses the default', () => {
+ const expected = runCommand({ platform: DEFAULT_PLATFORM });
+
+ expect(runCommand({ platform: null })).toEqual(expected);
+ expect(runCommand({ platform: undefined })).toEqual(expected);
+ });
+ });
+
+ describe.each([LINUX_PLATFORM, MACOS_PLATFORM, WINDOWS_PLATFORM])(
+ 'for "%s" platform',
+ (platform) => {
+ describe('platformArchitectures', () => {
+ it('returns correct list of architectures', () => {
+ expect(platformArchitectures({ platform })).toMatchSnapshot();
});
});
- describe('runCommand', () => {
- it('matches snapshot', () => {
- expect(runCommand({ platform })).toMatchSnapshot();
+
+ describe('installScript', () => {
+ const architectures = platformArchitectures({ platform });
+
+ it.each(architectures)('is correct for "%s" architecture', (architecture) => {
+ expect(installScript({ platform, architecture })).toMatchSnapshot();
});
});
},
diff --git a/spec/frontend/monitoring/components/variables/text_field_spec.js b/spec/frontend/monitoring/components/variables/text_field_spec.js
index 3073b3968aa..20e1937c5ac 100644
--- a/spec/frontend/monitoring/components/variables/text_field_spec.js
+++ b/spec/frontend/monitoring/components/variables/text_field_spec.js
@@ -33,25 +33,23 @@ describe('Text variable component', () => {
it('triggers keyup enter', async () => {
createShallowWrapper();
- jest.spyOn(wrapper.vm, '$emit');
findInput().element.value = 'prod-pod';
findInput().trigger('input');
findInput().trigger('keyup.enter');
await nextTick();
- expect(wrapper.vm.$emit).toHaveBeenCalledWith('input', 'prod-pod');
+ expect(wrapper.emitted('input')).toEqual([['prod-pod']]);
});
it('triggers blur enter', async () => {
createShallowWrapper();
- jest.spyOn(wrapper.vm, '$emit');
findInput().element.value = 'canary-pod';
findInput().trigger('input');
findInput().trigger('blur');
await nextTick();
- expect(wrapper.vm.$emit).toHaveBeenCalledWith('input', 'canary-pod');
+ expect(wrapper.emitted('input')).toEqual([['canary-pod']]);
});
});
diff --git a/spec/frontend/vue_shared/issuable/show/components/issuable_title_spec.js b/spec/frontend/vue_shared/issuable/show/components/issuable_title_spec.js
index 619d0cb5dd3..39316dfa249 100644
--- a/spec/frontend/vue_shared/issuable/show/components/issuable_title_spec.js
+++ b/spec/frontend/vue_shared/issuable/show/components/issuable_title_spec.js
@@ -29,28 +29,28 @@ const createComponent = (propsData = issuableTitleProps) =>
describe('IssuableTitle', () => {
let wrapper;
+ const findStickyHeader = () => wrapper.findComponent('[data-testid="header"]');
+
beforeEach(() => {
wrapper = createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('methods', () => {
describe('handleTitleAppear', () => {
- it('sets value of `stickyTitleVisible` prop to false', () => {
+ it('sets value of `stickyTitleVisible` prop to false', async () => {
wrapper.findComponent(GlIntersectionObserver).vm.$emit('appear');
+ await nextTick();
- expect(wrapper.vm.stickyTitleVisible).toBe(false);
+ expect(findStickyHeader().exists()).toBe(false);
});
});
describe('handleTitleDisappear', () => {
- it('sets value of `stickyTitleVisible` prop to true', () => {
+ it('sets value of `stickyTitleVisible` prop to true', async () => {
wrapper.findComponent(GlIntersectionObserver).vm.$emit('disappear');
+ await nextTick();
- expect(wrapper.vm.stickyTitleVisible).toBe(true);
+ expect(findStickyHeader().exists()).toBe(true);
});
});
});
@@ -87,14 +87,10 @@ describe('IssuableTitle', () => {
});
it('renders sticky header when `stickyTitleVisible` prop is true', async () => {
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({
- stickyTitleVisible: true,
- });
-
+ wrapper.findComponent(GlIntersectionObserver).vm.$emit('disappear');
await nextTick();
- const stickyHeaderEl = wrapper.find('[data-testid="header"]');
+
+ const stickyHeaderEl = findStickyHeader();
expect(stickyHeaderEl.exists()).toBe(true);
expect(stickyHeaderEl.findComponent(GlBadge).props('variant')).toBe('success');
diff --git a/spec/graphql/types/work_items/widget_interface_spec.rb b/spec/graphql/types/work_items/widget_interface_spec.rb
index a2b12ed52dc..d1dcfb961cb 100644
--- a/spec/graphql/types/work_items/widget_interface_spec.rb
+++ b/spec/graphql/types/work_items/widget_interface_spec.rb
@@ -15,11 +15,12 @@ RSpec.describe Types::WorkItems::WidgetInterface do
using RSpec::Parameterized::TableSyntax
where(:widget_class, :widget_type_name) do
- WorkItems::Widgets::Description | Types::WorkItems::Widgets::DescriptionType
- WorkItems::Widgets::Hierarchy | Types::WorkItems::Widgets::HierarchyType
- WorkItems::Widgets::Assignees | Types::WorkItems::Widgets::AssigneesType
- WorkItems::Widgets::Labels | Types::WorkItems::Widgets::LabelsType
- WorkItems::Widgets::Notes | Types::WorkItems::Widgets::NotesType
+ WorkItems::Widgets::Description | Types::WorkItems::Widgets::DescriptionType
+ WorkItems::Widgets::Hierarchy | Types::WorkItems::Widgets::HierarchyType
+ WorkItems::Widgets::Assignees | Types::WorkItems::Widgets::AssigneesType
+ WorkItems::Widgets::Labels | Types::WorkItems::Widgets::LabelsType
+ WorkItems::Widgets::Notes | Types::WorkItems::Widgets::NotesType
+ WorkItems::Widgets::Notifications | Types::WorkItems::Widgets::NotificationsType
end
with_them do
diff --git a/spec/graphql/types/work_items/widgets/notifications_type_spec.rb b/spec/graphql/types/work_items/widgets/notifications_type_spec.rb
new file mode 100644
index 00000000000..4f457a24710
--- /dev/null
+++ b/spec/graphql/types/work_items/widgets/notifications_type_spec.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Types::WorkItems::Widgets::NotificationsType, feature_category: :team_planning do
+ it 'exposes the expected fields' do
+ expected_fields = %i[subscribed type]
+
+ expect(described_class.graphql_name).to eq('WorkItemWidgetNotifications')
+ expect(described_class).to have_graphql_fields(*expected_fields)
+ end
+end
diff --git a/spec/lib/sidebars/projects/menus/infrastructure_menu_spec.rb b/spec/lib/sidebars/projects/menus/infrastructure_menu_spec.rb
index a5031ff6a82..dd04d63bcc0 100644
--- a/spec/lib/sidebars/projects/menus/infrastructure_menu_spec.rb
+++ b/spec/lib/sidebars/projects/menus/infrastructure_menu_spec.rb
@@ -107,6 +107,22 @@ RSpec.describe Sidebars::Projects::Menus::InfrastructureMenu, feature_category:
let(:item_id) { :terraform }
it_behaves_like 'access rights checks'
+
+ context 'if terraform_state.enabled=true' do
+ before do
+ stub_config(terraform_state: { enabled: true })
+ end
+
+ it_behaves_like 'access rights checks'
+ end
+
+ context 'if terraform_state.enabled=false' do
+ before do
+ stub_config(terraform_state: { enabled: false })
+ end
+
+ it { is_expected.to be_nil }
+ end
end
describe 'Google Cloud' do
diff --git a/spec/migrations/20230228142350_add_notifications_work_item_widget_spec.rb b/spec/migrations/20230228142350_add_notifications_work_item_widget_spec.rb
new file mode 100644
index 00000000000..065b6d00ddb
--- /dev/null
+++ b/spec/migrations/20230228142350_add_notifications_work_item_widget_spec.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe AddNotificationsWorkItemWidget, :migration, feature_category: :team_planning do
+ let(:migration) { described_class.new }
+ let(:work_item_definitions) { table(:work_item_widget_definitions) }
+
+ describe '#up' do
+ it 'creates notifications widget definition in all types' do
+ work_item_definitions.where(name: 'Notifications').delete_all
+
+ expect { migrate! }.to change { work_item_definitions.count }.by(7)
+ expect(work_item_definitions.all.pluck(:name)).to include('Notifications')
+ end
+ end
+
+ describe '#down' do
+ it 'removes definitions for notifications widget' do
+ migrate!
+
+ expect { migration.down }.to change { work_item_definitions.count }.by(-7)
+ expect(work_item_definitions.all.pluck(:name)).not_to include('Notifications')
+ end
+ end
+end
diff --git a/spec/models/concerns/subscribable_spec.rb b/spec/models/concerns/subscribable_spec.rb
index a60a0a5e26d..80060802de8 100644
--- a/spec/models/concerns/subscribable_spec.rb
+++ b/spec/models/concerns/subscribable_spec.rb
@@ -215,5 +215,31 @@ RSpec.describe Subscribable, 'Subscribable' do
expect(lazy_queries.count).to eq(0)
expect(preloaded_queries.count).to eq(1)
end
+
+ context 'with work items' do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:work_item) { create(:work_item, :task, project: project) }
+ let_it_be(:issue) { create(:issue, project: project) }
+
+ before do
+ [issue, work_item].each do |item|
+ create(:subscription, user: user, subscribable: item, subscribed: true, project: project)
+ end
+ end
+
+ it 'loads correct subscribable type' do
+ expect(issue).to receive(:subscribable_type).and_return('Issue')
+ issue.lazy_subscription(user, project)
+
+ expect(work_item).to receive(:subscribable_type).and_return('Issue')
+ work_item.lazy_subscription(user, project)
+ end
+
+ it 'matches existing subscription type' do
+ expect(issue.subscribed?(user, project)).to eq(true)
+ expect(work_item.subscribed?(user, project)).to eq(true)
+ end
+ end
end
end
diff --git a/spec/models/work_items/widget_definition_spec.rb b/spec/models/work_items/widget_definition_spec.rb
index 08f8f4d9663..3a4670c996f 100644
--- a/spec/models/work_items/widget_definition_spec.rb
+++ b/spec/models/work_items/widget_definition_spec.rb
@@ -11,7 +11,8 @@ RSpec.describe WorkItems::WidgetDefinition, feature_category: :team_planning do
::WorkItems::Widgets::Assignees,
::WorkItems::Widgets::StartAndDueDate,
::WorkItems::Widgets::Milestone,
- ::WorkItems::Widgets::Notes
+ ::WorkItems::Widgets::Notes,
+ ::WorkItems::Widgets::Notifications
]
if Gitlab.ee?
diff --git a/spec/models/work_items/widgets/notifications_spec.rb b/spec/models/work_items/widgets/notifications_spec.rb
new file mode 100644
index 00000000000..2942c149660
--- /dev/null
+++ b/spec/models/work_items/widgets/notifications_spec.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe WorkItems::Widgets::Notifications, feature_category: :team_planning do
+ let_it_be(:work_item) { create(:work_item) }
+
+ describe '.type' do
+ it { expect(described_class.type).to eq(:notifications) }
+ end
+
+ describe '#type' do
+ it { expect(described_class.new(work_item).type).to eq(:notifications) }
+ end
+
+ describe '#subscribed?' do
+ it { expect(described_class.new(work_item).subscribed?(work_item.author, work_item.project)).to eq(true) }
+ end
+end
diff --git a/spec/policies/metrics/dashboard/annotation_policy_spec.rb b/spec/policies/metrics/dashboard/annotation_policy_spec.rb
index 9ea9f843f2c..2d1ef0ee0cb 100644
--- a/spec/policies/metrics/dashboard/annotation_policy_spec.rb
+++ b/spec/policies/metrics/dashboard/annotation_policy_spec.rb
@@ -14,9 +14,7 @@ RSpec.describe Metrics::Dashboard::AnnotationPolicy, :models do
end
it { expect(policy).to be_disallowed :read_metrics_dashboard_annotation }
- it { expect(policy).to be_disallowed :create_metrics_dashboard_annotation }
- it { expect(policy).to be_disallowed :update_metrics_dashboard_annotation }
- it { expect(policy).to be_disallowed :delete_metrics_dashboard_annotation }
+ it { expect(policy).to be_disallowed :admin_metrics_dashboard_annotation }
end
context 'when reporter' do
@@ -25,9 +23,7 @@ RSpec.describe Metrics::Dashboard::AnnotationPolicy, :models do
end
it { expect(policy).to be_allowed :read_metrics_dashboard_annotation }
- it { expect(policy).to be_disallowed :create_metrics_dashboard_annotation }
- it { expect(policy).to be_disallowed :update_metrics_dashboard_annotation }
- it { expect(policy).to be_disallowed :delete_metrics_dashboard_annotation }
+ it { expect(policy).to be_disallowed :admin_metrics_dashboard_annotation }
end
context 'when developer' do
@@ -36,9 +32,7 @@ RSpec.describe Metrics::Dashboard::AnnotationPolicy, :models do
end
it { expect(policy).to be_allowed :read_metrics_dashboard_annotation }
- it { expect(policy).to be_allowed :create_metrics_dashboard_annotation }
- it { expect(policy).to be_allowed :update_metrics_dashboard_annotation }
- it { expect(policy).to be_allowed :delete_metrics_dashboard_annotation }
+ it { expect(policy).to be_allowed :admin_metrics_dashboard_annotation }
end
context 'when maintainer' do
@@ -47,9 +41,7 @@ RSpec.describe Metrics::Dashboard::AnnotationPolicy, :models do
end
it { expect(policy).to be_allowed :read_metrics_dashboard_annotation }
- it { expect(policy).to be_allowed :create_metrics_dashboard_annotation }
- it { expect(policy).to be_allowed :update_metrics_dashboard_annotation }
- it { expect(policy).to be_allowed :delete_metrics_dashboard_annotation }
+ it { expect(policy).to be_allowed :admin_metrics_dashboard_annotation }
end
end
diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb
index e7b548b8f3b..38c487f3c36 100644
--- a/spec/policies/project_policy_spec.rb
+++ b/spec/policies/project_policy_spec.rb
@@ -2275,6 +2275,12 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do
describe 'infrastructure feature' do
using RSpec::Parameterized::TableSyntax
+ before do
+ # assuming the default setting terraform_state.enabled=true
+ # the terraform_state permissions should follow the same logic as the other features
+ stub_config(terraform_state: { enabled: true })
+ end
+
let(:guest_permissions) { [] }
let(:developer_permissions) do
@@ -2338,6 +2344,31 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do
end
end
end
+
+ context 'when terraform state management is disabled' do
+ before do
+ stub_config(terraform_state: { enabled: false })
+ end
+
+ with_them do
+ let(:current_user) { user_subject(role) }
+ let(:project) { project_subject(project_visibility) }
+
+ let(:developer_permissions) do
+ [:read_terraform_state]
+ end
+
+ let(:maintainer_permissions) do
+ developer_permissions + [:admin_terraform_state]
+ end
+
+ it 'always disallows the terraform_state feature' do
+ project.project_feature.update!(infrastructure_access_level: access_level)
+
+ expect_disallowed(*permissions_abilities(role))
+ end
+ end
+ end
end
describe 'access_security_and_compliance' do
diff --git a/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/create_spec.rb b/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/create_spec.rb
index bce57b47aab..3c7f4a030f9 100644
--- a/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/create_spec.rb
+++ b/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/create_spec.rb
@@ -19,7 +19,7 @@ RSpec.describe Mutations::Metrics::Dashboard::Annotations::Create, feature_categ
graphql_mutation_response(:create_annotation)
end
- specify { expect(described_class).to require_graphql_authorizations(:create_metrics_dashboard_annotation) }
+ specify { expect(described_class).to require_graphql_authorizations(:admin_metrics_dashboard_annotation) }
context 'when annotation source is environment' do
let(:mutation) do
diff --git a/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/delete_spec.rb b/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/delete_spec.rb
index f505dc25dc0..c104138b725 100644
--- a/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/delete_spec.rb
+++ b/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/delete_spec.rb
@@ -17,7 +17,7 @@ RSpec.describe Mutations::Metrics::Dashboard::Annotations::Delete, feature_categ
graphql_mutation_response(:delete_annotation)
end
- specify { expect(described_class).to require_graphql_authorizations(:delete_metrics_dashboard_annotation) }
+ specify { expect(described_class).to require_graphql_authorizations(:admin_metrics_dashboard_annotation) }
context 'when the user has permission to delete the annotation' do
before do
diff --git a/spec/requests/api/graphql/project/work_items_spec.rb b/spec/requests/api/graphql/project/work_items_spec.rb
index f49165a88ea..d5dd12de63e 100644
--- a/spec/requests/api/graphql/project/work_items_spec.rb
+++ b/spec/requests/api/graphql/project/work_items_spec.rb
@@ -313,6 +313,34 @@ RSpec.describe 'getting a work item list for a project', feature_category: :team
end
end
+ context 'when fetching work item notifications widget' do
+ let(:fields) do
+ <<~GRAPHQL
+ nodes {
+ widgets {
+ type
+ ... on WorkItemWidgetNotifications {
+ subscribed
+ }
+ }
+ }
+ GRAPHQL
+ end
+
+ it 'executes limited number of N+1 queries', :use_sql_query_cache do
+ control = ActiveRecord::QueryRecorder.new(skip_cached: false) do
+ post_graphql(query, current_user: current_user)
+ end
+
+ create_list(:work_item, 3, project: project)
+
+ # Performs 1 extra query per item to fetch subscriptions
+ expect { post_graphql(query, current_user: current_user) }
+ .not_to exceed_all_query_limit(control).with_threshold(3)
+ expect_graphql_errors_to_be_empty
+ end
+ end
+
def item_ids
graphql_dig_at(items_data, :node, :id)
end
diff --git a/spec/requests/api/graphql/work_item_spec.rb b/spec/requests/api/graphql/work_item_spec.rb
index 0fad4f4ff3a..24c72a8bb00 100644
--- a/spec/requests/api/graphql/work_item_spec.rb
+++ b/spec/requests/api/graphql/work_item_spec.rb
@@ -373,6 +373,32 @@ RSpec.describe 'Query.work_item(id)', feature_category: :team_planning do
)
end
end
+
+ describe 'notifications widget' do
+ let(:work_item_fields) do
+ <<~GRAPHQL
+ id
+ widgets {
+ type
+ ... on WorkItemWidgetNotifications {
+ subscribed
+ }
+ }
+ GRAPHQL
+ end
+
+ it 'returns widget information' do
+ expect(work_item_data).to include(
+ 'id' => work_item.to_gid.to_s,
+ 'widgets' => include(
+ hash_including(
+ 'type' => 'NOTIFICATIONS',
+ 'subscribed' => work_item.subscribed?(current_user, project)
+ )
+ )
+ )
+ end
+ end
end
context 'when an Issue Global ID is provided' do
diff --git a/spec/requests/api/terraform/state_spec.rb b/spec/requests/api/terraform/state_spec.rb
index 7784985f7f4..c94643242c9 100644
--- a/spec/requests/api/terraform/state_spec.rb
+++ b/spec/requests/api/terraform/state_spec.rb
@@ -21,6 +21,7 @@ RSpec.describe API::Terraform::State, :snowplow, feature_category: :infrastructu
before do
stub_terraform_state_object_storage
+ stub_config(terraform_state: { enabled: true })
end
shared_examples 'endpoint with unique user tracking' do
@@ -81,6 +82,7 @@ RSpec.describe API::Terraform::State, :snowplow, feature_category: :infrastructu
subject(:request) { get api(state_path), headers: auth_header }
it_behaves_like 'endpoint with unique user tracking'
+ it_behaves_like 'it depends on value of the `terraform_state.enabled` config'
context 'without authentication' do
let(:auth_header) { basic_auth_header('bad', 'token') }
@@ -193,6 +195,7 @@ RSpec.describe API::Terraform::State, :snowplow, feature_category: :infrastructu
subject(:request) { post api(state_path), headers: auth_header, as: :json, params: params }
it_behaves_like 'endpoint with unique user tracking'
+ it_behaves_like 'it depends on value of the `terraform_state.enabled` config'
context 'when terraform state with a given name is already present' do
context 'with maintainer permissions' do
@@ -371,6 +374,7 @@ RSpec.describe API::Terraform::State, :snowplow, feature_category: :infrastructu
subject(:request) { delete api(state_path), headers: auth_header }
it_behaves_like 'endpoint with unique user tracking'
+ it_behaves_like 'it depends on value of the `terraform_state.enabled` config'
shared_examples 'schedules the state for deletion' do
it 'returns empty body' do
@@ -552,6 +556,10 @@ RSpec.describe API::Terraform::State, :snowplow, feature_category: :infrastructu
let(:lock_id) { 'irrelevant to this test, just needs to be present' }
end
+ it_behaves_like 'it depends on value of the `terraform_state.enabled` config' do
+ let(:lock_id) { '123.456' }
+ end
+
where(given_state_name: %w[test-state test.state test%2Ffoo])
with_them do
let(:state_name) { given_state_name }
diff --git a/spec/requests/api/terraform/state_version_spec.rb b/spec/requests/api/terraform/state_version_spec.rb
index 28abbb5749d..24b3ca94581 100644
--- a/spec/requests/api/terraform/state_version_spec.rb
+++ b/spec/requests/api/terraform/state_version_spec.rb
@@ -22,9 +22,15 @@ RSpec.describe API::Terraform::StateVersion, feature_category: :infrastructure_a
let(:version_serial) { version.version }
let(:state_version_path) { "/projects/#{project_id}/terraform/state/#{state_name}/versions/#{version_serial}" }
+ before do
+ stub_config(terraform_state: { enabled: true })
+ end
+
describe 'GET /projects/:id/terraform/state/:name/versions/:serial' do
subject(:request) { get api(state_version_path), headers: auth_header }
+ it_behaves_like 'it depends on value of the `terraform_state.enabled` config'
+
context 'with invalid authentication' do
let(:auth_header) { basic_auth_header('bad', 'token') }
@@ -147,6 +153,8 @@ RSpec.describe API::Terraform::StateVersion, feature_category: :infrastructure_a
describe 'DELETE /projects/:id/terraform/state/:name/versions/:serial' do
subject(:request) { delete api(state_version_path), headers: auth_header }
+ it_behaves_like 'it depends on value of the `terraform_state.enabled` config', { success_status: :no_content }
+
context 'with invalid authentication' do
let(:auth_header) { basic_auth_header('bad', 'token') }
diff --git a/spec/support/shared_contexts/policies/group_policy_shared_context.rb b/spec/support/shared_contexts/policies/group_policy_shared_context.rb
index ccfd403bebd..a9b12d47393 100644
--- a/spec/support/shared_contexts/policies/group_policy_shared_context.rb
+++ b/spec/support/shared_contexts/policies/group_policy_shared_context.rb
@@ -42,9 +42,7 @@ RSpec.shared_context 'GroupPolicy context' do
let(:developer_permissions) do
%i[
- create_metrics_dashboard_annotation
- delete_metrics_dashboard_annotation
- update_metrics_dashboard_annotation
+ admin_metrics_dashboard_annotation
create_custom_emoji
create_package
read_cluster
diff --git a/spec/support/shared_contexts/policies/project_policy_shared_context.rb b/spec/support/shared_contexts/policies/project_policy_shared_context.rb
index afc7fc8766f..71244933702 100644
--- a/spec/support/shared_contexts/policies/project_policy_shared_context.rb
+++ b/spec/support/shared_contexts/policies/project_policy_shared_context.rb
@@ -52,12 +52,11 @@ RSpec.shared_context 'ProjectPolicy context' do
admin_merge_request admin_tag create_build
create_commit_status create_container_image create_deployment
create_environment create_merge_request_from
- create_metrics_dashboard_annotation create_pipeline create_release
- create_wiki delete_metrics_dashboard_annotation
- destroy_container_image push_code read_pod_logs read_terraform_state
- resolve_note update_build update_commit_status update_container_image
- update_deployment update_environment update_merge_request
- update_metrics_dashboard_annotation update_pipeline update_release destroy_release
+ admin_metrics_dashboard_annotation create_pipeline create_release
+ create_wiki destroy_container_image push_code read_pod_logs
+ read_terraform_state resolve_note update_build update_commit_status
+ update_container_image update_deployment update_environment
+ update_merge_request update_pipeline update_release destroy_release
read_resource_group update_resource_group update_escalation_status
]
end
diff --git a/spec/support/shared_contexts/rack_attack_shared_context.rb b/spec/support/shared_contexts/rack_attack_shared_context.rb
index 12625ead72b..4e8c6e9dec6 100644
--- a/spec/support/shared_contexts/rack_attack_shared_context.rb
+++ b/spec/support/shared_contexts/rack_attack_shared_context.rb
@@ -5,8 +5,7 @@ RSpec.shared_context 'rack attack cache store' do
# Instead of test environment's :null_store so the throttles can increment
Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new
- # Make time-dependent tests deterministic
- freeze_time { example.run }
+ example.run
Rack::Attack.cache.store = Rails.cache
end
diff --git a/spec/support/shared_examples/lib/api/terraform_state_enabled_shared_examples.rb b/spec/support/shared_examples/lib/api/terraform_state_enabled_shared_examples.rb
new file mode 100644
index 00000000000..b88eade7db2
--- /dev/null
+++ b/spec/support/shared_examples/lib/api/terraform_state_enabled_shared_examples.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'it depends on value of the `terraform_state.enabled` config' do |params = {}|
+ let(:expected_success_status) { params[:success_status] || :ok }
+
+ context 'when terraform_state.enabled=false' do
+ before do
+ stub_config(terraform_state: { enabled: false })
+ end
+
+ it 'returns `forbidden` response' do
+ request
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ context 'when terraform_state.enabled=true' do
+ before do
+ stub_config(terraform_state: { enabled: true })
+ end
+
+ it 'returns a successful response' do
+ request
+
+ expect(response).to have_gitlab_http_status(expected_success_status)
+ end
+ end
+end