summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml4
-rw-r--r--.gitlab/ci/reports.gitlab-ci.yml2
-rw-r--r--.gitlab/issue_templates/Geo Replicate a new Git repository type.md36
-rw-r--r--.gitlab/issue_templates/Geo Replicate a new blob type.md36
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--app/assets/javascripts/constants.js3
-rw-r--r--app/assets/javascripts/editor/components/source_editor_toolbar.vue13
-rw-r--r--app/assets/javascripts/editor/components/source_editor_toolbar_button.vue1
-rw-r--r--app/assets/javascripts/editor/constants.js104
-rw-r--r--app/assets/javascripts/editor/extensions/source_editor_markdown_ext.js48
-rw-r--r--app/assets/javascripts/environments/environment_details/constants.js47
-rw-r--r--app/assets/javascripts/environments/environment_details/index.vue118
-rw-r--r--app/assets/javascripts/environments/graphql/queries/environment_details.query.graphql48
-rw-r--r--app/assets/javascripts/environments/helpers/deployment_data_transformation_helper.js62
-rw-r--r--app/assets/javascripts/environments/mount_show.js30
-rw-r--r--app/assets/javascripts/jobs/components/job/empty_state.vue25
-rw-r--r--app/assets/javascripts/jobs/components/job/legacy_manual_variables_form.vue199
-rw-r--r--app/assets/javascripts/jobs/components/job/manual_variables_form.vue53
-rw-r--r--app/assets/javascripts/jobs/components/job/sidebar/job_sidebar_retry_button.vue7
-rw-r--r--app/assets/javascripts/jobs/components/job/sidebar/legacy_job_sidebar_retry_button.vue53
-rw-r--r--app/assets/javascripts/jobs/components/job/sidebar/legacy_sidebar_header.vue98
-rw-r--r--app/assets/javascripts/jobs/components/job/sidebar/sidebar.vue9
-rw-r--r--app/assets/javascripts/jobs/components/job/sidebar/sidebar_header.vue3
-rw-r--r--app/assets/javascripts/pages/projects/environments/show/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue116
-rw-r--r--app/assets/javascripts/pipelines/components/test_reports/test_reports.vue14
-rw-r--r--app/controllers/dashboard/todos_controller.rb23
-rw-r--r--app/controllers/projects/environments_controller.rb3
-rw-r--r--app/controllers/projects/jobs_controller.rb3
-rw-r--r--app/controllers/projects/settings/repository_controller.rb8
-rw-r--r--app/controllers/projects_controller.rb20
-rw-r--r--app/finders/todos_finder.rb2
-rw-r--r--app/graphql/types/ci/runner_type.rb2
-rw-r--r--app/helpers/environment_helper.rb1
-rw-r--r--app/helpers/projects_helper.rb1
-rw-r--r--app/policies/project_policy.rb25
-rw-r--r--app/services/merge_requests/after_create_service.rb2
-rw-r--r--app/services/projects/update_service.rb16
-rw-r--r--app/views/projects/blob/_editor.html.haml3
-rw-r--r--app/views/projects/environments/show.html.haml49
-rw-r--r--config/events/1671198983_Gitlab__UsageDataCounters__MergeRequestActivityUniqueCounter_create.yml27
-rw-r--r--config/feature_flags/development/environment_details_vue.yml8
-rw-r--r--config/feature_flags/development/graphql_job_app.yml8
-rw-r--r--config/feature_flags/development/split_operations_visibility_permissions.yml8
-rw-r--r--config/metrics/counts_28d/20221213182900_i_code_review_create_mr_monthly.yml26
-rw-r--r--config/metrics/counts_7d/20221213183300_i_code_review_create_mr_weekly.yml26
-rw-r--r--doc/administration/clusters/kas.md2
-rw-r--r--doc/development/database/migrations_for_multiple_databases.md18
-rw-r--r--doc/development/database/multiple_databases.md4
-rw-r--r--doc/development/documentation/styleguide/img/tier_badge.pngbin9320 -> 0 bytes
-rw-r--r--doc/development/migration_style_guide.md22
-rw-r--r--doc/integration/jira/connect-app.md3
-rw-r--r--doc/integration/saml.md127
-rw-r--r--doc/user/packages/nuget_repository/img/visual_studio_adding_nuget_source.pngbin36730 -> 0 bytes
-rw-r--r--doc/user/packages/nuget_repository/img/visual_studio_nuget_source_added.pngbin6234 -> 0 bytes
-rw-r--r--doc/user/project/pages/img/remove_fork_relationship_v13_1.pngbin11640 -> 0 bytes
-rw-r--r--lib/bitbucket_server/connection.rb1
-rw-r--r--lib/gitlab/database/gitlab_schemas.yml619
-rw-r--r--lib/gitlab/database/migration_helpers/automatic_lock_writes_on_tables.rb3
-rw-r--r--lib/gitlab/ssh/signature.rb3
-rw-r--r--lib/gitlab/usage_data_counters/known_events/code_review_events.yml6
-rw-r--r--lib/gitlab/usage_data_counters/merge_request_activity_unique_counter.rb23
-rw-r--r--lib/sidebars/projects/menus/infrastructure_menu.rb6
-rw-r--r--lib/sidebars/projects/menus/monitor_menu.rb6
-rw-r--r--locale/gitlab.pot18
-rw-r--r--package.json2
-rw-r--r--qa/qa/page/project/job/show.rb2
-rw-r--r--spec/controllers/dashboard/todos_controller_spec.rb35
-rw-r--r--spec/controllers/projects/settings/repository_controller_spec.rb36
-rw-r--r--spec/controllers/projects_controller_spec.rb29
-rw-r--r--spec/features/monitor_sidebar_link_spec.rb53
-rw-r--r--spec/features/projects/blobs/edit_spec.rb9
-rw-r--r--spec/features/projects/environments/environment_spec.rb71
-rw-r--r--spec/finders/todos_finder_spec.rb4
-rw-r--r--spec/frontend/editor/components/source_editor_toolbar_button_spec.js38
-rw-r--r--spec/frontend/editor/source_editor_markdown_ext_spec.js14
-rw-r--r--spec/frontend/environments/environment_details_page_spec.js50
-rw-r--r--spec/frontend/environments/helpers/__snapshots__/deployment_data_transformation_helper_spec.js.snap127
-rw-r--r--spec/frontend/environments/helpers/deployment_data_transformation_helper_spec.js96
-rw-r--r--spec/frontend/fixtures/environments.rb53
-rw-r--r--spec/frontend/jobs/components/job/empty_state_spec.js19
-rw-r--r--spec/frontend/jobs/components/job/job_app_spec.js175
-rw-r--r--spec/frontend/jobs/components/job/job_sidebar_retry_button_spec.js2
-rw-r--r--spec/frontend/jobs/components/job/legacy_manual_variables_form_spec.js156
-rw-r--r--spec/frontend/jobs/components/job/legacy_sidebar_header_spec.js102
-rw-r--r--spec/frontend/jobs/components/job/manual_variables_form_spec.js1
-rw-r--r--spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js135
-rw-r--r--spec/helpers/environment_helper_spec.rb1
-rw-r--r--spec/helpers/projects_helper_spec.rb1
-rw-r--r--spec/lib/bitbucket_server/connection_spec.rb6
-rw-r--r--spec/lib/gitlab/database/migration_helpers/automatic_lock_writes_on_tables_spec.rb3
-rw-r--r--spec/lib/gitlab/ssh/signature_spec.rb85
-rw-r--r--spec/lib/gitlab/usage_data_counters/code_review_events_spec.rb2
-rw-r--r--spec/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter_spec.rb20
-rw-r--r--spec/lib/sidebars/projects/menus/deployments_menu_spec.rb28
-rw-r--r--spec/lib/sidebars/projects/menus/infrastructure_menu_spec.rb31
-rw-r--r--spec/lib/sidebars/projects/menus/monitor_menu_spec.rb32
-rw-r--r--spec/policies/project_policy_spec.rb81
-rw-r--r--spec/requests/api/graphql/ci/runner_spec.rb2
-rw-r--r--spec/services/merge_requests/after_create_service_spec.rb2
-rw-r--r--spec/services/projects/update_service_spec.rb19
-rw-r--r--yarn.lock266
102 files changed, 1750 insertions, 2294 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 5dfcc5715da..3834af822ea 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -152,6 +152,10 @@ variables:
REGISTRY_HOST: "registry.gitlab.com"
REGISTRY_GROUP: "gitlab-org"
+ # Disable useless network connections when installing some NPM packages.
+ # See https://gitlab.com/gitlab-com/gl-security/engineering-and-research/inventory/-/issues/827#note_1203181407
+ DISABLE_OPENCOLLECTIVE: "true"
+
include:
- local: .gitlab/ci/*.gitlab-ci.yml
- remote: 'https://gitlab.com/gitlab-org/frontend/untamper-my-lockfile/-/raw/main/templates/merge_request_pipelines.yml'
diff --git a/.gitlab/ci/reports.gitlab-ci.yml b/.gitlab/ci/reports.gitlab-ci.yml
index 5fdcdc12fc8..b87e5ad9bba 100644
--- a/.gitlab/ci/reports.gitlab-ci.yml
+++ b/.gitlab/ci/reports.gitlab-ci.yml
@@ -88,7 +88,7 @@ yarn-audit-dependency_scanning:
extends: .default-retry
stage: test
image:
- name: ${REGISTRY_HOST}/${REGISTRY_GROUP}/security-products/package-hunter-cli:v1.3.2@sha256:7529deaef9ea21aab56bfb74ae1abbc121311affdb6ece49ce7b1c360f997ca2
+ name: ${REGISTRY_HOST}/${REGISTRY_GROUP}/security-products/package-hunter-cli:v1.3.3@sha256:1d3af9a61aa01549a62be17fa655fcf06271ac9e1b1e822c2a7930fa1d4a8a6b
entrypoint: [""]
variables:
HTR_user: '$PACKAGE_HUNTER_USER'
diff --git a/.gitlab/issue_templates/Geo Replicate a new Git repository type.md b/.gitlab/issue_templates/Geo Replicate a new Git repository type.md
index f07d37cc0e5..571b0db0a30 100644
--- a/.gitlab/issue_templates/Geo Replicate a new Git repository type.md
+++ b/.gitlab/issue_templates/Geo Replicate a new Git repository type.md
@@ -94,11 +94,19 @@ Geo secondary sites have a [Geo tracking database](https://gitlab.com/gitlab-org
- [ ] If deviating from the above example, then be sure to order columns according to [our guidelines](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/development/ordering_table_columns.md).
-- [ ] Add the new table to the GitLab Schema defined in [`ee/lib/ee/gitlab/database/gitlab_schemas.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/ee/gitlab/database/gitlab_schemas.yml).
-
- ```yaml
- cool_widget_registry: :gitlab_geo
- ```
+- [ ] Add the new table to the [database dictionary](database_dictionary.md) defined in [`ee/db/docs/`](https://gitlab.com/gitlab-org/gitlab/-/tree/master/ee/db/docs):
+
+ ```yaml
+ table_name: cool_widget_registry
+ description: Description example
+ introduced_by_url: Merge request link
+ milestone: Milestone example
+ feature_categories:
+ - Feature category example
+ classes:
+ - Class example
+ gitlab_schema: gitlab_geo
+ ```
- [ ] Run Geo tracking database migrations:
@@ -157,11 +165,19 @@ The Geo primary site needs to checksum every replicable so secondaries can verif
- [ ] If deviating from the above example, then be sure to order columns according to [our guidelines](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/development/ordering_table_columns.md).
-- [ ] Add the new table to the GitLab Schema defined in [`lib/gitlab/database/gitlab_schemas.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/database/gitlab_schemas.yml) with the databases they need to be added to.
-
- ```yaml
- cool_widget_states: :gitlab_main
- ```
+- [ ] Add the new table to the [database dictionary](database_dictionary.md) defined in [`db/docs/`](https://gitlab.com/gitlab-org/gitlab/-/tree/master/db/docs):
+
+ ```yaml
+ table_name: cool_widget_states
+ description: Description example
+ introduced_by_url: Merge request link
+ milestone: Milestone example
+ feature_categories:
+ - Feature category example
+ classes:
+ - Class example
+ gitlab_schema: gitlab_main
+ ```
- [ ] Run database migrations:
diff --git a/.gitlab/issue_templates/Geo Replicate a new blob type.md b/.gitlab/issue_templates/Geo Replicate a new blob type.md
index 2bb8918df60..121dbdf035f 100644
--- a/.gitlab/issue_templates/Geo Replicate a new blob type.md
+++ b/.gitlab/issue_templates/Geo Replicate a new blob type.md
@@ -94,11 +94,19 @@ Geo secondary sites have a [Geo tracking database](https://gitlab.com/gitlab-org
- [ ] If deviating from the above example, then be sure to order columns according to [our guidelines](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/development/ordering_table_columns.md).
-- [ ] Add the new table to the GitLab Schema defined in [`ee/lib/ee/gitlab/database/gitlab_schemas.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/ee/gitlab/database/gitlab_schemas.yml).
-
- ```yaml
- cool_widget_registry: :gitlab_geo
- ```
+- [ ] Add the new table to the [database dictionary](database_dictionary.md) defined in [`ee/db/docs/`](https://gitlab.com/gitlab-org/gitlab/-/tree/master/ee/db/docs):
+
+ ```yaml
+ table_name: cool_widget_registry
+ description: Description example
+ introduced_by_url: Merge request link
+ milestone: Milestone example
+ feature_categories:
+ - Feature category example
+ classes:
+ - Class example
+ gitlab_schema: gitlab_geo
+ ```
- [ ] Run Geo tracking database migrations:
@@ -159,11 +167,19 @@ The Geo primary site needs to checksum every replicable so secondaries can verif
- [ ] If deviating from the above example, then be sure to order columns according to [our guidelines](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/development/ordering_table_columns.md).
-- [ ] Add the new table to the GitLab Schema defined in [`lib/gitlab/database/gitlab_schemas.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/database/gitlab_schemas.yml) with the databases they need to be added to.
-
- ```yaml
- cool_widget_states: :gitlab_main
- ```
+- [ ] Add the new table to the database dictionary defined in [`db/docs/`](https://gitlab.com/gitlab-org/gitlab/-/tree/master/db/docs):
+
+ ```yaml
+ table_name: cool_widget_states
+ description: Description example
+ introduced_by_url: Merge request link
+ milestone: Milestone example
+ feature_categories:
+ - Feature category example
+ classes:
+ - Class example
+ gitlab_schema: gitlab_main
+ ```
- [ ] Run database migrations:
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 0fe952d64e5..9875fcbeb2d 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-d34f576112de436f79854197770acbcc8f69d61d
+db86e7b08f1b6e19108fa69cc4b31d91ef4ad033
diff --git a/app/assets/javascripts/constants.js b/app/assets/javascripts/constants.js
new file mode 100644
index 00000000000..c56d45166a0
--- /dev/null
+++ b/app/assets/javascripts/constants.js
@@ -0,0 +1,3 @@
+import { s__ } from '~/locale';
+
+export const MODIFIER_KEY = window.gl?.client?.isMac ? '⌘' : s__('KeyboardKey|Ctrl+');
diff --git a/app/assets/javascripts/editor/components/source_editor_toolbar.vue b/app/assets/javascripts/editor/components/source_editor_toolbar.vue
index 2c177634bbe..c72145f9d2f 100644
--- a/app/assets/javascripts/editor/components/source_editor_toolbar.vue
+++ b/app/assets/javascripts/editor/components/source_editor_toolbar.vue
@@ -57,13 +57,12 @@ export default {
>
<div v-for="group in $options.groups" :key="group">
<gl-button-group v-if="hasGroupItems(group)">
- <template v-for="item in getGroupItems(group)">
- <source-editor-toolbar-button
- :key="item.id"
- :button="item"
- @click="$emit('click', item)"
- />
- </template>
+ <source-editor-toolbar-button
+ v-for="item in getGroupItems(group)"
+ :key="item.id"
+ :button="item"
+ @click="$emit('click', item)"
+ />
</gl-button-group>
</div>
</section>
diff --git a/app/assets/javascripts/editor/components/source_editor_toolbar_button.vue b/app/assets/javascripts/editor/components/source_editor_toolbar_button.vue
index e440feb5a22..38f586f0773 100644
--- a/app/assets/javascripts/editor/components/source_editor_toolbar_button.vue
+++ b/app/assets/javascripts/editor/components/source_editor_toolbar_button.vue
@@ -59,6 +59,7 @@ export default {
:icon="icon"
:title="label"
:aria-label="label"
+ :class="button.class"
@click="clickHandler($event)"
/>
</template>
diff --git a/app/assets/javascripts/editor/constants.js b/app/assets/javascripts/editor/constants.js
index 83cfdd25757..d0649ecccba 100644
--- a/app/assets/javascripts/editor/constants.js
+++ b/app/assets/javascripts/editor/constants.js
@@ -1,5 +1,6 @@
+import { MODIFIER_KEY } from '~/constants';
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
-import { s__, __ } from '~/locale';
+import { s__, __, sprintf } from '~/locale';
export const URI_PREFIX = 'gitlab';
export const CONTENT_UPDATE_DEBOUNCE = DEFAULT_DEBOUNCE_AND_THROTTLE_MS;
@@ -62,3 +63,104 @@ export const EXTENSION_MARKDOWN_PREVIEW_PANEL_WIDTH = 0.5; // 50% of the width
export const EXTENSION_MARKDOWN_PREVIEW_UPDATE_DELAY = 250; // ms
export const EXTENSION_MARKDOWN_PREVIEW_LABEL = __('Preview Markdown');
export const EXTENSION_MARKDOWN_HIDE_PREVIEW_LABEL = __('Hide Live Preview');
+export const EXTENSION_MARKDOWN_BUTTONS = [
+ {
+ id: 'bold',
+ label: sprintf(s__('MarkdownEditor|Add bold text (%{modifierKey}B)'), {
+ modifierKey: MODIFIER_KEY,
+ }),
+ data: {
+ mdTag: '**',
+ mdShortcuts: '["mod+b"]',
+ },
+ },
+ {
+ id: 'italic',
+ label: sprintf(s__('MarkdownEditor|Add italic text (%{modifierKey}I)'), {
+ modifierKey: MODIFIER_KEY,
+ }),
+ data: {
+ mdTag: '_',
+ mdShortcuts: '["mod+i"]',
+ },
+ },
+ {
+ id: 'strikethrough',
+ label: sprintf(s__('MarkdownEditor|Add strikethrough text (%{modifierKey}⇧X)'), {
+ modifierKey: MODIFIER_KEY,
+ }),
+ data: {
+ mdTag: '~~',
+ mdShortcuts: '["mod+shift+x]',
+ },
+ },
+ {
+ id: 'quote',
+ label: __('Insert a quote'),
+ data: {
+ mdTag: '> ',
+ mdPrepend: true,
+ },
+ },
+ {
+ id: 'code',
+ label: __('Insert code'),
+ data: {
+ mdTag: '`',
+ mdBlock: '```',
+ },
+ },
+ {
+ id: 'link',
+ label: sprintf(s__('MarkdownEditor|Add a link (%{modifier_key}K)'), {
+ modifierKey: MODIFIER_KEY,
+ }),
+ data: {
+ mdTag: '[{text}](url)',
+ mdSelect: 'url',
+ mdShortcuts: '["mod+k"]',
+ },
+ },
+ {
+ id: 'list-bulleted',
+ label: __('Add a bullet list'),
+ data: {
+ mdTag: '- ',
+ mdPrepend: true,
+ },
+ },
+ {
+ id: 'list-numbered',
+ label: __('Add a numbered list'),
+ data: {
+ mdTag: '1. ',
+ mdPrepend: true,
+ },
+ },
+ {
+ id: 'list-task',
+ label: __('Add a checklist'),
+ data: {
+ mdTag: '- [ ] ',
+ mdPrepend: true,
+ },
+ },
+ {
+ id: 'details-block',
+ label: __('Add a collapsible section'),
+ data: {
+ mdTag: '<details><summary>Click to expand</summary>\n{text}\n</details>',
+ mdPrepend: true,
+ mdSelect: __('Click to expand'),
+ },
+ },
+ {
+ id: 'table',
+ label: __('Add a table'),
+ data: {
+ /* eslint-disable-next-line @gitlab/require-i18n-strings */
+ mdTag: '| header | header |\n| ------ | ------ |\n| | |\n| | |',
+ mdPrepend: true,
+ },
+ },
+];
diff --git a/app/assets/javascripts/editor/extensions/source_editor_markdown_ext.js b/app/assets/javascripts/editor/extensions/source_editor_markdown_ext.js
index a16fe93026e..6105a577996 100644
--- a/app/assets/javascripts/editor/extensions/source_editor_markdown_ext.js
+++ b/app/assets/javascripts/editor/extensions/source_editor_markdown_ext.js
@@ -1,8 +1,37 @@
+import { insertMarkdownText } from '~/lib/utils/text_markdown';
+import { EDITOR_TOOLBAR_RIGHT_GROUP, EXTENSION_MARKDOWN_BUTTONS } from '../constants';
+
export class EditorMarkdownExtension {
static get extensionName() {
return 'EditorMarkdown';
}
+ onSetup(instance) {
+ this.toolbarButtons = [];
+ if (instance.toolbar) {
+ this.setupToolbar(instance);
+ }
+ }
+ onBeforeUnuse(instance) {
+ const ids = this.toolbarButtons.map((item) => item.id);
+ if (instance.toolbar) {
+ instance.toolbar.removeItems(ids);
+ }
+ }
+
+ setupToolbar(instance) {
+ this.toolbarButtons = EXTENSION_MARKDOWN_BUTTONS.map((btn) => {
+ return {
+ ...btn,
+ icon: btn.id,
+ group: EDITOR_TOOLBAR_RIGHT_GROUP,
+ category: 'tertiary',
+ onClick: (e) => instance.insertMarkdown(e),
+ };
+ });
+ instance.toolbar.addItems(this.toolbarButtons);
+ }
+
// eslint-disable-next-line class-methods-use-this
provides() {
return {
@@ -36,6 +65,25 @@ export class EditorMarkdownExtension {
pos.lineNumber += dy;
instance.setPosition(pos);
},
+ insertMarkdown: (instance, e) => {
+ const {
+ mdTag: tag,
+ mdBlock: blockTag,
+ mdPrepend,
+ mdSelect: select,
+ } = e.currentTarget.dataset;
+
+ insertMarkdownText({
+ tag,
+ blockTag,
+ wrap: !mdPrepend,
+ select,
+ selected: instance.getSelectedText(),
+ text: instance.getValue(),
+ editor: instance,
+ });
+ instance.focus();
+ },
/**
* Adjust existing selection to select text within the original selection.
* - If `selectedText` is not supplied, we fetch selected text with
diff --git a/app/assets/javascripts/environments/environment_details/constants.js b/app/assets/javascripts/environments/environment_details/constants.js
new file mode 100644
index 00000000000..56c70c354b7
--- /dev/null
+++ b/app/assets/javascripts/environments/environment_details/constants.js
@@ -0,0 +1,47 @@
+import { __ } from '~/locale';
+
+export const ENVIRONMENT_DETAILS_PAGE_SIZE = 20;
+export const ENVIRONMENT_DETAILS_TABLE_FIELDS = [
+ {
+ key: 'status',
+ label: __('Status'),
+ columnClass: 'gl-w-10p',
+ tdClass: 'gl-vertical-align-middle!',
+ },
+ {
+ key: 'id',
+ label: __('ID'),
+ columnClass: 'gl-w-5p',
+ tdClass: 'gl-vertical-align-middle!',
+ },
+ {
+ key: 'triggerer',
+ label: __('Triggerer'),
+ columnClass: 'gl-w-10p',
+ tdClass: 'gl-vertical-align-middle!',
+ },
+ {
+ key: 'commit',
+ label: __('Commit'),
+ columnClass: 'gl-w-20p',
+ tdClass: 'gl-vertical-align-middle!',
+ },
+ {
+ key: 'job',
+ label: __('Job'),
+ columnClass: 'gl-w-20p',
+ tdClass: 'gl-vertical-align-middle!',
+ },
+ {
+ key: 'created',
+ label: __('Created'),
+ columnClass: 'gl-w-10p',
+ tdClass: 'gl-vertical-align-middle! gl-white-space-nowrap',
+ },
+ {
+ key: 'deployed',
+ label: __('Deployed'),
+ columnClass: 'gl-w-10p',
+ tdClass: 'gl-vertical-align-middle! gl-white-space-nowrap',
+ },
+];
diff --git a/app/assets/javascripts/environments/environment_details/index.vue b/app/assets/javascripts/environments/environment_details/index.vue
new file mode 100644
index 00000000000..435d3fd820e
--- /dev/null
+++ b/app/assets/javascripts/environments/environment_details/index.vue
@@ -0,0 +1,118 @@
+<script>
+import {
+ GlTableLite,
+ GlAvatarLink,
+ GlAvatar,
+ GlLink,
+ GlTooltipDirective,
+ GlTruncate,
+ GlBadge,
+ GlLoadingIcon,
+} from '@gitlab/ui';
+import Commit from '~/vue_shared/components/commit.vue';
+import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+import environmentDetailsQuery from '../graphql/queries/environment_details.query.graphql';
+import { convertToDeploymentTableRow } from '../helpers/deployment_data_transformation_helper';
+import DeploymentStatusBadge from '../components/deployment_status_badge.vue';
+import { ENVIRONMENT_DETAILS_PAGE_SIZE, ENVIRONMENT_DETAILS_TABLE_FIELDS } from './constants';
+
+export default {
+ components: {
+ GlLoadingIcon,
+ GlBadge,
+ DeploymentStatusBadge,
+ TimeAgoTooltip,
+ GlTableLite,
+ GlAvatarLink,
+ GlAvatar,
+ GlLink,
+ GlTruncate,
+ Commit,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ props: {
+ projectFullPath: {
+ type: String,
+ required: true,
+ },
+ environmentName: {
+ type: String,
+ required: true,
+ },
+ },
+ apollo: {
+ project: {
+ query: environmentDetailsQuery,
+ variables() {
+ return {
+ projectFullPath: this.projectFullPath,
+ environmentName: this.environmentName,
+ pageSize: ENVIRONMENT_DETAILS_PAGE_SIZE,
+ };
+ },
+ },
+ },
+ data() {
+ return {
+ project: {
+ loading: true,
+ },
+ loading: 0,
+ tableFields: ENVIRONMENT_DETAILS_TABLE_FIELDS,
+ };
+ },
+ computed: {
+ deployments() {
+ return this.project.environment?.deployments.nodes.map(convertToDeploymentTableRow) || [];
+ },
+ isLoading() {
+ return this.$apollo.queries.project.loading;
+ },
+ },
+};
+</script>
+<template>
+ <div>
+ <gl-loading-icon v-if="isLoading" size="lg" class="mt-3" />
+ <gl-table-lite v-else :items="deployments" :fields="tableFields" fixed stacked="lg">
+ <template #table-colgroup="{ fields }">
+ <col v-for="field in fields" :key="field.key" :class="field.columnClass" />
+ </template>
+ <template #cell(status)="{ item }">
+ <div>
+ <deployment-status-badge :status="item.status" />
+ </div>
+ </template>
+ <template #cell(id)="{ item }">
+ <strong>{{ item.id }}</strong>
+ </template>
+ <template #cell(triggerer)="{ item }">
+ <gl-avatar-link :href="item.triggerer.webUrl">
+ <gl-avatar
+ v-gl-tooltip
+ :title="item.triggerer.name"
+ :src="item.triggerer.avatarUrl"
+ :size="24"
+ />
+ </gl-avatar-link>
+ </template>
+ <template #cell(commit)="{ item }">
+ <commit v-bind="item.commit" />
+ </template>
+ <template #cell(job)="{ item }">
+ <gl-link v-if="item.job" :href="item.job.webPath">
+ <gl-truncate :text="item.job.label" />
+ </gl-link>
+ <gl-badge v-else variant="info">{{ __('API') }}</gl-badge>
+ </template>
+ <template #cell(created)="{ item }">
+ <time-ago-tooltip :time="item.created" />
+ </template>
+ <template #cell(deployed)="{ item }">
+ <time-ago-tooltip :time="item.deployed" />
+ </template>
+ </gl-table-lite>
+ </div>
+</template>
diff --git a/app/assets/javascripts/environments/graphql/queries/environment_details.query.graphql b/app/assets/javascripts/environments/graphql/queries/environment_details.query.graphql
new file mode 100644
index 00000000000..e8f2a2cdf7f
--- /dev/null
+++ b/app/assets/javascripts/environments/graphql/queries/environment_details.query.graphql
@@ -0,0 +1,48 @@
+query getEnvironmentDetails($projectFullPath: ID!, $environmentName: String, $pageSize: Int) {
+ project(fullPath: $projectFullPath) {
+ id
+ name
+ fullPath
+ environment(name: $environmentName) {
+ id
+ name
+ deployments(orderBy: { createdAt: DESC }, first: $pageSize) {
+ nodes {
+ id
+ iid
+ status
+ ref
+ tag
+ job {
+ name
+ id
+ webPath
+ }
+ commit {
+ id
+ shortId
+ message
+ webUrl
+ authorGravatar
+ authorName
+ authorEmail
+ author {
+ id
+ name
+ avatarUrl
+ webUrl
+ }
+ }
+ triggerer {
+ id
+ webUrl
+ name
+ avatarUrl
+ }
+ createdAt
+ finishedAt
+ }
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/environments/helpers/deployment_data_transformation_helper.js b/app/assets/javascripts/environments/helpers/deployment_data_transformation_helper.js
new file mode 100644
index 00000000000..bfe92fe3125
--- /dev/null
+++ b/app/assets/javascripts/environments/helpers/deployment_data_transformation_helper.js
@@ -0,0 +1,62 @@
+import { getIdFromGraphQLId } from '~/graphql_shared/utils';
+
+/**
+ * This function transforms Commit object coming from GraphQL to object compatible with app/assets/javascripts/vue_shared/components/commit.vue author object
+ * @param {Object} Commit
+ * @returns {Object}
+ */
+export const getAuthorFromCommit = (commit) => {
+ if (commit.author) {
+ return {
+ username: commit.author.name,
+ path: commit.author.webUrl,
+ avatar_url: commit.author.avatarUrl,
+ };
+ }
+ return {
+ username: commit.authorName,
+ path: `mailto:${commit.authorEmail}`,
+ avatar_url: commit.authorGravatar,
+ };
+};
+
+/**
+ * This function transforms deploymentNode object coming from GraphQL to object compatible with app/assets/javascripts/vue_shared/components/commit.vue
+ * @param {Object} deploymentNode
+ * @returns {Object}
+ */
+export const getCommitFromDeploymentNode = (deploymentNode) => {
+ if (!deploymentNode.commit) {
+ throw new Error("deploymentNode argument doesn't have 'commit' field", deploymentNode);
+ }
+ return {
+ title: deploymentNode.commit.message,
+ commitUrl: deploymentNode.commit.webUrl,
+ shortSha: deploymentNode.commit.shortId,
+ tag: deploymentNode.tag,
+ commitRef: {
+ name: deploymentNode.ref,
+ },
+ author: getAuthorFromCommit(deploymentNode.commit),
+ };
+};
+
+/**
+ * This function transforms deploymentNode object coming from GraphQL to object compatible with app/assets/javascripts/environments/environment_details/page.vue table
+ * @param {Object} deploymentNode
+ * @returns {Object}
+ */
+export const convertToDeploymentTableRow = (deploymentNode) => {
+ return {
+ status: deploymentNode.status.toLowerCase(),
+ id: deploymentNode.iid,
+ triggerer: deploymentNode.triggerer,
+ commit: getCommitFromDeploymentNode(deploymentNode),
+ job: deploymentNode.job && {
+ webPath: deploymentNode.job.webPath,
+ label: `${deploymentNode.job.name} (#${getIdFromGraphQLId(deploymentNode.job.id)})`,
+ },
+ created: deploymentNode.createdAt || '',
+ deployed: deploymentNode.finishedAt || '',
+ };
+};
diff --git a/app/assets/javascripts/environments/mount_show.js b/app/assets/javascripts/environments/mount_show.js
index 6df4fad83f2..ba816599ac2 100644
--- a/app/assets/javascripts/environments/mount_show.js
+++ b/app/assets/javascripts/environments/mount_show.js
@@ -1,6 +1,8 @@
import Vue from 'vue';
+import VueApollo from 'vue-apollo';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import EnvironmentsDetailHeader from './components/environments_detail_header.vue';
+import { apolloProvider } from './graphql/client';
import environmentsMixin from './mixins/environments_mixin';
export const initHeader = () => {
@@ -41,7 +43,33 @@ export const initHeader = () => {
cancelAutoStopPath: dataset.environmentCancelAutoStopPath,
terminalPath: dataset.environmentTerminalPath,
metricsPath: dataset.environmentMetricsPath,
- updatePath: dataset.environmentEditPath,
+ updatePath: dataset.tnvironmentEditPath,
+ },
+ });
+ },
+ });
+};
+
+export const initPage = async () => {
+ if (!gon.features.environmentDetailsVue) {
+ return null;
+ }
+ const EnvironmentsDetailPageModule = await import('./environment_details/index.vue');
+ const EnvironmentsDetailPage = EnvironmentsDetailPageModule.default;
+ const dataElement = document.getElementById('environments-detail-view');
+ const dataSet = convertObjectPropsToCamelCase(JSON.parse(dataElement.dataset.details));
+
+ Vue.use(VueApollo);
+ const el = document.getElementById('environment_details_page');
+ return new Vue({
+ el,
+ apolloProvider: apolloProvider(),
+ provide: {},
+ render(createElement) {
+ return createElement(EnvironmentsDetailPage, {
+ props: {
+ projectFullPath: dataSet.projectFullPath,
+ environmentName: dataSet.name,
},
});
},
diff --git a/app/assets/javascripts/jobs/components/job/empty_state.vue b/app/assets/javascripts/jobs/components/job/empty_state.vue
index 053d5a4e740..d0a39025807 100644
--- a/app/assets/javascripts/jobs/components/job/empty_state.vue
+++ b/app/assets/javascripts/jobs/components/job/empty_state.vue
@@ -1,16 +1,12 @@
<script>
import { GlLink } from '@gitlab/ui';
-import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
-import LegacyManualVariablesForm from '~/jobs/components/job/legacy_manual_variables_form.vue';
import ManualVariablesForm from '~/jobs/components/job/manual_variables_form.vue';
export default {
components: {
GlLink,
- LegacyManualVariablesForm,
ManualVariablesForm,
},
- mixins: [glFeatureFlagsMixin()],
props: {
illustrationPath: {
type: String,
@@ -22,7 +18,7 @@ export default {
},
isRetryable: {
type: Boolean,
- required: false,
+ required: true,
},
jobId: {
type: Number,
@@ -62,9 +58,6 @@ export default {
},
},
computed: {
- showGraphQLManualVariablesForm() {
- return this.glFeatures?.graphqlJobApp && this.isRetryable;
- },
shouldRenderManualVariables() {
return this.playable && !this.scheduled;
},
@@ -85,16 +78,12 @@ export default {
<p v-if="content" data-testid="job-empty-state-content">{{ content }}</p>
</div>
- <template v-if="showGraphQLManualVariablesForm">
- <manual-variables-form
- v-if="shouldRenderManualVariables"
- :job-id="jobId"
- @hideManualVariablesForm="$emit('hideManualVariablesForm')"
- />
- </template>
- <template v-else>
- <legacy-manual-variables-form v-if="shouldRenderManualVariables" :action="action" />
- </template>
+ <manual-variables-form
+ v-if="shouldRenderManualVariables"
+ :is-retryable="isRetryable"
+ :job-id="jobId"
+ @hideManualVariablesForm="$emit('hideManualVariablesForm')"
+ />
<div v-if="action && !shouldRenderManualVariables" class="text-content">
<div class="text-center">
<gl-link
diff --git a/app/assets/javascripts/jobs/components/job/legacy_manual_variables_form.vue b/app/assets/javascripts/jobs/components/job/legacy_manual_variables_form.vue
deleted file mode 100644
index 2b6b6f8e59e..00000000000
--- a/app/assets/javascripts/jobs/components/job/legacy_manual_variables_form.vue
+++ /dev/null
@@ -1,199 +0,0 @@
-<script>
-import {
- GlFormInputGroup,
- GlInputGroupText,
- GlFormInput,
- GlButton,
- GlLink,
- GlSprintf,
- GlTooltipDirective,
-} from '@gitlab/ui';
-import { uniqueId } from 'lodash';
-import { mapActions } from 'vuex';
-import { helpPagePath } from '~/helpers/help_page_helper';
-import { s__ } from '~/locale';
-
-export default {
- name: 'LegacyManualVariablesForm',
- components: {
- GlFormInputGroup,
- GlInputGroupText,
- GlFormInput,
- GlButton,
- GlLink,
- GlSprintf,
- },
- directives: {
- GlTooltip: GlTooltipDirective,
- },
- props: {
- action: {
- type: Object,
- required: false,
- default: null,
- validator(value) {
- return (
- value === null ||
- (Object.prototype.hasOwnProperty.call(value, 'path') &&
- Object.prototype.hasOwnProperty.call(value, 'method') &&
- Object.prototype.hasOwnProperty.call(value, 'button_title'))
- );
- },
- },
- },
- inputTypes: {
- key: 'key',
- value: 'value',
- },
- i18n: {
- clearInputs: s__('CiVariables|Clear inputs'),
- header: s__('CiVariables|Variables'),
- keyLabel: s__('CiVariables|Key'),
- valueLabel: s__('CiVariables|Value'),
- keyPlaceholder: s__('CiVariables|Input variable key'),
- valuePlaceholder: s__('CiVariables|Input variable value'),
- formHelpText: s__(
- 'CiVariables|Specify variable values to be used in this run. The values specified in %{linkStart}CI/CD settings%{linkEnd} will be used as default',
- ),
- },
- data() {
- return {
- variables: [
- {
- key: '',
- secretValue: '',
- id: uniqueId(),
- },
- ],
- triggerBtnDisabled: false,
- };
- },
- computed: {
- variableSettings() {
- return helpPagePath('ci/variables/index', { anchor: 'add-a-cicd-variable-to-a-project' });
- },
- preparedVariables() {
- // we need to ensure no empty variables are passed to the API
- // and secretValue should be snake_case when passed to the API
- return this.variables
- .filter((variable) => variable.key !== '')
- .map(({ key, secretValue }) => ({ key, secret_value: secretValue }));
- },
- },
- methods: {
- ...mapActions(['triggerManualJob']),
- addEmptyVariable() {
- const lastVar = this.variables[this.variables.length - 1];
-
- if (lastVar.key === '') {
- return;
- }
-
- this.variables.push({
- key: '',
- secret_value: '',
- id: uniqueId(),
- });
- },
- canRemove(index) {
- return index < this.variables.length - 1;
- },
- deleteVariable(id) {
- this.variables.splice(
- this.variables.findIndex((el) => el.id === id),
- 1,
- );
- },
- inputRef(type, id) {
- return `${this.$options.inputTypes[type]}-${id}`;
- },
- trigger() {
- this.triggerBtnDisabled = true;
-
- this.triggerManualJob(this.preparedVariables);
- },
- },
-};
-</script>
-<template>
- <div class="row gl-justify-content-center">
- <div class="col-10" data-testid="manual-vars-form">
- <label>{{ $options.i18n.header }}</label>
-
- <div
- v-for="(variable, index) in variables"
- :key="variable.id"
- class="gl-display-flex gl-align-items-center gl-mb-4"
- data-testid="ci-variable-row"
- >
- <gl-form-input-group class="gl-mr-4 gl-flex-grow-1">
- <template #prepend>
- <gl-input-group-text>
- {{ $options.i18n.keyLabel }}
- </gl-input-group-text>
- </template>
- <gl-form-input
- :ref="inputRef('key', variable.id)"
- v-model="variable.key"
- :placeholder="$options.i18n.keyPlaceholder"
- data-testid="ci-variable-key"
- @change="addEmptyVariable"
- />
- </gl-form-input-group>
-
- <gl-form-input-group class="gl-flex-grow-2">
- <template #prepend>
- <gl-input-group-text>
- {{ $options.i18n.valueLabel }}
- </gl-input-group-text>
- </template>
- <gl-form-input
- :ref="inputRef('value', variable.id)"
- v-model="variable.secretValue"
- :placeholder="$options.i18n.valuePlaceholder"
- data-testid="ci-variable-value"
- />
- </gl-form-input-group>
-
- <gl-button
- v-if="canRemove(index)"
- v-gl-tooltip
- :aria-label="$options.i18n.clearInputs"
- :title="$options.i18n.clearInputs"
- class="gl-flex-grow-0 gl-flex-basis-0"
- category="tertiary"
- variant="danger"
- icon="clear"
- data-testid="delete-variable-btn"
- @click="deleteVariable(variable.id)"
- />
-
- <!-- delete variable button placeholder to not break flex layout -->
- <div v-else class="gl-w-7 gl-mr-3" data-testid="delete-variable-btn-placeholder"></div>
- </div>
-
- <div class="gl-text-center gl-mt-5">
- <gl-sprintf :message="$options.i18n.formHelpText">
- <template #link="{ content }">
- <gl-link :href="variableSettings" target="_blank">
- {{ content }}
- </gl-link>
- </template>
- </gl-sprintf>
- </div>
- <div class="gl-display-flex gl-justify-content-center gl-mt-5">
- <gl-button
- class="gl-mt-5"
- variant="confirm"
- category="primary"
- :aria-label="__('Trigger manual job')"
- :disabled="triggerBtnDisabled"
- data-testid="trigger-manual-job-btn"
- @click="trigger"
- >
- {{ action.button_title }}
- </gl-button>
- </div>
- </div>
- </div>
-</template>
diff --git a/app/assets/javascripts/jobs/components/job/manual_variables_form.vue b/app/assets/javascripts/jobs/components/job/manual_variables_form.vue
index e8edc7fc56f..d7bbd6daed2 100644
--- a/app/assets/javascripts/jobs/components/job/manual_variables_form.vue
+++ b/app/assets/javascripts/jobs/components/job/manual_variables_form.vue
@@ -10,6 +10,7 @@ import {
GlTooltipDirective,
} from '@gitlab/ui';
import { cloneDeep, uniqueId } from 'lodash';
+import { mapActions } from 'vuex';
import { fetchPolicies } from '~/lib/graphql';
import { createAlert } from '~/flash';
import { convertToGraphQLId } from '~/graphql_shared/utils';
@@ -58,6 +59,10 @@ export default {
},
},
props: {
+ isRetryable: {
+ type: Boolean,
+ required: true,
+ },
jobId: {
type: Number,
required: true,
@@ -76,9 +81,14 @@ export default {
keyLabel: s__('CiVariables|Key'),
keyPlaceholder: s__('CiVariables|Input variable key'),
runAgainButtonText: s__('CiVariables|Run job again'),
+ triggerButtonText: s__('CiVariables|Trigger this manual action'),
valueLabel: s__('CiVariables|Value'),
valuePlaceholder: s__('CiVariables|Input variable value'),
},
+ variableValueKeys: {
+ rest: 'secret_value',
+ gql: 'value',
+ },
data() {
return {
job: {},
@@ -90,14 +100,29 @@ export default {
},
],
runAgainBtnDisabled: false,
+ triggerBtnDisabled: false,
};
},
computed: {
+ preparedVariables() {
+ // filtering out 'id' along with empty variables to send only key, value in the mutation.
+ // This will be removed in: https://gitlab.com/gitlab-org/gitlab/-/issues/377268
+
+ return this.variables
+ .filter((variable) => variable.key !== '')
+ .map(({ key, value }) => ({ key, [this.valueKey]: value }));
+ },
+ valueKey() {
+ return this.isRetryable
+ ? this.$options.variableValueKeys.gql
+ : this.$options.variableValueKeys.rest;
+ },
variableSettings() {
return helpPagePath('ci/variables/index', { anchor: 'add-a-cicd-variable-to-a-project' });
},
},
methods: {
+ ...mapActions(['triggerManualJob']),
addEmptyVariable() {
const lastVar = this.variables[this.variables.length - 1];
@@ -128,18 +153,12 @@ export default {
},
async retryJob() {
try {
- // filtering out 'id' along with empty variables to send only key, value in the mutation.
- // This will be removed in: https://gitlab.com/gitlab-org/gitlab/-/issues/377268
- const preparedVariables = this.variables
- .filter((variable) => variable.key !== '')
- .map(({ key, value }) => ({ key, value }));
-
const { data } = await this.$apollo.mutate({
mutation: retryJobWithVariablesMutation,
variables: {
id: convertToGraphQLId(GRAPHQL_ID_TYPES.ciBuild, this.jobId),
// we need to ensure no empty variables are passed to the API
- variables: preparedVariables,
+ variables: this.preparedVariables,
},
});
if (data.jobRetry?.errors?.length) {
@@ -156,6 +175,11 @@ export default {
this.retryJob();
},
+ triggerJob() {
+ this.triggerBtnDisabled = true;
+
+ this.triggerManualJob(this.preparedVariables);
+ },
},
};
</script>
@@ -226,7 +250,7 @@ export default {
</template>
</gl-sprintf>
</div>
- <div class="gl-display-flex gl-justify-content-center gl-mt-5">
+ <div v-if="isRetryable" class="gl-display-flex gl-justify-content-center gl-mt-5">
<gl-button
class="gl-mt-5"
:aria-label="__('Cancel')"
@@ -246,6 +270,19 @@ export default {
{{ $options.i18n.runAgainButtonText }}
</gl-button>
</div>
+ <div v-else class="gl-display-flex gl-justify-content-center gl-mt-5">
+ <gl-button
+ class="gl-mt-5"
+ variant="confirm"
+ category="primary"
+ :aria-label="__('Trigger manual job')"
+ :disabled="triggerBtnDisabled"
+ data-testid="trigger-manual-job-btn"
+ @click="triggerJob"
+ >
+ {{ $options.i18n.triggerButtonText }}
+ </gl-button>
+ </div>
</div>
</div>
</template>
diff --git a/app/assets/javascripts/jobs/components/job/sidebar/job_sidebar_retry_button.vue b/app/assets/javascripts/jobs/components/job/sidebar/job_sidebar_retry_button.vue
index 65175df555a..7183a8b5d03 100644
--- a/app/assets/javascripts/jobs/components/job/sidebar/job_sidebar_retry_button.vue
+++ b/app/assets/javascripts/jobs/components/job/sidebar/job_sidebar_retry_button.vue
@@ -1,7 +1,6 @@
<script>
import { GlButton, GlDropdown, GlDropdownItem, GlModalDirective } from '@gitlab/ui';
import { mapGetters } from 'vuex';
-import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { JOB_SIDEBAR_COPY } from '~/jobs/constants';
export default {
@@ -17,7 +16,6 @@ export default {
directives: {
GlModal: GlModalDirective,
},
- mixins: [glFeatureFlagsMixin()],
props: {
modalId: {
type: String,
@@ -34,9 +32,6 @@ export default {
},
computed: {
...mapGetters(['hasForwardDeploymentFailure']),
- showRetryDropdown() {
- return this.glFeatures?.graphqlJobApp && this.isManualJob;
- },
},
};
</script>
@@ -51,7 +46,7 @@ export default {
data-testid="retry-job-button"
/>
<gl-dropdown
- v-else-if="showRetryDropdown"
+ v-else-if="isManualJob"
icon="retry"
category="primary"
:right="true"
diff --git a/app/assets/javascripts/jobs/components/job/sidebar/legacy_job_sidebar_retry_button.vue b/app/assets/javascripts/jobs/components/job/sidebar/legacy_job_sidebar_retry_button.vue
deleted file mode 100644
index 8a821b69f8c..00000000000
--- a/app/assets/javascripts/jobs/components/job/sidebar/legacy_job_sidebar_retry_button.vue
+++ /dev/null
@@ -1,53 +0,0 @@
-<script>
-import { GlButton, GlModalDirective } from '@gitlab/ui';
-import { mapGetters } from 'vuex';
-import { JOB_SIDEBAR_COPY } from '~/jobs/constants';
-
-export default {
- name: 'LegacyJobSidebarRetryButton',
- i18n: {
- retryLabel: JOB_SIDEBAR_COPY.retryJobLabel,
- },
- components: {
- GlButton,
- },
- directives: {
- GlModal: GlModalDirective,
- },
- props: {
- modalId: {
- type: String,
- required: true,
- },
- href: {
- type: String,
- required: true,
- },
- },
- computed: {
- ...mapGetters(['hasForwardDeploymentFailure']),
- },
-};
-</script>
-<template>
- <gl-button
- v-if="hasForwardDeploymentFailure"
- v-gl-modal="modalId"
- :aria-label="$options.i18n.retryLabel"
- category="primary"
- variant="confirm"
- icon="retry"
- data-testid="retry-job-button"
- />
-
- <gl-button
- v-else
- :href="href"
- :aria-label="$options.i18n.retryLabel"
- category="primary"
- variant="confirm"
- icon="retry"
- data-method="post"
- data-testid="retry-job-link"
- />
-</template>
diff --git a/app/assets/javascripts/jobs/components/job/sidebar/legacy_sidebar_header.vue b/app/assets/javascripts/jobs/components/job/sidebar/legacy_sidebar_header.vue
deleted file mode 100644
index 5bbb831a293..00000000000
--- a/app/assets/javascripts/jobs/components/job/sidebar/legacy_sidebar_header.vue
+++ /dev/null
@@ -1,98 +0,0 @@
-<script>
-import { GlButton, GlTooltipDirective } from '@gitlab/ui';
-import { mapActions } from 'vuex';
-import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue';
-import { JOB_SIDEBAR_COPY, forwardDeploymentFailureModalId, PASSED_STATUS } from '~/jobs/constants';
-import JobSidebarRetryButton from './job_sidebar_retry_button.vue';
-
-export default {
- name: 'LegacySidebarHeader',
- i18n: {
- ...JOB_SIDEBAR_COPY,
- },
- forwardDeploymentFailureModalId,
- directives: {
- GlTooltip: GlTooltipDirective,
- },
- components: {
- GlButton,
- JobSidebarRetryButton,
- TooltipOnTruncate,
- },
- props: {
- job: {
- type: Object,
- required: true,
- default: () => ({}),
- },
- },
- computed: {
- retryButtonCategory() {
- return this.job.status && this.job.recoverable ? 'primary' : 'secondary';
- },
- buttonTitle() {
- return this.job.status && this.job.status.text === PASSED_STATUS
- ? this.$options.i18n.runAgainJobButtonLabel
- : this.$options.i18n.retryJobLabel;
- },
- },
- methods: {
- ...mapActions(['toggleSidebar']),
- },
-};
-</script>
-
-<template>
- <div class="gl-py-5 gl-display-flex gl-align-items-center">
- <tooltip-on-truncate :title="job.name" truncate-target="child"
- ><h4 class="gl-my-0 gl-mr-3 gl-text-truncate">{{ job.name }}</h4>
- </tooltip-on-truncate>
- <div class="gl-flex-grow-1 gl-flex-shrink-0 gl-text-right">
- <gl-button
- v-if="job.erase_path"
- v-gl-tooltip.left
- :title="$options.i18n.eraseLogButtonLabel"
- :aria-label="$options.i18n.eraseLogButtonLabel"
- :href="job.erase_path"
- :data-confirm="$options.i18n.eraseLogConfirmText"
- class="gl-mr-2"
- data-testid="job-log-erase-link"
- data-confirm-btn-variant="danger"
- data-method="post"
- icon="remove"
- />
- <job-sidebar-retry-button
- v-if="job.retry_path"
- v-gl-tooltip.left
- :title="buttonTitle"
- :aria-label="buttonTitle"
- :category="retryButtonCategory"
- :href="job.retry_path"
- :modal-id="$options.forwardDeploymentFailureModalId"
- :is-manual-job="false"
- variant="confirm"
- data-qa-selector="retry_button"
- data-testid="retry-button"
- />
- <gl-button
- v-if="job.cancel_path"
- v-gl-tooltip.left
- :title="$options.i18n.cancelJobButtonLabel"
- :aria-label="$options.i18n.cancelJobButtonLabel"
- :href="job.cancel_path"
- variant="danger"
- icon="cancel"
- data-method="post"
- data-testid="cancel-button"
- rel="nofollow"
- />
- <gl-button
- :aria-label="$options.i18n.toggleSidebar"
- category="tertiary"
- class="gl-md-display-none gl-ml-2"
- icon="chevron-double-lg-right"
- @click="toggleSidebar"
- />
- </div>
- </div>
-</template>
diff --git a/app/assets/javascripts/jobs/components/job/sidebar/sidebar.vue b/app/assets/javascripts/jobs/components/job/sidebar/sidebar.vue
index 02c3d60557b..69271cc9022 100644
--- a/app/assets/javascripts/jobs/components/job/sidebar/sidebar.vue
+++ b/app/assets/javascripts/jobs/components/job/sidebar/sidebar.vue
@@ -3,13 +3,11 @@ import { GlButton, GlIcon } from '@gitlab/ui';
import { isEmpty } from 'lodash';
import { mapActions, mapGetters, mapState } from 'vuex';
import { JOB_SIDEBAR_COPY, forwardDeploymentFailureModalId } from '~/jobs/constants';
-import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import ArtifactsBlock from './artifacts_block.vue';
import CommitBlock from './commit_block.vue';
import JobsContainer from './jobs_container.vue';
import JobRetryForwardDeploymentModal from './job_retry_forward_deployment_modal.vue';
import JobSidebarDetailsContainer from './sidebar_job_details_container.vue';
-import LegacySidebarHeader from './legacy_sidebar_header.vue';
import SidebarHeader from './sidebar_header.vue';
import StagesDropdown from './stages_dropdown.vue';
import TriggerBlock from './trigger_block.vue';
@@ -29,12 +27,10 @@ export default {
JobsContainer,
JobRetryForwardDeploymentModal,
JobSidebarDetailsContainer,
- LegacySidebarHeader,
SidebarHeader,
StagesDropdown,
TriggerBlock,
},
- mixins: [glFeatureFlagsMixin()],
props: {
artifactHelpUrl: {
type: String,
@@ -52,9 +48,6 @@ export default {
hasTriggers() {
return !isEmpty(this.job.trigger);
},
- isGraphQL() {
- return this.glFeatures?.graphqlJobApp;
- },
commit() {
return this.job?.pipeline?.commit || {};
},
@@ -85,12 +78,10 @@ export default {
<div class="sidebar-container">
<div class="blocks-container">
<sidebar-header
- v-if="isGraphQL"
:rest-job="job"
:job-id="job.id"
@updateVariables="$emit('updateVariables')"
/>
- <legacy-sidebar-header v-else :job="job" />
<div
v-if="job.terminal_path || job.new_issue_path"
class="gl-py-5"
diff --git a/app/assets/javascripts/jobs/components/job/sidebar/sidebar_header.vue b/app/assets/javascripts/jobs/components/job/sidebar/sidebar_header.vue
index c124f52ae79..40aec0b0536 100644
--- a/app/assets/javascripts/jobs/components/job/sidebar/sidebar_header.vue
+++ b/app/assets/javascripts/jobs/components/job/sidebar/sidebar_header.vue
@@ -14,9 +14,6 @@ import {
import GetJob from '../graphql/queries/get_job.query.graphql';
import JobSidebarRetryButton from './job_sidebar_retry_button.vue';
-// This component is a port of ~/jobs/components/job/sidebar/legacy_sidebar_header.vue
-// It is meant to fetch the job information via GraphQL instead of REST API.
-
export default {
name: 'SidebarHeader',
i18n: {
diff --git a/app/assets/javascripts/pages/projects/environments/show/index.js b/app/assets/javascripts/pages/projects/environments/show/index.js
index 53e48ad8d86..1ce8899ac63 100644
--- a/app/assets/javascripts/pages/projects/environments/show/index.js
+++ b/app/assets/javascripts/pages/projects/environments/show/index.js
@@ -1,5 +1,6 @@
import initConfirmRollBackModal from '~/environments/init_confirm_rollback_modal';
-import { initHeader } from '~/environments/mount_show';
+import { initHeader, initPage } from '~/environments/mount_show';
initHeader();
+initPage();
initConfirmRollBackModal();
diff --git a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
index 79a9bbb84c2..5fa3288bbef 100644
--- a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
+++ b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
@@ -38,7 +38,6 @@ export default {
issuesLabel: s__('ProjectSettings|Issues'),
lfsLabel: s__('ProjectSettings|Git Large File Storage (LFS)'),
mergeRequestsLabel: s__('ProjectSettings|Merge requests'),
- operationsLabel: s__('ProjectSettings|Operations'),
environmentsLabel: s__('ProjectSettings|Environments'),
environmentsHelpText: s__(
'ProjectSettings|Every project can make deployments to environments either via CI/CD or API calls. Non-project members have read-only access.',
@@ -259,7 +258,6 @@ export default {
analyticsAccessLevel: featureAccessLevel.EVERYONE,
requirementsAccessLevel: featureAccessLevel.EVERYONE,
securityAndComplianceAccessLevel: featureAccessLevel.PROJECT_MEMBERS,
- operationsAccessLevel: featureAccessLevel.EVERYONE,
environmentsAccessLevel: featureAccessLevel.EVERYONE,
featureFlagsAccessLevel: featureAccessLevel.PROJECT_MEMBERS,
infrastructureAccessLevel: featureAccessLevel.PROJECT_MEMBERS,
@@ -316,10 +314,6 @@ export default {
return options;
},
- operationsEnabled() {
- return this.operationsAccessLevel > featureAccessLevel.NOT_ENABLED;
- },
-
environmentsEnabled() {
return this.environmentsAccessLevel > featureAccessLevel.NOT_ENABLED;
},
@@ -373,16 +367,8 @@ export default {
packageRegistryApiForEveryoneEnabledShown() {
return this.visibilityLevel !== VISIBILITY_LEVEL_PUBLIC_INTEGER;
},
- splitOperationsEnabled() {
- return this.glFeatures.splitOperationsVisibilityPermissions;
- },
monitorOperationsFeatureAccessLevelOptions() {
- if (this.splitOperationsEnabled) {
- return this.featureAccessLevelOptions.filter(([value]) => value <= this.monitorAccessLevel);
- }
- return this.featureAccessLevelOptions.filter(
- ([value]) => value <= this.operationsAccessLevel,
- );
+ return this.featureAccessLevelOptions.filter(([value]) => value <= this.monitorAccessLevel);
},
},
@@ -436,10 +422,6 @@ export default {
featureAccessLevel.PROJECT_MEMBERS,
this.securityAndComplianceAccessLevel,
);
- this.operationsAccessLevel = Math.min(
- featureAccessLevel.PROJECT_MEMBERS,
- this.operationsAccessLevel,
- );
this.environmentsAccessLevel = Math.min(
featureAccessLevel.PROJECT_MEMBERS,
this.environmentsAccessLevel,
@@ -498,8 +480,6 @@ export default {
this.metricsDashboardAccessLevel = featureAccessLevel.EVERYONE;
if (this.requirementsAccessLevel === featureAccessLevel.PROJECT_MEMBERS)
this.requirementsAccessLevel = featureAccessLevel.EVERYONE;
- if (this.operationsAccessLevel === featureAccessLevel.PROJECT_MEMBERS)
- this.operationsAccessLevel = featureAccessLevel.EVERYONE;
if (this.environmentsAccessLevel === featureAccessLevel.PROJECT_MEMBERS)
this.environmentsAccessLevel = featureAccessLevel.EVERYONE;
if (this.monitorAccessLevel === featureAccessLevel.PROJECT_MEMBERS)
@@ -538,10 +518,6 @@ export default {
toggleHiddenClassBySelector('.merge-requests-feature', false);
},
- operationsAccessLevel(value, oldValue) {
- this.updateSubFeatureAccessLevel(value, oldValue);
- },
-
monitorAccessLevel(value, oldValue) {
this.updateSubFeatureAccessLevel(value, oldValue);
},
@@ -971,7 +947,6 @@ export default {
/>
</project-setting-row>
<project-setting-row
- v-if="splitOperationsEnabled"
ref="monitor-settings"
:label="$options.i18n.monitorLabel"
:help-text="
@@ -985,21 +960,6 @@ export default {
name="project[project_feature_attributes][monitor_access_level]"
/>
</project-setting-row>
- <project-setting-row
- v-else
- ref="operations-settings"
- :label="$options.i18n.operationsLabel"
- :help-text="
- s__('ProjectSettings|Configure your project resources and monitor their health.')
- "
- >
- <project-feature-setting
- v-model="operationsAccessLevel"
- :label="$options.i18n.operationsLabel"
- :options="featureAccessLevelOptions"
- name="project[project_feature_attributes][operations_access_level]"
- />
- </project-setting-row>
<div class="project-feature-setting-group gl-pl-7 gl-sm-pl-5">
<project-setting-row
ref="metrics-visibility-settings"
@@ -1014,47 +974,45 @@ export default {
/>
</project-setting-row>
</div>
- <template v-if="splitOperationsEnabled">
- <project-setting-row
- ref="environments-settings"
+ <project-setting-row
+ ref="environments-settings"
+ :label="$options.i18n.environmentsLabel"
+ :help-text="$options.i18n.environmentsHelpText"
+ :help-path="environmentsHelpPath"
+ >
+ <project-feature-setting
+ v-model="environmentsAccessLevel"
:label="$options.i18n.environmentsLabel"
- :help-text="$options.i18n.environmentsHelpText"
- :help-path="environmentsHelpPath"
- >
- <project-feature-setting
- v-model="environmentsAccessLevel"
- :label="$options.i18n.environmentsLabel"
- :options="featureAccessLevelOptions"
- name="project[project_feature_attributes][environments_access_level]"
- />
- </project-setting-row>
- <project-setting-row
- ref="feature-flags-settings"
+ :options="featureAccessLevelOptions"
+ name="project[project_feature_attributes][environments_access_level]"
+ />
+ </project-setting-row>
+ <project-setting-row
+ ref="feature-flags-settings"
+ :label="$options.i18n.featureFlagsLabel"
+ :help-text="$options.i18n.featureFlagsHelpText"
+ :help-path="featureFlagsHelpPath"
+ >
+ <project-feature-setting
+ v-model="featureFlagsAccessLevel"
:label="$options.i18n.featureFlagsLabel"
- :help-text="$options.i18n.featureFlagsHelpText"
- :help-path="featureFlagsHelpPath"
- >
- <project-feature-setting
- v-model="featureFlagsAccessLevel"
- :label="$options.i18n.featureFlagsLabel"
- :options="featureAccessLevelOptions"
- name="project[project_feature_attributes][feature_flags_access_level]"
- />
- </project-setting-row>
- <project-setting-row
- ref="infrastructure-settings"
+ :options="featureAccessLevelOptions"
+ name="project[project_feature_attributes][feature_flags_access_level]"
+ />
+ </project-setting-row>
+ <project-setting-row
+ ref="infrastructure-settings"
+ :label="$options.i18n.infrastructureLabel"
+ :help-text="$options.i18n.infrastructureHelpText"
+ :help-path="infrastructureHelpPath"
+ >
+ <project-feature-setting
+ v-model="infrastructureAccessLevel"
:label="$options.i18n.infrastructureLabel"
- :help-text="$options.i18n.infrastructureHelpText"
- :help-path="infrastructureHelpPath"
- >
- <project-feature-setting
- v-model="infrastructureAccessLevel"
- :label="$options.i18n.infrastructureLabel"
- :options="featureAccessLevelOptions"
- name="project[project_feature_attributes][infrastructure_access_level]"
- />
- </project-setting-row>
- </template>
+ :options="featureAccessLevelOptions"
+ name="project[project_feature_attributes][infrastructure_access_level]"
+ />
+ </project-setting-row>
<project-setting-row
ref="releases-settings"
:label="$options.i18n.releasesLabel"
diff --git a/app/assets/javascripts/pipelines/components/test_reports/test_reports.vue b/app/assets/javascripts/pipelines/components/test_reports/test_reports.vue
index e5666f7a658..3f2c013d44a 100644
--- a/app/assets/javascripts/pipelines/components/test_reports/test_reports.vue
+++ b/app/assets/javascripts/pipelines/components/test_reports/test_reports.vue
@@ -1,8 +1,6 @@
<script>
import { GlLoadingIcon } from '@gitlab/ui';
import { mapActions, mapGetters, mapState } from 'vuex';
-import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
-import createTestReportsStore from '../../stores/test_reports';
import EmptyState from './empty_state.vue';
import TestSuiteTable from './test_suite_table.vue';
import TestSummary from './test_summary.vue';
@@ -17,7 +15,6 @@ export default {
TestSummary,
TestSummaryTable,
},
- mixins: [glFeatureFlagMixin()],
inject: ['blobPath', 'summaryEndpoint', 'suiteEndpoint'],
computed: {
...mapState('testReports', ['isLoading', 'selectedSuiteIndex', 'testReports']),
@@ -31,17 +28,6 @@ export default {
},
},
created() {
- if (!this.glFeatures.pipelineTabsVue) {
- this.$store.registerModule(
- 'testReports',
- createTestReportsStore({
- blobPath: this.blobPath,
- summaryEndpoint: this.summaryEndpoint,
- suiteEndpoint: this.suiteEndpoint,
- }),
- );
- }
-
this.fetchSummary();
},
methods: {
diff --git a/app/controllers/dashboard/todos_controller.rb b/app/controllers/dashboard/todos_controller.rb
index c904f09d1bb..3005d19f8ed 100644
--- a/app/controllers/dashboard/todos_controller.rb
+++ b/app/controllers/dashboard/todos_controller.rb
@@ -3,6 +3,7 @@
class Dashboard::TodosController < Dashboard::ApplicationController
include ActionView::Helpers::NumberHelper
include PaginatedCollection
+ include Gitlab::Utils::StrongMemoize
before_action :authorize_read_project!, only: :index
before_action :authorize_read_group!, only: :index
@@ -99,14 +100,28 @@ class Dashboard::TodosController < Dashboard::ApplicationController
end
def todo_params
- aliased_action_id(
+ aliased_params(
params.permit(:action_id, :author_id, :project_id, :type, :sort, :state, :group_id)
)
end
+ strong_memoize_attr :todo_params
- def aliased_action_id(original_params)
- return original_params unless original_params[:action_id].to_i == ::Todo::MENTIONED
+ def aliased_params(original_params)
+ alias_issue_type(original_params)
+ alias_action_id(original_params)
- original_params.merge(action_id: [::Todo::MENTIONED, ::Todo::DIRECTLY_ADDRESSED])
+ original_params
+ end
+
+ def alias_issue_type(original_params)
+ return unless original_params[:type] == Issue.name
+
+ original_params[:type] = [Issue.name, WorkItem.name]
+ end
+
+ def alias_action_id(original_params)
+ return unless original_params[:action_id].to_i == ::Todo::MENTIONED
+
+ original_params[:action_id] = [::Todo::MENTIONED, ::Todo::DIRECTLY_ADDRESSED]
end
end
diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb
index 5b1dbe83f6d..537fd3854c4 100644
--- a/app/controllers/projects/environments_controller.rb
+++ b/app/controllers/projects/environments_controller.rb
@@ -16,6 +16,9 @@ class Projects::EnvironmentsController < Projects::ApplicationController
authorize_metrics_dashboard!
end
+ before_action only: [:show] do
+ push_frontend_feature_flag(:environment_details_vue, @project)
+ end
before_action :authorize_read_environment!, except: [:metrics, :additional_metrics, :metrics_dashboard, :metrics_redirect]
before_action :authorize_create_environment!, only: [:new, :create]
before_action :authorize_stop_environment!, only: [:stop]
diff --git a/app/controllers/projects/jobs_controller.rb b/app/controllers/projects/jobs_controller.rb
index 557ac566733..c6d442a6f27 100644
--- a/app/controllers/projects/jobs_controller.rb
+++ b/app/controllers/projects/jobs_controller.rb
@@ -20,9 +20,6 @@ class Projects::JobsController < Projects::ApplicationController
before_action :verify_proxy_request!, only: :proxy_websocket_authorize
before_action :push_job_log_jump_to_failures, only: [:show]
before_action :reject_if_build_artifacts_size_refreshing!, only: [:erase]
- before_action do
- push_frontend_feature_flag(:graphql_job_app, project, type: :development)
- end
layout 'project'
diff --git a/app/controllers/projects/settings/repository_controller.rb b/app/controllers/projects/settings/repository_controller.rb
index 90988645d3a..6d099aa8b3d 100644
--- a/app/controllers/projects/settings/repository_controller.rb
+++ b/app/controllers/projects/settings/repository_controller.rb
@@ -95,6 +95,14 @@ module Projects
@protected_tags_count = @protected_tags.reduce(0) { |sum, tag| sum + tag.matching(@project.repository.tag_names).size }
+ if Feature.enabled?(:group_protected_branches)
+ @protected_group_branches = if @project.root_namespace.is_a?(Group)
+ @project.root_namespace.protected_branches.order(:name).page(params[:page])
+ else
+ []
+ end
+ end
+
load_gon_index
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index fd0b1e916b7..886819fe778 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -48,10 +48,6 @@ class ProjectsController < Projects::ApplicationController
push_frontend_feature_flag(:package_registry_access_level)
end
- before_action only: :edit do
- push_frontend_feature_flag(:split_operations_visibility_permissions, @project)
- end
-
layout :determine_layout
feature_category :projects, [
@@ -433,17 +429,11 @@ class ProjectsController < Projects::ApplicationController
security_and_compliance_access_level
container_registry_access_level
releases_access_level
- ] + operations_feature_attributes
- end
-
- def operations_feature_attributes
- if Feature.enabled?(:split_operations_visibility_permissions, project)
- %i[
- environments_access_level feature_flags_access_level monitor_access_level infrastructure_access_level
- ]
- else
- %i[operations_access_level]
- end
+ environments_access_level
+ feature_flags_access_level
+ monitor_access_level
+ infrastructure_access_level
+ ]
end
def project_setting_attributes
diff --git a/app/finders/todos_finder.rb b/app/finders/todos_finder.rb
index 31287f63eb2..0bf31ea33dd 100644
--- a/app/finders/todos_finder.rb
+++ b/app/finders/todos_finder.rb
@@ -24,7 +24,7 @@ class TodosFinder
NONE = '0'
- TODO_TYPES = Set.new(%w[Issue MergeRequest DesignManagement::Design AlertManagement::Alert]).freeze
+ TODO_TYPES = Set.new(%w[Issue WorkItem MergeRequest DesignManagement::Design AlertManagement::Alert]).freeze
attr_accessor :current_user, :params
diff --git a/app/graphql/types/ci/runner_type.rb b/app/graphql/types/ci/runner_type.rb
index de7329ee661..5d34906f7b8 100644
--- a/app/graphql/types/ci/runner_type.rb
+++ b/app/graphql/types/ci/runner_type.rb
@@ -113,7 +113,7 @@ module Types
runners_tbl = ::Ci::Runner.arel_table
lateral_query = ::Ci::Build.select(1)
.where(builds_tbl['runner_id'].eq(runners_tbl['id']))
- .limit(JOB_COUNT_LIMIT)
+ .limit(JOB_COUNT_LIMIT + 1)
counts = ::Ci::Runner.joins("JOIN LATERAL (#{lateral_query.to_sql}) builds_with_limit ON true")
.id_in(runner_ids)
.select(:id, Arel.star.count.as('count'))
diff --git a/app/helpers/environment_helper.rb b/app/helpers/environment_helper.rb
index b6997b6fb70..0e64a98c9da 100644
--- a/app/helpers/environment_helper.rb
+++ b/app/helpers/environment_helper.rb
@@ -72,6 +72,7 @@ module EnvironmentHelper
{
name: environment.name,
id: environment.id,
+ project_full_path: project.full_path,
external_url: environment.external_url,
can_update_environment: can?(current_user, :update_environment, environment),
can_destroy_environment: can_destroy_environment?(environment),
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index a4d7299075f..8a535b557b5 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -686,7 +686,6 @@ module ProjectsHelper
lfsEnabled: !!project.lfs_enabled,
emailsDisabled: project.emails_disabled?,
metricsDashboardAccessLevel: feature.metrics_dashboard_access_level,
- operationsAccessLevel: feature.operations_access_level,
monitorAccessLevel: feature.monitor_access_level,
showDefaultAwardEmojis: project.show_default_award_emojis?,
warnAboutPotentiallyUnwantedCharacters: project.warn_about_potentially_unwanted_characters?,
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index aaa44b2ff54..7f67e80e432 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -369,29 +369,12 @@ class ProjectPolicy < BasePolicy
prevent(:metrics_dashboard)
end
- condition(:split_operations_visibility_permissions) do
- ::Feature.enabled?(:split_operations_visibility_permissions, @subject)
- end
-
- rule { ~split_operations_visibility_permissions & operations_disabled }.policy do
- prevent(*create_read_update_admin_destroy(:feature_flag))
- prevent(*create_read_update_admin_destroy(:environment))
- prevent(*create_read_update_admin_destroy(:sentry_issue))
- prevent(*create_read_update_admin_destroy(:alert_management_alert))
- prevent(*create_read_update_admin_destroy(:cluster))
- prevent(*create_read_update_admin_destroy(:terraform_state))
- prevent(*create_read_update_admin_destroy(:deployment))
- prevent(:metrics_dashboard)
- prevent(:read_pod_logs)
- prevent(:read_prometheus)
- end
-
- rule { split_operations_visibility_permissions & environments_disabled }.policy do
+ rule { environments_disabled }.policy do
prevent(*create_read_update_admin_destroy(:environment))
prevent(*create_read_update_admin_destroy(:deployment))
end
- rule { split_operations_visibility_permissions & feature_flags_disabled }.policy do
+ rule { feature_flags_disabled }.policy do
prevent(*create_read_update_admin_destroy(:feature_flag))
prevent(:admin_feature_flags_user_lists)
prevent(:admin_feature_flags_client)
@@ -401,13 +384,13 @@ class ProjectPolicy < BasePolicy
prevent(*create_read_update_admin_destroy(:release))
end
- rule { split_operations_visibility_permissions & monitor_disabled }.policy do
+ rule { monitor_disabled }.policy do
prevent(:metrics_dashboard)
prevent(*create_read_update_admin_destroy(:sentry_issue))
prevent(*create_read_update_admin_destroy(:alert_management_alert))
end
- rule { split_operations_visibility_permissions & infrastructure_disabled }.policy do
+ rule { infrastructure_disabled }.policy do
prevent(*create_read_update_admin_destroy(:terraform_state))
prevent(*create_read_update_admin_destroy(:cluster))
prevent(:read_pod_logs)
diff --git a/app/services/merge_requests/after_create_service.rb b/app/services/merge_requests/after_create_service.rb
index 20b32dbc2a0..9e39aa94246 100644
--- a/app/services/merge_requests/after_create_service.rb
+++ b/app/services/merge_requests/after_create_service.rb
@@ -22,7 +22,7 @@ module MergeRequests
def prepare_merge_request(merge_request)
event_service.open_mr(merge_request, current_user)
- merge_request_activity_counter.track_create_mr_action(user: current_user)
+ merge_request_activity_counter.track_create_mr_action(user: current_user, merge_request: merge_request)
merge_request_activity_counter.track_mr_including_ci_config(user: current_user, merge_request: merge_request)
notification_service.new_merge_request(merge_request, current_user)
diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb
index f02bbdbee60..301d11d841c 100644
--- a/app/services/projects/update_service.rb
+++ b/app/services/projects/update_service.rb
@@ -10,7 +10,6 @@ module Projects
def execute
build_topics
remove_unallowed_params
- mirror_operations_access_level_changes
validate!
ensure_wiki_exists if enabling_wiki?
@@ -103,21 +102,6 @@ module Projects
params.delete(:emails_disabled) unless can?(current_user, :set_emails_disabled, project)
end
- # Temporary code to sync permissions changes as operations access setting
- # is being split into monitor_access_level, deployments_access_level, infrastructure_access_level.
- # To be removed as part of https://gitlab.com/gitlab-org/gitlab/-/issues/364240
- def mirror_operations_access_level_changes
- return if Feature.enabled?(:split_operations_visibility_permissions, project)
-
- operations_access_level = params.dig(:project_feature_attributes, :operations_access_level)
-
- return if operations_access_level.nil?
-
- [:monitor_access_level, :infrastructure_access_level, :feature_flags_access_level, :environments_access_level].each do |key|
- params[:project_feature_attributes][key] = operations_access_level
- end
- end
-
def after_update
todos_features_changes = %w(
issues_access_level
diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml
index a907e175443..87a6b54d697 100644
--- a/app/views/projects/blob/_editor.html.haml
+++ b/app/views/projects/blob/_editor.html.haml
@@ -28,7 +28,8 @@
.file-buttons.gl-display-flex.gl-align-items-center.gl-justify-content-end
- if is_markdown
- = render 'shared/blob/markdown_buttons', show_fullscreen_button: false, supports_file_upload: false
+ - unless Feature.enabled?(:source_editor_toolbar, current_user)
+ = render 'shared/blob/markdown_buttons', show_fullscreen_button: false, supports_file_upload: false
%span.soft-wrap-toggle
= render Pajamas::ButtonComponent.new(icon: 'soft-unwrap', button_options: { class: 'no-wrap' }) do
= _("No wrap")
diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml
index 6d60ef92d86..53b2af88511 100644
--- a/app/views/projects/environments/show.html.haml
+++ b/app/views/projects/environments/show.html.haml
@@ -8,28 +8,31 @@
#environments-detail-view{ data: { details: environments_detail_data_json(current_user, @project, @environment) } }
#environments-detail-view-header
- .environments-container
- - if @deployments.blank?
- .empty-state
- .text-content
- %h4.state-title
- = _("You don't have any deployments right now.")
- %p
- = html_escape(_("Define environments in the deploy stage(s) in %{code_open}.gitlab-ci.yml%{code_close} to track deployments here.")) % { code_open: '<code>'.html_safe, code_close: '</code>'.html_safe }
- .text-center
- = link_to _("Read more"), help_page_path("ci/environments/index.md"), class: "gl-button btn btn-confirm"
- - else
- .table-holder.gl-overflow-visible
- .ci-table.environments{ role: 'grid' }
- .gl-responsive-table-row.table-row-header{ role: 'row' }
- .table-section.section-15{ role: 'columnheader' }= _('Status')
- .table-section.section-10{ role: 'columnheader' }= _('ID')
- .table-section.section-10{ role: 'columnheader' }= _('Triggerer')
- .table-section.section-25{ role: 'columnheader' }= _('Commit')
- .table-section.section-10{ role: 'columnheader' }= _('Job')
- .table-section.section-10{ role: 'columnheader' }= _('Created')
- .table-section.section-10{ role: 'columnheader' }= _('Deployed')
+ - if Feature.enabled?(:environment_details_vue, @project)
+ #environment_details_page
+ - else
+ .environments-container
+ - if @deployments.blank?
+ .empty-state
+ .text-content
+ %h4.state-title
+ = _("You don't have any deployments right now.")
+ %p
+ = html_escape(_("Define environments in the deploy stage(s) in %{code_open}.gitlab-ci.yml%{code_close} to track deployments here.")) % { code_open: '<code>'.html_safe, code_close: '</code>'.html_safe }
+ .text-center
+ = link_to _("Read more"), help_page_path("ci/environments/index.md"), class: "gl-button btn btn-confirm"
+ - else
+ .table-holder.gl-overflow-visible
+ .ci-table.environments{ role: 'grid' }
+ .gl-responsive-table-row.table-row-header{ role: 'row' }
+ .table-section.section-15{ role: 'columnheader' }= _('Status')
+ .table-section.section-10{ role: 'columnheader' }= _('ID')
+ .table-section.section-10{ role: 'columnheader' }= _('Triggerer')
+ .table-section.section-25{ role: 'columnheader' }= _('Commit')
+ .table-section.section-10{ role: 'columnheader' }= _('Job')
+ .table-section.section-10{ role: 'columnheader' }= _('Created')
+ .table-section.section-10{ role: 'columnheader' }= _('Deployed')
- = render @deployments
+ = render @deployments
- = paginate @deployments, theme: 'gitlab'
+ = paginate @deployments, theme: 'gitlab'
diff --git a/config/events/1671198983_Gitlab__UsageDataCounters__MergeRequestActivityUniqueCounter_create.yml b/config/events/1671198983_Gitlab__UsageDataCounters__MergeRequestActivityUniqueCounter_create.yml
new file mode 100644
index 00000000000..9c1b389b4b2
--- /dev/null
+++ b/config/events/1671198983_Gitlab__UsageDataCounters__MergeRequestActivityUniqueCounter_create.yml
@@ -0,0 +1,27 @@
+---
+description: Count of unique merge requests created per month
+category: Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter
+action: create
+label_description: "Mirrored RedisHLL i_code_review_user_create_mr_monthly events sent to Snowplow"
+label_description:
+property_description:
+value_description:
+extra_properties:
+identifiers:
+- project
+- user
+- namespace
+product_section: dev
+product_stage: create
+product_group: code_review
+product_category: code_review
+milestone: "15.7"
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/106869
+distributions:
+- ce
+- ee
+tiers:
+- free
+- premium
+- ultimate
+
diff --git a/config/feature_flags/development/environment_details_vue.yml b/config/feature_flags/development/environment_details_vue.yml
new file mode 100644
index 00000000000..5a647f65a7a
--- /dev/null
+++ b/config/feature_flags/development/environment_details_vue.yml
@@ -0,0 +1,8 @@
+---
+name: environment_details_vue
+introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/105527"
+rollout_issue_url: "https://gitlab.com/gitlab-org/gitlab/-/issues/384914"
+milestone: '15.7'
+type: development
+group: group::release
+default_enabled: false
diff --git a/config/feature_flags/development/graphql_job_app.yml b/config/feature_flags/development/graphql_job_app.yml
deleted file mode 100644
index a0f0cb71e17..00000000000
--- a/config/feature_flags/development/graphql_job_app.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: graphql_job_app
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/96703
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/372276
-milestone: '15.4'
-type: development
-group: group::pipeline authoring
-default_enabled: false
diff --git a/config/feature_flags/development/split_operations_visibility_permissions.yml b/config/feature_flags/development/split_operations_visibility_permissions.yml
deleted file mode 100644
index 56955733217..00000000000
--- a/config/feature_flags/development/split_operations_visibility_permissions.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: split_operations_visibility_permissions
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/89089
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/364240
-milestone: '15.1'
-type: development
-group: group::respond
-default_enabled: true
diff --git a/config/metrics/counts_28d/20221213182900_i_code_review_create_mr_monthly.yml b/config/metrics/counts_28d/20221213182900_i_code_review_create_mr_monthly.yml
new file mode 100644
index 00000000000..dca8545691a
--- /dev/null
+++ b/config/metrics/counts_28d/20221213182900_i_code_review_create_mr_monthly.yml
@@ -0,0 +1,26 @@
+---
+key_path: redis_hll_counters.code_review.i_code_review_create_mr_monthly
+description: Count of unique merge requests created per month
+product_section: dev
+product_stage: create
+product_group: code_review
+product_category: code_review
+value_type: number
+status: active
+milestone: "15.7"
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/106869
+time_frame: 28d
+data_source: redis_hll
+data_category: optional
+instrumentation_class: RedisHLLMetric
+performance_indicator_type: []
+options:
+ events:
+ - i_code_review_create_mr
+distribution:
+- ce
+- ee
+tier:
+- free
+- premium
+- ultimate
diff --git a/config/metrics/counts_7d/20221213183300_i_code_review_create_mr_weekly.yml b/config/metrics/counts_7d/20221213183300_i_code_review_create_mr_weekly.yml
new file mode 100644
index 00000000000..43405d5bd2c
--- /dev/null
+++ b/config/metrics/counts_7d/20221213183300_i_code_review_create_mr_weekly.yml
@@ -0,0 +1,26 @@
+---
+key_path: redis_hll_counters.code_review.i_code_review_create_mr_weekly
+description: Count of unique merge requests created per week
+product_section: dev
+product_stage: create
+product_group: code_review
+product_category: code_review
+value_type: number
+status: active
+milestone: "15.7"
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/106869
+time_frame: 7d
+data_source: redis_hll
+data_category: optional
+instrumentation_class: RedisHLLMetric
+performance_indicator_type: []
+options:
+ events:
+ - i_code_review_create_mr
+distribution:
+- ce
+- ee
+tier:
+- free
+- premium
+- ultimate
diff --git a/doc/administration/clusters/kas.md b/doc/administration/clusters/kas.md
index d7e1c9af1de..79dd69183a6 100644
--- a/doc/administration/clusters/kas.md
+++ b/doc/administration/clusters/kas.md
@@ -69,7 +69,7 @@ To enable the agent server on multiple nodes:
- `gitlab_kas['api_secret_key']` is the shared secret used for authentication between KAS and GitLab. This value must be Base64-encoded and exactly 32 bytes long.
- `gitlab_kas['private_api_secret_key']` is the shared secret used for authentication between different KAS instances. This value must be Base64-encoded and exactly 32 bytes long.
-1. For each application node, follow the steps in: [Use an external installation](../clusters/kas.md#use-an-external-installation).
+1. For each application node, follow the steps in [Use an external installation](../clusters/kas.md#use-an-external-installation). If the agent server is enabled on the application node, do not include `gitlab_kas['enable'] = false` in the configuration for that node.
1. [Reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
### For GitLab Helm Chart
diff --git a/doc/development/database/migrations_for_multiple_databases.md b/doc/development/database/migrations_for_multiple_databases.md
index 0ae9cbaf1c2..bc0ef654336 100644
--- a/doc/development/database/migrations_for_multiple_databases.md
+++ b/doc/development/database/migrations_for_multiple_databases.md
@@ -75,10 +75,18 @@ end
#### Example: Add a new table to store in a single database
-1. Define the [GitLab Schema](multiple_databases.md#gitlab-schema) of the table in [`lib/gitlab/database/gitlab_schemas.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/database/gitlab_schemas.yml):
+1. Add the table to the [database dictionary](database_dictionary.md) in [`db/docs/`](https://gitlab.com/gitlab-org/gitlab/-/tree/master/db/docs):
```yaml
- ssh_signatures: :gitlab_main
+ table_name: ssh_signatures
+ description: Description example
+ introduced_by_url: Merge request link
+ milestone: Milestone example
+ feature_categories:
+ - Feature category example
+ classes:
+ - Class example
+ gitlab_schema: gitlab_main
```
1. Create the table in a schema migration:
@@ -211,7 +219,7 @@ end
#### Example: run DML `gitlab_shared` only on the database containing the given `gitlab_schema`
Example migration updating `loose_foreign_keys_deleted_records` table
-that is marked in `lib/gitlab/database/gitlab_schemas.yml` as `gitlab_shared`.
+that is marked in `db/docs/loose_foreign_keys_deleted_records.yml` as `gitlab_shared`.
This migration since it configures restriction on `gitlab_ci` is executed only
in context of database containing `gitlab_ci` schema.
@@ -261,7 +269,7 @@ the `database_tasks: false` set. `gitlab:db:validate_config` always runs before
## Validation
Validation in a nutshell uses [`pg_query`](https://github.com/pganalyze/pg_query) to analyze
-each query and classify tables with information from [`gitlab_schema.yml`](multiple_databases.md#gitlab-schema).
+each query and classify tables with information from [`db/docs/`](database_dictionary.md).
The migration is skipped if the specified `gitlab_schema` is outside of a list of schemas
managed by a given database connection (`Gitlab::Database::gitlab_schemas_for_connection`).
@@ -435,4 +443,4 @@ tables in any database, just like any ordinary Sidekiq worker can.
## How to determine `gitlab_schema` for a given table
-See [GitLab Schema](multiple_databases.md#gitlab-schema).
+See [database dictionary](database_dictionary.md).
diff --git a/doc/development/database/multiple_databases.md b/doc/development/database/multiple_databases.md
index e5b6cfb8866..d22e3209096 100644
--- a/doc/development/database/multiple_databases.md
+++ b/doc/development/database/multiple_databases.md
@@ -14,9 +14,9 @@ On GitLab.com we are using two separate databases.
## GitLab Schema
For properly discovering allowed patterns between different databases
-the GitLab application implements the `lib/gitlab/database/gitlab_schemas.yml` YAML file.
+the GitLab application implements the [database dictionary](database_dictionary.md).
-This file provides a virtual classification of tables into a `gitlab_schema`
+The database dictionary provides a virtual classification of tables into a `gitlab_schema`
which conceptually is similar to [PostgreSQL Schema](https://www.postgresql.org/docs/current/ddl-schemas.html).
We decided as part of [using database schemas to better isolated CI decomposed features](https://gitlab.com/gitlab-org/gitlab/-/issues/333415)
that we cannot use PostgreSQL schema due to complex migration procedures. Instead we implemented
diff --git a/doc/development/documentation/styleguide/img/tier_badge.png b/doc/development/documentation/styleguide/img/tier_badge.png
deleted file mode 100644
index 5fc38e08172..00000000000
--- a/doc/development/documentation/styleguide/img/tier_badge.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/migration_style_guide.md b/doc/development/migration_style_guide.md
index e857ae5b112..8f035d4aa13 100644
--- a/doc/development/migration_style_guide.md
+++ b/doc/development/migration_style_guide.md
@@ -184,13 +184,21 @@ git checkout origin/master db/structure.sql
VERSION=<migration ID> bundle exec rails db:migrate:main
```
-### Adding new tables to GitLab Schema
-
-GitLab connects to two different Postgres databases: `main` and `ci`. New tables should be defined in [`lib/gitlab/database/gitlab_schemas.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/database/gitlab_schemas.yml) with the databases they need to be added to.
-
- ```yaml
- <TABLE_NAME>: :gitlab_main
- ```
+### Adding new tables to the database dictionary
+
+GitLab connects to two different Postgres databases: `main` and `ci`. New tables should be defined in [`db/docs/`](https://gitlab.com/gitlab-org/gitlab/-/tree/master/db/docs):
+
+```yaml
+table_name: table name exmaple
+description: Description example
+introduced_by_url: Merge request link
+milestone: Milestone example
+feature_categories:
+- Feature category example
+classes:
+- Class example
+gitlab_schema: gitlab_main
+```
## Avoiding downtime
diff --git a/doc/integration/jira/connect-app.md b/doc/integration/jira/connect-app.md
index d5a6b13b25f..513877a7b71 100644
--- a/doc/integration/jira/connect-app.md
+++ b/doc/integration/jira/connect-app.md
@@ -87,13 +87,14 @@ Prerequisites:
- GitLab.com must serve as a proxy for the instance.
- The instance must be publicly available.
+- The instance must be on version 15.7 or later.
You can link self-managed instances after installing the GitLab.com for Jira Cloud app from the marketplace.
Jira apps can only link to one URL per marketplace listing. The official listing links to GitLab.com.
### Set up your instance
-To set up your self-managed instance for the GitLab.com for Jira Cloud app:
+To set up your self-managed instance for the GitLab.com for Jira Cloud app in GitLab 15.7 or later:
1. On the top bar, select **Main menu > Admin**.
1. On the left sidebar, select **Applications** (`/admin/applications`).
diff --git a/doc/integration/saml.md b/doc/integration/saml.md
index 5e15a6c02f5..84879b7c4c7 100644
--- a/doc/integration/saml.md
+++ b/doc/integration/saml.md
@@ -82,8 +82,8 @@ For more information on:
1. Configure the following attributes so your SAML users cannot change them:
- - [`NameID`](../user/group/saml_sso/index.md#nameid)
- - `Email` when used with `omniauth_auto_link_saml_user`
+ - [`NameID`](../user/group/saml_sso/index.md#nameid).
+ - `Email` when used with `omniauth_auto_link_saml_user`.
If users can change these attributes, they can sign in as other authorized users.
See your SAML IdP documentation for information on how to make these attributes
@@ -186,18 +186,48 @@ Your IdP may need additional configuration. For more information, see
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/14361) in GitLab 14.6.
-You can configure GitLab to use multiple SAML 2.0 identity providers if:
+You can configure GitLab to use multiple SAML IdPs if:
-- Each provider has a unique name set that matches a name set in `args`. At least one provider **must** have the name `saml` to mitigate a
- [known issue](https://gitlab.com/gitlab-org/gitlab/-/issues/366450) in GitLab 14.6 and newer.
-- The providers' names are:
- - Used in OmniAuth configuration for properties based on the provider name. For example, `allowBypassTwoFactor`, `allowSingleSignOn`, and
- `syncProfileFromProvider`.
- - Used for association to each existing user as an additional identity.
+- Each provider has a unique name set that matches a name set in `args`. At least
+ one provider must have the name `saml` to mitigate a
+ [known issue](https://gitlab.com/gitlab-org/gitlab/-/issues/366450) in GitLab
+ 14.6 and newer.
+- The providers' names are used:
+ - In OmniAuth configuration for properties based on the provider name. For example,
+ `allowBypassTwoFactor`, `allowSingleSignOn`, and `syncProfileFromProvider`.
+ - For association to each existing user as an additional identity.
- The `assertion_consumer_service_url` matches the provider name.
-- The `strategy_class` is explicitly set because it cannot be inferred from provider name.
+- The `strategy_class` is explicitly set because it cannot be inferred from provider
+ name.
+
+Example provider's configuration for installations from source:
+
+```yaml
+omniauth:
+ providers:
+ - {
+ name: 'saml', # This must match the following name configuration parameter
+ args: {
+ name: 'saml', # This is mandatory and must match the provider name
+ strategy_class: 'OmniAuth::Strategies::SAML',
+ assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml_1/callback', # URL must match the name of the provider
+ ... # Put here all the required arguments similar to a single provider
+ },
+ label: 'Provider 1' # Differentiate the two buttons and providers in the UI
+ }
+ - {
+ name: 'saml1', # This must match the following name configuration parameter
+ args: {
+ name: 'saml1', # This is mandatory and must match the provider name
+ strategy_class: 'OmniAuth::Strategies::SAML',
+ assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml_2/callback', # URL must match the name of the provider
+ ... # Put here all the required arguments similar to a single provider
+ },
+ label: 'Provider 2' # Differentiate the two buttons and providers in the UI
+ }
+```
-Example multiple providers configuration for Omnibus GitLab:
+Example provider's configuration for Omnibus GitLab installations:
To allow your users to use SAML to sign up without having to manually create an account from either of the providers, add the following values to your configuration.
@@ -230,31 +260,11 @@ gitlab_rails['omniauth_providers'] = [
]
```
-Example providers configuration for installations from source:
+To allow your users to use SAML to sign up without having to manually create an
+account from either of the providers, add the following values to your configuration.
-```yaml
-omniauth:
- providers:
- - {
- name: 'saml',
- args: {
- name: 'saml', # This is mandatory and must match the provider name
- strategy_class: 'OmniAuth::Strategies::SAML',
- assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml_1/callback', # URL must match the name of the provider
- ... # Put here all the required arguments similar to a single provider
- },
- label: 'Provider 1' # Differentiate the two buttons and providers in the UI
- }
- - {
- name: 'saml1',
- args: {
- name: 'saml1', # This is mandatory and must match the provider name
- strategy_class: 'OmniAuth::Strategies::SAML',
- assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml_2/callback', # URL must match the name of the provider
- ... # Put here all the required arguments similar to a single provider
- },
- label: 'Provider 2' # Differentiate the two buttons and providers in the UI
- }
+```ruby
+gitlab_rails['omniauth_allow_single_sign_on'] = ['saml', 'saml1']
```
## Set up identity providers
@@ -643,11 +653,11 @@ For more information on solving these errors, see the [troubleshooting SAML guid
### Redirect users to SAML server for authentication
-You can add this setting to your GitLab configuration to automatically redirect you
-to your SAML server for authentication. This removes the requirement to select a button
-before actually signing in.
+You can add the `auto_sign_in_with_provider` setting to your GitLab configuration
+to automatically redirect you to your SAML server for authentication. This removes
+the requirement to select an element before actually signing in.
-For Omnibus package:
+For Omnibus GitLab installations:
```ruby
gitlab_rails['omniauth_auto_sign_in_with_provider'] = 'saml'
@@ -660,31 +670,28 @@ omniauth:
auto_sign_in_with_provider: saml
```
-Keep in mind that every sign in attempt redirects to the SAML server;
-you cannot sign in using local credentials. Ensure at least one of the
-SAML users has administrator access.
+Every sign in attempt redirects to the SAML server, so you cannot sign in using
+local credentials. Make sure at least one of the SAML users has administrator access.
-You may also bypass the auto sign-in feature by browsing to
+You can also bypass the auto sign-in feature by
`https://gitlab.example.com/users/sign_in?auto_sign_in=false`.
### Map SAML response attribute names **(FREE SELF)**
-NOTE:
-This setting should be used only to map attributes that are part of the OmniAuth
-`info` hash schema.
-
-`attribute_statements` is used to map Attribute Names in a `SAMLResponse` to entries
+You can use `attribute_statements` to map attribute names in a SAML response to entries
in the OmniAuth [`info` hash](https://github.com/omniauth/omniauth/wiki/Auth-Hash-Schema#schema-10-and-later).
+NOTE:
+Only use this setting to map attributes that are part of the OmniAuth `info` hash schema.
+
For example, if your `SAMLResponse` contains an Attribute called `EmailAddress`,
specify `{ email: ['EmailAddress'] }` to map the Attribute to the
corresponding key in the `info` hash. URI-named Attributes are also supported, for example,
`{ email: ['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress'] }`.
-This setting allows you tell GitLab where to look for certain attributes required
-to create an account. Like mentioned above, if your IdP sends the user's email
-address as `EmailAddress` instead of `email`, let GitLab know by setting it on
-your configuration:
+Use this setting to tell GitLab where to look for certain attributes required
+to create an account. If your IdP sends the user's email address as `EmailAddress`
+instead of `email`, let GitLab know by setting it on your configuration:
```yaml
args: {
@@ -738,7 +745,9 @@ args: {
### Designate a unique attribute for the `uid`
-By default, the `uid` is set as the `name_id` in the SAML response. If you'd like to designate a unique attribute for the `uid`, you can set the `uid_attribute`. In the example below, the value of `uid` attribute in the SAML response is set as the `uid_attribute`.
+By default, the `uid` is set as the `name_id` in the SAML response. To designate
+a unique attribute for the `uid`, you can set the `uid_attribute`. In the following
+example, the value of `uid` attribute in the SAML response is set as the `uid_attribute`.
```yaml
args: {
@@ -751,9 +760,15 @@ args: {
}
```
-Ensure that attributes define the SAML user, such as
-[`NameID`](../user/group/saml_sso/index.md#nameid) and email address, are fixed
-for each user before changing this value.
+Before setting the `uid` to a unique attribute, make sure that you have configured
+the following attributes so your SAML users cannot change them:
+
+- [`NameID`](../user/group/saml_sso/index.md#nameid).
+- `Email` when used with `omniauth_auto_link_saml_user`.
+
+If users can change these attributes, they can sign in as other authorized users.
+See your SAML IdP documentation for information on how to make these attributes
+unchangeable.
## Assertion encryption (optional)
diff --git a/doc/user/packages/nuget_repository/img/visual_studio_adding_nuget_source.png b/doc/user/packages/nuget_repository/img/visual_studio_adding_nuget_source.png
deleted file mode 100644
index 7397403f4bf..00000000000
--- a/doc/user/packages/nuget_repository/img/visual_studio_adding_nuget_source.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/packages/nuget_repository/img/visual_studio_nuget_source_added.png b/doc/user/packages/nuget_repository/img/visual_studio_nuget_source_added.png
deleted file mode 100644
index 8c14a14e304..00000000000
--- a/doc/user/packages/nuget_repository/img/visual_studio_nuget_source_added.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/pages/img/remove_fork_relationship_v13_1.png b/doc/user/project/pages/img/remove_fork_relationship_v13_1.png
deleted file mode 100644
index 84aa2e571c7..00000000000
--- a/doc/user/project/pages/img/remove_fork_relationship_v13_1.png
+++ /dev/null
Binary files differ
diff --git a/lib/bitbucket_server/connection.rb b/lib/bitbucket_server/connection.rb
index fbd451efb23..845acf034a5 100644
--- a/lib/bitbucket_server/connection.rb
+++ b/lib/bitbucket_server/connection.rb
@@ -15,6 +15,7 @@ module BitbucketServer
Errno::EHOSTUNREACH,
Net::OpenTimeout,
Net::ReadTimeout,
+ URI::InvalidURIError,
Gitlab::HTTP::BlockedUrlError
].freeze
diff --git a/lib/gitlab/database/gitlab_schemas.yml b/lib/gitlab/database/gitlab_schemas.yml
deleted file mode 100644
index b9df83f432a..00000000000
--- a/lib/gitlab/database/gitlab_schemas.yml
+++ /dev/null
@@ -1,619 +0,0 @@
-abuse_reports: :gitlab_main
-achievements: :gitlab_main
-agent_activity_events: :gitlab_main
-agent_group_authorizations: :gitlab_main
-agent_project_authorizations: :gitlab_main
-alert_management_alert_assignees: :gitlab_main
-alert_management_alerts: :gitlab_main
-alert_management_alert_metric_images: :gitlab_main
-alert_management_alert_user_mentions: :gitlab_main
-alert_management_http_integrations: :gitlab_main
-allowed_email_domains: :gitlab_main
-analytics_cycle_analytics_aggregations: :gitlab_main
-analytics_cycle_analytics_group_stages: :gitlab_main
-analytics_cycle_analytics_group_value_streams: :gitlab_main
-analytics_cycle_analytics_issue_stage_events: :gitlab_main
-analytics_cycle_analytics_merge_request_stage_events: :gitlab_main
-analytics_cycle_analytics_project_stages: :gitlab_main
-analytics_cycle_analytics_project_value_streams: :gitlab_main
-analytics_cycle_analytics_stage_event_hashes: :gitlab_main
-analytics_devops_adoption_segments: :gitlab_main
-analytics_devops_adoption_snapshots: :gitlab_main
-analytics_language_trend_repository_languages: :gitlab_main
-analytics_usage_trends_measurements: :gitlab_main
-appearances: :gitlab_main
-application_settings: :gitlab_main
-application_setting_terms: :gitlab_main
-approval_merge_request_rules_approved_approvers: :gitlab_main
-approval_merge_request_rules: :gitlab_main
-approval_merge_request_rules_groups: :gitlab_main
-approval_merge_request_rule_sources: :gitlab_main
-approval_merge_request_rules_users: :gitlab_main
-approval_project_rules: :gitlab_main
-approval_project_rules_groups: :gitlab_main
-approval_project_rules_protected_branches: :gitlab_main
-approval_project_rules_users: :gitlab_main
-approvals: :gitlab_main
-approver_groups: :gitlab_main
-approvers: :gitlab_main
-ar_internal_metadata: :gitlab_internal
-atlassian_identities: :gitlab_main
-audit_events_external_audit_event_destinations: :gitlab_main
-audit_events: :gitlab_main
-audit_events_streaming_headers: :gitlab_main
-audit_events_streaming_event_type_filters: :gitlab_main
-authentication_events: :gitlab_main
-award_emoji: :gitlab_main
-aws_roles: :gitlab_main
-background_migration_jobs: :gitlab_shared
-badges: :gitlab_main
-banned_users: :gitlab_main
-batched_background_migration_jobs: :gitlab_shared
-batched_background_migrations: :gitlab_shared
-board_assignees: :gitlab_main
-board_group_recent_visits: :gitlab_main
-board_labels: :gitlab_main
-board_project_recent_visits: :gitlab_main
-boards_epic_board_labels: :gitlab_main
-boards_epic_board_positions: :gitlab_main
-boards_epic_board_recent_visits: :gitlab_main
-boards_epic_boards: :gitlab_main
-boards_epic_lists: :gitlab_main
-boards_epic_list_user_preferences: :gitlab_main
-boards_epic_user_preferences: :gitlab_main
-boards: :gitlab_main
-board_user_preferences: :gitlab_main
-broadcast_messages: :gitlab_main
-bulk_import_configurations: :gitlab_main
-bulk_import_entities: :gitlab_main
-bulk_import_exports: :gitlab_main
-bulk_import_export_uploads: :gitlab_main
-bulk_import_failures: :gitlab_main
-bulk_imports: :gitlab_main
-bulk_import_trackers: :gitlab_main
-chat_names: :gitlab_main
-chat_teams: :gitlab_main
-ci_build_needs: :gitlab_ci
-ci_build_pending_states: :gitlab_ci
-ci_build_report_results: :gitlab_ci
-ci_builds: :gitlab_ci
-ci_builds_metadata: :gitlab_ci
-ci_builds_runner_session: :gitlab_ci
-ci_build_trace_chunks: :gitlab_ci
-ci_build_trace_metadata: :gitlab_ci
-ci_daily_build_group_report_results: :gitlab_ci
-ci_deleted_objects: :gitlab_ci
-ci_freeze_periods: :gitlab_ci
-ci_group_variables: :gitlab_ci
-ci_instance_variables: :gitlab_ci
-ci_job_artifacts: :gitlab_ci
-ci_job_token_project_scope_links: :gitlab_ci
-ci_job_variables: :gitlab_ci
-ci_job_artifact_states: :gitlab_ci
-ci_minutes_additional_packs: :gitlab_ci
-ci_namespace_monthly_usages: :gitlab_ci
-ci_namespace_mirrors: :gitlab_ci
-ci_partitions: :gitlab_ci
-ci_pending_builds: :gitlab_ci
-ci_pipeline_artifacts: :gitlab_ci
-ci_pipeline_chat_data: :gitlab_ci
-ci_pipeline_messages: :gitlab_ci
-ci_pipeline_schedules: :gitlab_ci
-ci_pipeline_schedule_variables: :gitlab_ci
-ci_pipelines_config: :gitlab_ci
-ci_pipeline_metadata: :gitlab_ci
-ci_pipelines: :gitlab_ci
-ci_pipeline_variables: :gitlab_ci
-ci_platform_metrics: :gitlab_ci
-ci_project_monthly_usages: :gitlab_ci
-ci_project_mirrors: :gitlab_ci
-ci_refs: :gitlab_ci
-ci_resource_groups: :gitlab_ci
-ci_resources: :gitlab_ci
-ci_runner_namespaces: :gitlab_ci
-ci_runner_projects: :gitlab_ci
-ci_runner_versions: :gitlab_ci
-ci_runners: :gitlab_ci
-ci_running_builds: :gitlab_ci
-ci_sources_pipelines: :gitlab_ci
-ci_secure_files: :gitlab_ci
-ci_secure_file_states: :gitlab_ci
-ci_sources_projects: :gitlab_ci
-ci_stages: :gitlab_ci
-ci_subscriptions_projects: :gitlab_ci
-ci_trigger_requests: :gitlab_ci
-ci_triggers: :gitlab_ci
-ci_unit_test_failures: :gitlab_ci
-ci_unit_tests: :gitlab_ci
-ci_variables: :gitlab_ci
-cluster_agents: :gitlab_main
-cluster_agent_tokens: :gitlab_main
-cluster_enabled_grants: :gitlab_main
-cluster_groups: :gitlab_main
-cluster_platforms_kubernetes: :gitlab_main
-cluster_projects: :gitlab_main
-cluster_providers_aws: :gitlab_main
-cluster_providers_gcp: :gitlab_main
-clusters_applications_cert_managers: :gitlab_main
-clusters_applications_cilium: :gitlab_main
-clusters_applications_crossplane: :gitlab_main
-clusters_applications_helm: :gitlab_main
-clusters_applications_ingress: :gitlab_main
-clusters_applications_jupyter: :gitlab_main
-clusters_applications_knative: :gitlab_main
-clusters_applications_prometheus: :gitlab_main
-clusters_applications_runners: :gitlab_main
-clusters: :gitlab_main
-clusters_integration_prometheus: :gitlab_main
-clusters_kubernetes_namespaces: :gitlab_main
-commit_user_mentions: :gitlab_main
-compliance_management_frameworks: :gitlab_main
-container_expiration_policies: :gitlab_main
-container_repositories: :gitlab_main
-content_blocked_states: :gitlab_main
-conversational_development_index_metrics: :gitlab_main
-coverage_fuzzing_corpuses: :gitlab_main
-csv_issue_imports: :gitlab_main
-custom_emoji: :gitlab_main
-customer_relations_contacts: :gitlab_main
-customer_relations_organizations: :gitlab_main
-dast_pre_scan_verifications: :gitlab_main
-dast_pre_scan_verification_steps: :gitlab_main
-dast_profile_schedules: :gitlab_main
-dast_profiles: :gitlab_main
-dast_profiles_pipelines: :gitlab_main
-dast_scanner_profiles_builds: :gitlab_main
-dast_scanner_profiles: :gitlab_main
-dast_site_profiles_builds: :gitlab_main
-dast_site_profile_secret_variables: :gitlab_main
-dast_site_profiles: :gitlab_main
-dast_site_profiles_pipelines: :gitlab_main
-dast_sites: :gitlab_main
-dast_site_tokens: :gitlab_main
-dast_site_validations: :gitlab_main
-dependency_proxy_blob_states: :gitlab_main
-dependency_list_exports: :gitlab_main
-dependency_proxy_blobs: :gitlab_main
-dependency_proxy_group_settings: :gitlab_main
-dependency_proxy_image_ttl_group_policies: :gitlab_main
-dependency_proxy_manifests: :gitlab_main
-dependency_proxy_manifest_states: :gitlab_main
-deploy_keys_projects: :gitlab_main
-deployment_approvals: :gitlab_main
-deployment_clusters: :gitlab_main
-deployment_merge_requests: :gitlab_main
-deployments: :gitlab_main
-deploy_tokens: :gitlab_main
-description_versions: :gitlab_main
-design_management_designs: :gitlab_main
-design_management_designs_versions: :gitlab_main
-design_management_versions: :gitlab_main
-design_user_mentions: :gitlab_main
-detached_partitions: :gitlab_shared
-diff_note_positions: :gitlab_main
-dora_configurations: :gitlab_main
-dora_daily_metrics: :gitlab_main
-draft_notes: :gitlab_main
-elastic_index_settings: :gitlab_main
-elastic_reindexing_slices: :gitlab_main
-elastic_reindexing_subtasks: :gitlab_main
-elastic_reindexing_tasks: :gitlab_main
-elasticsearch_indexed_namespaces: :gitlab_main
-elasticsearch_indexed_projects: :gitlab_main
-emails: :gitlab_main
-environments: :gitlab_main
-epic_issues: :gitlab_main
-epic_metrics: :gitlab_main
-epics: :gitlab_main
-epic_user_mentions: :gitlab_main
-error_tracking_client_keys: :gitlab_main
-error_tracking_error_events: :gitlab_main
-error_tracking_errors: :gitlab_main
-events: :gitlab_main
-evidences: :gitlab_main
-external_approval_rules: :gitlab_main
-external_approval_rules_protected_branches: :gitlab_main
-external_pull_requests: :gitlab_ci
-external_status_checks: :gitlab_main
-external_status_checks_protected_branches: :gitlab_main
-feature_gates: :gitlab_main
-features: :gitlab_main
-fork_network_members: :gitlab_main
-fork_networks: :gitlab_main
-geo_cache_invalidation_events: :gitlab_main
-geo_container_repository_updated_events: :gitlab_main
-geo_event_log: :gitlab_main
-geo_events: :gitlab_main
-geo_hashed_storage_attachments_events: :gitlab_main
-geo_hashed_storage_migrated_events: :gitlab_main
-geo_node_namespace_links: :gitlab_main
-geo_nodes: :gitlab_main
-geo_node_statuses: :gitlab_main
-geo_repositories_changed_events: :gitlab_main
-geo_repository_created_events: :gitlab_main
-geo_repository_deleted_events: :gitlab_main
-geo_repository_renamed_events: :gitlab_main
-geo_repository_updated_events: :gitlab_main
-geo_reset_checksum_events: :gitlab_main
-ghost_user_migrations: :gitlab_main
-gitlab_subscription_histories: :gitlab_main
-gitlab_subscriptions: :gitlab_main
-gpg_keys: :gitlab_main
-gpg_key_subkeys: :gitlab_main
-gpg_signatures: :gitlab_main
-grafana_integrations: :gitlab_main
-group_custom_attributes: :gitlab_main
-group_crm_settings: :gitlab_main
-group_deletion_schedules: :gitlab_main
-group_deploy_keys: :gitlab_main
-group_deploy_keys_groups: :gitlab_main
-group_deploy_tokens: :gitlab_main
-group_features: :gitlab_main
-group_group_links: :gitlab_main
-group_import_states: :gitlab_main
-group_merge_request_approval_settings: :gitlab_main
-group_repository_storage_moves: :gitlab_main
-group_wiki_repositories: :gitlab_main
-historical_data: :gitlab_main
-identities: :gitlab_main
-import_export_uploads: :gitlab_main
-import_failures: :gitlab_main
-incident_management_escalation_policies: :gitlab_main
-incident_management_escalation_rules: :gitlab_main
-incident_management_issuable_escalation_statuses: :gitlab_main
-incident_management_oncall_participants: :gitlab_main
-incident_management_oncall_rotations: :gitlab_main
-incident_management_oncall_schedules: :gitlab_main
-incident_management_oncall_shifts: :gitlab_main
-incident_management_pending_alert_escalations: :gitlab_main
-incident_management_pending_issue_escalations: :gitlab_main
-incident_management_timeline_events: :gitlab_main
-incident_management_timeline_event_tags: :gitlab_main
-incident_management_timeline_event_tag_links: :gitlab_main
-index_statuses: :gitlab_main
-in_product_marketing_emails: :gitlab_main
-insights: :gitlab_main
-integrations: :gitlab_main
-internal_ids: :gitlab_main
-ip_restrictions: :gitlab_main
-issuable_metric_images: :gitlab_main
-issuable_resource_links: :gitlab_main
-issuable_severities: :gitlab_main
-issuable_slas: :gitlab_main
-issue_assignees: :gitlab_main
-issue_customer_relations_contacts: :gitlab_main
-issue_emails: :gitlab_main
-issue_email_participants: :gitlab_main
-issue_links: :gitlab_main
-issue_metrics: :gitlab_main
-issue_search_data: :gitlab_main
-issues: :gitlab_main
-issues_prometheus_alert_events: :gitlab_main
-issues_self_managed_prometheus_alert_events: :gitlab_main
-issue_tracker_data: :gitlab_main
-issue_user_mentions: :gitlab_main
-iterations_cadences: :gitlab_main
-jira_connect_installations: :gitlab_main
-jira_connect_subscriptions: :gitlab_main
-jira_imports: :gitlab_main
-jira_tracker_data: :gitlab_main
-keys: :gitlab_main
-label_links: :gitlab_main
-label_priorities: :gitlab_main
-labels: :gitlab_main
-ldap_group_links: :gitlab_main
-lfs_file_locks: :gitlab_main
-lfs_objects: :gitlab_main
-lfs_objects_projects: :gitlab_main
-lfs_object_states: :gitlab_main
-licenses: :gitlab_main
-lists: :gitlab_main
-list_user_preferences: :gitlab_main
-loose_foreign_keys_deleted_records: :gitlab_shared
-member_roles: :gitlab_main
-member_tasks: :gitlab_main
-members: :gitlab_main
-merge_request_assignees: :gitlab_main
-merge_request_blocks: :gitlab_main
-merge_request_cleanup_schedules: :gitlab_main
-merge_requests_compliance_violations: :gitlab_main
-merge_request_context_commit_diff_files: :gitlab_main
-merge_request_context_commits: :gitlab_main
-merge_request_diff_commits: :gitlab_main
-merge_request_diff_commit_users: :gitlab_main
-merge_request_diff_details: :gitlab_main
-merge_request_diff_files: :gitlab_main
-merge_request_diffs: :gitlab_main
-merge_request_metrics: :gitlab_main
-merge_request_predictions: :gitlab_main
-merge_request_reviewers: :gitlab_main
-merge_requests_closing_issues: :gitlab_main
-merge_requests: :gitlab_main
-merge_request_user_mentions: :gitlab_main
-merge_trains: :gitlab_main
-metrics_dashboard_annotations: :gitlab_main
-metrics_users_starred_dashboards: :gitlab_main
-milestone_releases: :gitlab_main
-milestones: :gitlab_main
-ml_candidates: :gitlab_main
-ml_experiments: :gitlab_main
-ml_experiment_metadata: :gitlab_main
-ml_candidate_metrics: :gitlab_main
-ml_candidate_params: :gitlab_main
-ml_candidate_metadata: :gitlab_main
-namespace_admin_notes: :gitlab_main
-namespace_aggregation_schedules: :gitlab_main
-namespace_bans: :gitlab_main
-namespace_limits: :gitlab_main
-namespace_package_settings: :gitlab_main
-namespace_root_storage_statistics: :gitlab_main
-namespace_ci_cd_settings: :gitlab_main
-namespace_commit_emails: :gitlab_main
-namespace_settings: :gitlab_main
-namespace_details: :gitlab_main
-namespaces: :gitlab_main
-namespaces_sync_events: :gitlab_main
-namespace_statistics: :gitlab_main
-note_diff_files: :gitlab_main
-notes: :gitlab_main
-notification_settings: :gitlab_main
-oauth_access_grants: :gitlab_main
-oauth_access_tokens: :gitlab_main
-oauth_applications: :gitlab_main
-oauth_openid_requests: :gitlab_main
-onboarding_progresses: :gitlab_main
-operations_feature_flags_clients: :gitlab_main
-operations_feature_flag_scopes: :gitlab_main
-operations_feature_flags: :gitlab_main
-operations_feature_flags_issues: :gitlab_main
-operations_scopes: :gitlab_main
-operations_strategies: :gitlab_main
-operations_strategies_user_lists: :gitlab_main
-operations_user_lists: :gitlab_main
-p_ci_builds_metadata: :gitlab_ci
-packages_build_infos: :gitlab_main
-packages_cleanup_policies: :gitlab_main
-packages_composer_cache_files: :gitlab_main
-packages_composer_metadata: :gitlab_main
-packages_conan_file_metadata: :gitlab_main
-packages_conan_metadata: :gitlab_main
-packages_debian_file_metadata: :gitlab_main
-packages_debian_group_architectures: :gitlab_main
-packages_debian_group_component_files: :gitlab_main
-packages_debian_group_components: :gitlab_main
-packages_debian_group_distribution_keys: :gitlab_main
-packages_debian_group_distributions: :gitlab_main
-packages_debian_project_architectures: :gitlab_main
-packages_debian_project_component_files: :gitlab_main
-packages_debian_project_components: :gitlab_main
-packages_debian_project_distribution_keys: :gitlab_main
-packages_debian_project_distributions: :gitlab_main
-packages_debian_publications: :gitlab_main
-packages_dependencies: :gitlab_main
-packages_dependency_links: :gitlab_main
-packages_events: :gitlab_main
-packages_helm_file_metadata: :gitlab_main
-packages_maven_metadata: :gitlab_main
-packages_npm_metadata: :gitlab_main
-packages_rpm_metadata: :gitlab_main
-packages_nuget_dependency_link_metadata: :gitlab_main
-packages_nuget_metadata: :gitlab_main
-packages_package_file_build_infos: :gitlab_main
-packages_package_files: :gitlab_main
-packages_rpm_repository_files: :gitlab_main
-packages_packages: :gitlab_main
-packages_pypi_metadata: :gitlab_main
-packages_rubygems_metadata: :gitlab_main
-packages_tags: :gitlab_main
-pages_deployments: :gitlab_main
-pages_deployment_states: :gitlab_main
-pages_domain_acme_orders: :gitlab_main
-pages_domains: :gitlab_main
-path_locks: :gitlab_main
-personal_access_tokens: :gitlab_main
-plan_limits: :gitlab_main
-plans: :gitlab_main
-pm_licenses: :gitlab_pm
-pm_packages: :gitlab_pm
-pm_package_versions: :gitlab_pm
-pm_package_version_licenses: :gitlab_pm
-pool_repositories: :gitlab_main
-postgres_async_indexes: :gitlab_shared
-postgres_autovacuum_activity: :gitlab_shared
-postgres_constraints: :gitlab_shared
-postgres_foreign_keys: :gitlab_shared
-postgres_index_bloat_estimates: :gitlab_shared
-postgres_indexes: :gitlab_shared
-postgres_partitioned_tables: :gitlab_shared
-postgres_partitions: :gitlab_shared
-postgres_reindex_actions: :gitlab_shared
-postgres_reindex_queued_actions: :gitlab_shared
-product_analytics_events_experimental: :gitlab_main
-programming_languages: :gitlab_main
-project_access_tokens: :gitlab_main
-project_alerting_settings: :gitlab_main
-project_aliases: :gitlab_main
-project_authorizations: :gitlab_main
-project_auto_devops: :gitlab_main
-project_build_artifacts_size_refreshes: :gitlab_main
-project_ci_cd_settings: :gitlab_main
-project_ci_feature_usages: :gitlab_main
-project_compliance_framework_settings: :gitlab_main
-project_custom_attributes: :gitlab_main
-project_daily_statistics: :gitlab_main
-project_deploy_tokens: :gitlab_main
-project_error_tracking_settings: :gitlab_main
-project_export_jobs: :gitlab_main
-project_features: :gitlab_main
-project_feature_usages: :gitlab_main
-project_group_links: :gitlab_main
-project_import_data: :gitlab_main
-project_incident_management_settings: :gitlab_main
-project_metrics_settings: :gitlab_main
-project_mirror_data: :gitlab_main
-project_pages_metadata: :gitlab_main
-project_relation_export_uploads: :gitlab_main
-project_relation_exports: :gitlab_main
-project_repositories: :gitlab_main
-project_repository_states: :gitlab_main
-project_repository_storage_moves: :gitlab_main
-project_security_settings: :gitlab_main
-project_settings: :gitlab_main
-projects: :gitlab_main
-projects_sync_events: :gitlab_main
-project_statistics: :gitlab_main
-project_topics: :gitlab_main
-project_wiki_repositories: :gitlab_main
-project_wiki_repository_states: :gitlab_main
-prometheus_alert_events: :gitlab_main
-prometheus_alerts: :gitlab_main
-prometheus_metrics: :gitlab_main
-protected_branches: :gitlab_main
-protected_branch_merge_access_levels: :gitlab_main
-protected_branch_push_access_levels: :gitlab_main
-protected_branch_unprotect_access_levels: :gitlab_main
-protected_environment_approval_rules: :gitlab_main
-protected_environment_deploy_access_levels: :gitlab_main
-protected_environments: :gitlab_main
-protected_tag_create_access_levels: :gitlab_main
-protected_tags: :gitlab_main
-push_event_payloads: :gitlab_main
-push_rules: :gitlab_main
-raw_usage_data: :gitlab_main
-redirect_routes: :gitlab_main
-related_epic_links: :gitlab_main
-release_links: :gitlab_main
-releases: :gitlab_main
-remote_mirrors: :gitlab_main
-repository_languages: :gitlab_main
-required_code_owners_sections: :gitlab_main
-requirements: :gitlab_main
-requirements_management_test_reports: :gitlab_main
-resource_iteration_events: :gitlab_main
-resource_label_events: :gitlab_main
-resource_milestone_events: :gitlab_main
-resource_state_events: :gitlab_main
-resource_weight_events: :gitlab_main
-reviews: :gitlab_main
-routes: :gitlab_main
-saml_group_links: :gitlab_main
-saml_providers: :gitlab_main
-saved_replies: :gitlab_main
-sbom_components: :gitlab_main
-sbom_occurrences: :gitlab_main
-sbom_component_versions: :gitlab_main
-sbom_sources: :gitlab_main
-sbom_vulnerable_component_versions: :gitlab_main
-schema_migrations: :gitlab_internal
-scim_identities: :gitlab_main
-scim_oauth_access_tokens: :gitlab_main
-security_findings: :gitlab_main
-security_orchestration_policy_configurations: :gitlab_main
-security_orchestration_policy_rule_schedules: :gitlab_main
-security_scans: :gitlab_main
-security_training_providers: :gitlab_main
-security_trainings: :gitlab_main
-self_managed_prometheus_alert_events: :gitlab_main
-sent_notifications: :gitlab_main
-sentry_issues: :gitlab_main
-serverless_domain_cluster: :gitlab_main
-service_desk_settings: :gitlab_main
-shards: :gitlab_main
-slack_api_scopes: :gitlab_main
-slack_integrations: :gitlab_main
-slack_integrations_scopes: :gitlab_main
-smartcard_identities: :gitlab_main
-snippet_repositories: :gitlab_main
-snippet_repository_storage_moves: :gitlab_main
-snippets: :gitlab_main
-snippet_statistics: :gitlab_main
-snippet_user_mentions: :gitlab_main
-software_license_policies: :gitlab_main
-software_licenses: :gitlab_main
-spam_logs: :gitlab_main
-sprints: :gitlab_main
-ssh_signatures: :gitlab_main
-status_check_responses: :gitlab_main
-status_page_published_incidents: :gitlab_main
-status_page_settings: :gitlab_main
-subscriptions: :gitlab_main
-suggestions: :gitlab_main
-system_note_metadata: :gitlab_main
-taggings: :gitlab_ci
-tags: :gitlab_ci
-term_agreements: :gitlab_main
-terraform_states: :gitlab_main
-terraform_state_versions: :gitlab_main
-timelogs: :gitlab_main
-timelog_categories: :gitlab_main
-todos: :gitlab_main
-token_with_ivs: :gitlab_main
-topics: :gitlab_main
-trending_projects: :gitlab_main
-u2f_registrations: :gitlab_main
-upcoming_reconciliations: :gitlab_main
-uploads: :gitlab_main
-upload_states: :gitlab_main
-user_agent_details: :gitlab_main
-user_callouts: :gitlab_main
-user_canonical_emails: :gitlab_main
-user_credit_card_validations: :gitlab_main
-user_custom_attributes: :gitlab_main
-user_details: :gitlab_main
-user_follow_users: :gitlab_main
-user_group_callouts: :gitlab_main
-user_project_callouts: :gitlab_main
-user_highest_roles: :gitlab_main
-user_interacted_projects: :gitlab_main
-user_phone_number_validations: :gitlab_main
-user_permission_export_uploads: :gitlab_main
-user_preferences: :gitlab_main
-users: :gitlab_main
-users_ops_dashboard_projects: :gitlab_main
-users_security_dashboard_projects: :gitlab_main
-users_star_projects: :gitlab_main
-users_statistics: :gitlab_main
-user_statuses: :gitlab_main
-user_synced_attributes_metadata: :gitlab_main
-verification_codes: :gitlab_main
-vulnerabilities: :gitlab_main
-vulnerability_advisories: :gitlab_main
-vulnerability_exports: :gitlab_main
-vulnerability_external_issue_links: :gitlab_main
-vulnerability_feedback: :gitlab_main
-vulnerability_finding_evidences: :gitlab_main
-vulnerability_finding_links: :gitlab_main
-vulnerability_finding_signatures: :gitlab_main
-vulnerability_findings_remediations: :gitlab_main
-vulnerability_flags: :gitlab_main
-vulnerability_historical_statistics: :gitlab_main
-vulnerability_identifiers: :gitlab_main
-vulnerability_issue_links: :gitlab_main
-vulnerability_merge_request_links: :gitlab_main
-vulnerability_occurrence_identifiers: :gitlab_main
-vulnerability_occurrence_pipelines: :gitlab_main
-vulnerability_occurrences: :gitlab_main
-vulnerability_reads: :gitlab_main
-vulnerability_remediations: :gitlab_main
-vulnerability_scanners: :gitlab_main
-vulnerability_state_transitions: :gitlab_main
-vulnerability_statistics: :gitlab_main
-vulnerability_user_mentions: :gitlab_main
-webauthn_registrations: :gitlab_main
-web_hook_logs: :gitlab_main
-web_hooks: :gitlab_main
-wiki_page_meta: :gitlab_main
-wiki_page_slugs: :gitlab_main
-work_item_parent_links: :gitlab_main
-work_item_progresses: :gitlab_main
-work_item_types: :gitlab_main
-work_item_hierarchy_restrictions: :gitlab_main
-x509_certificates: :gitlab_main
-x509_commit_signatures: :gitlab_main
-x509_issuers: :gitlab_main
-zentao_tracker_data: :gitlab_main
-# dingtalk_tracker_data JiHu-specific, see https://jihulab.com/gitlab-cn/gitlab/-/merge_requests/417
-dingtalk_tracker_data: :gitlab_main
-zoom_meetings: :gitlab_main
-batched_background_migration_job_transition_logs: :gitlab_shared
-user_namespace_callouts: :gitlab_main
diff --git a/lib/gitlab/database/migration_helpers/automatic_lock_writes_on_tables.rb b/lib/gitlab/database/migration_helpers/automatic_lock_writes_on_tables.rb
index 1ba0bafa7b4..0aa4b0d01c4 100644
--- a/lib/gitlab/database/migration_helpers/automatic_lock_writes_on_tables.rb
+++ b/lib/gitlab/database/migration_helpers/automatic_lock_writes_on_tables.rb
@@ -49,7 +49,8 @@ module Gitlab
if table_schema.nil?
error_message = <<~ERROR
No gitlab_schema is defined for the table #{table_name}. Please consider
- adding it to the file config 'lib/gitlab/database/gitlab_schemas.yml'
+ adding it to the database dictionary.
+ More info: https://docs.gitlab.com/ee/development/database/database_dictionary.html
ERROR
raise error_message
end
diff --git a/lib/gitlab/ssh/signature.rb b/lib/gitlab/ssh/signature.rb
index 3500d1346e2..a654d5b2ff1 100644
--- a/lib/gitlab/ssh/signature.rb
+++ b/lib/gitlab/ssh/signature.rb
@@ -9,6 +9,8 @@ module Gitlab
class Signature
include Gitlab::Utils::StrongMemoize
+ GIT_NAMESPACE = 'git'
+
def initialize(signature_text, signed_text, committer_email)
@signature_text = signature_text
@signed_text = signed_text
@@ -53,6 +55,7 @@ module Gitlab
# still need to check that the key belongs to the committer.
def valid_signature_blob?
return false unless signature
+ return false unless signature.namespace == GIT_NAMESPACE
signature.verify(@signed_text)
end
diff --git a/lib/gitlab/usage_data_counters/known_events/code_review_events.yml b/lib/gitlab/usage_data_counters/known_events/code_review_events.yml
index d4dfc0f8d75..3bb6655d762 100644
--- a/lib/gitlab/usage_data_counters/known_events/code_review_events.yml
+++ b/lib/gitlab/usage_data_counters/known_events/code_review_events.yml
@@ -39,6 +39,10 @@
redis_slot: code_review
category: code_review
aggregation: weekly
+- name: i_code_review_create_mr
+ redis_slot: code_review
+ category: code_review
+ aggregation: weekly
- name: i_code_review_user_create_mr
redis_slot: code_review
category: code_review
@@ -450,4 +454,4 @@
- name: i_code_review_merge_request_widget_security_reports_expand_failed
redis_slot: code_review
category: code_review
- aggregation: weekly \ No newline at end of file
+ aggregation: weekly
diff --git a/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter.rb b/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter.rb
index c5f6d028250..10dae35d0bf 100644
--- a/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter.rb
+++ b/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter.rb
@@ -6,7 +6,8 @@ module Gitlab
MR_DIFFS_ACTION = 'i_code_review_mr_diffs'
MR_DIFFS_SINGLE_FILE_ACTION = 'i_code_review_mr_single_file_diffs'
MR_DIFFS_USER_SINGLE_FILE_ACTION = 'i_code_review_user_single_file_diffs'
- MR_CREATE_ACTION = 'i_code_review_user_create_mr'
+ MR_CREATE_ACTION = 'i_code_review_create_mr'
+ MR_USER_CREATE_ACTION = 'i_code_review_user_create_mr'
MR_CLOSE_ACTION = 'i_code_review_user_close_mr'
MR_REOPEN_ACTION = 'i_code_review_user_reopen_mr'
MR_MERGE_ACTION = 'i_code_review_user_merge_mr'
@@ -62,8 +63,24 @@ module Gitlab
track_unique_action_by_user(MR_DIFFS_USER_SINGLE_FILE_ACTION, user)
end
- def track_create_mr_action(user:)
- track_unique_action_by_user(MR_CREATE_ACTION, user)
+ def track_create_mr_action(user:, merge_request:)
+ track_unique_action_by_user(MR_USER_CREATE_ACTION, user)
+ track_unique_action_by_merge_request(MR_CREATE_ACTION, merge_request)
+
+ project = merge_request.target_project
+ return unless Feature.enabled?(:route_hll_to_snowplow_phase2, project.namespace)
+
+ Gitlab::Tracking.event(
+ name,
+ :create,
+ project: project,
+ namespace: project.namespace,
+ user: user,
+ property: MR_CREATE_ACTION,
+ label: 'redis_hll_counters.code_review.i_code_review_create_mr_monthly',
+ context: [Gitlab::Tracking::ServicePingContext.new(data_source: :redis_hll,
+ event: MR_CREATE_ACTION).to_context]
+ )
end
def track_close_mr_action(user:)
diff --git a/lib/sidebars/projects/menus/infrastructure_menu.rb b/lib/sidebars/projects/menus/infrastructure_menu.rb
index a8ac3d10f83..04c9ab77729 100644
--- a/lib/sidebars/projects/menus/infrastructure_menu.rb
+++ b/lib/sidebars/projects/menus/infrastructure_menu.rb
@@ -35,11 +35,7 @@ module Sidebars
private
def feature_enabled?
- if ::Feature.enabled?(:split_operations_visibility_permissions, context.project)
- context.project.feature_available?(:infrastructure, context.current_user)
- else
- context.project.feature_available?(:operations, context.current_user)
- end
+ context.project.feature_available?(:infrastructure, context.current_user)
end
def kubernetes_menu_item
diff --git a/lib/sidebars/projects/menus/monitor_menu.rb b/lib/sidebars/projects/menus/monitor_menu.rb
index 035634702db..fea71e4aefd 100644
--- a/lib/sidebars/projects/menus/monitor_menu.rb
+++ b/lib/sidebars/projects/menus/monitor_menu.rb
@@ -41,11 +41,7 @@ module Sidebars
private
def feature_enabled?
- if ::Feature.enabled?(:split_operations_visibility_permissions, context.project)
- context.project.feature_available?(:monitor, context.current_user)
- else
- context.project.feature_available?(:operations, context.current_user)
- end
+ context.project.feature_available?(:monitor, context.current_user)
end
def metrics_dashboard_menu_item
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index afe26cea26f..5c91b399eec 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -8655,6 +8655,9 @@ msgstr ""
msgid "CiVariables|This %{entity} has %{currentVariableCount} defined CI/CD variables. The maximum number of variables per %{entity} is %{maxVariableLimit}. To add new variables, you must reduce the number of defined variables."
msgstr ""
+msgid "CiVariables|Trigger this manual action"
+msgstr ""
+
msgid "CiVariables|Type"
msgstr ""
@@ -8780,6 +8783,9 @@ msgstr ""
msgid "Click the link below to confirm your email address."
msgstr ""
+msgid "Click to expand"
+msgstr ""
+
msgid "Click to expand it."
msgstr ""
@@ -32548,9 +32554,6 @@ msgstr ""
msgid "ProjectSettings|Configure your infrastructure."
msgstr ""
-msgid "ProjectSettings|Configure your project resources and monitor their health."
-msgstr ""
-
msgid "ProjectSettings|Contact an admin to change this setting."
msgstr ""
@@ -32722,9 +32725,6 @@ msgstr ""
msgid "ProjectSettings|Only signed commits can be pushed to this repository."
msgstr ""
-msgid "ProjectSettings|Operations"
-msgstr ""
-
msgid "ProjectSettings|Override user notification preferences for all project members."
msgstr ""
@@ -40335,6 +40335,9 @@ msgstr ""
msgid "SuperSonics|Activation not possible due to true-up value mismatch"
msgstr ""
+msgid "SuperSonics|Add activation code"
+msgstr ""
+
msgid "SuperSonics|An error occurred while adding your subscription"
msgstr ""
@@ -40356,9 +40359,6 @@ msgstr ""
msgid "SuperSonics|Customers Portal"
msgstr ""
-msgid "SuperSonics|Enter activation code"
-msgstr ""
-
msgid "SuperSonics|Export license usage file"
msgstr ""
diff --git a/package.json b/package.json
index d3a378ae41a..21976a66dfa 100644
--- a/package.json
+++ b/package.json
@@ -124,7 +124,7 @@
"dropzone": "^4.2.0",
"editorconfig": "^0.15.3",
"emoji-regex": "^10.0.0",
- "esbuild": "0.15.9",
+ "esbuild": "0.15.18",
"esbuild-loader": "^2.20.0",
"fast-mersenne-twister": "1.0.2",
"file-loader": "^6.2.0",
diff --git a/qa/qa/page/project/job/show.rb b/qa/qa/page/project/job/show.rb
index 5506c5ed4d9..24fd34b4d22 100644
--- a/qa/qa/page/project/job/show.rb
+++ b/qa/qa/page/project/job/show.rb
@@ -15,7 +15,7 @@ module QA
element :pipeline_path, required: true
end
- view 'app/assets/javascripts/jobs/components/job/sidebar/legacy_sidebar_header.vue' do
+ view 'app/assets/javascripts/jobs/components/job/sidebar/sidebar_header.vue' do
element :retry_button
end
diff --git a/spec/controllers/dashboard/todos_controller_spec.rb b/spec/controllers/dashboard/todos_controller_spec.rb
index efa00877142..4f01839fec4 100644
--- a/spec/controllers/dashboard/todos_controller_spec.rb
+++ b/spec/controllers/dashboard/todos_controller_spec.rb
@@ -3,14 +3,12 @@
require 'spec_helper'
RSpec.describe Dashboard::TodosController do
- let(:user) { create(:user) }
- let(:author) { create(:user) }
- let(:project) { create(:project) }
- let(:todo_service) { TodoService.new }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project).tap { |project| project.add_developer(user) } }
+ let_it_be(:author) { create(:user) }
before do
sign_in(user)
- project.add_developer(user)
end
describe 'GET #index' do
@@ -83,11 +81,14 @@ RSpec.describe Dashboard::TodosController do
end
it_behaves_like 'paginated collection' do
- let!(:issues) { create_list(:issue, 3, project: project, assignees: [user]) }
+ let_it_be(:issues) { create_list(:issue, 3, project: project, assignees: [user]) }
let(:collection) { user.todos }
+ before_all do
+ issues.each { |issue| TodoService.new.new_issue(issue, user) }
+ end
+
before do
- issues.each { |issue| todo_service.new_issue(issue, user) }
allow(Kaminari.config).to receive(:default_per_page).and_return(2)
end
@@ -126,6 +127,26 @@ RSpec.describe Dashboard::TodosController do
expect(assigns(:todos)).to match_array(mentioned_todos)
end
+
+ context 'when filtering by type Issue' do
+ it 'also includes work item todos' do
+ mentioned_issue_todos = [
+ create(:todo, :mentioned, project: project, user: user, target: issues.first),
+ create(
+ :todo,
+ :mentioned,
+ project: project,
+ user: user,
+ target_id: issues.last.id,
+ target_type: 'WorkItem'
+ )
+ ]
+
+ get :index, params: { action_id: ::Todo::MENTIONED, type: 'Issue', project_id: project.id }
+
+ expect(assigns(:todos)).to match_array(mentioned_issue_todos)
+ end
+ end
end
end
diff --git a/spec/controllers/projects/settings/repository_controller_spec.rb b/spec/controllers/projects/settings/repository_controller_spec.rb
index 617558f56bf..51ea2e5d7c6 100644
--- a/spec/controllers/projects/settings/repository_controller_spec.rb
+++ b/spec/controllers/projects/settings/repository_controller_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Projects::Settings::RepositoryController do
+RSpec.describe Projects::Settings::RepositoryController, feature_category: :source_code_management do
let(:project) { create(:project_empty_repo, :public) }
let(:user) { create(:user) }
let(:base_params) { { namespace_id: project.namespace, project_id: project } }
@@ -19,6 +19,40 @@ RSpec.describe Projects::Settings::RepositoryController do
expect(response).to have_gitlab_http_status(:ok)
expect(response).to render_template(:show)
end
+
+ context 'when feature flag `group_protected_branches` disabled' do
+ before do
+ stub_feature_flags(group_protected_branches: false)
+ end
+
+ it 'does not assign instance variable `protected_group_branches`' do
+ get :show, params: base_params
+
+ expect(assigns).not_to include(:protected_group_branches)
+ end
+ end
+
+ context 'when feature flag `group_protected_branches` enabled' do
+ context 'when the root namespace is a user' do
+ it 'assigns empty instance variable `protected_group_branches`' do
+ get :show, params: base_params
+
+ expect(assigns[:protected_group_branches]).to eq([])
+ end
+ end
+
+ context 'when the root namespace is a group' do
+ let_it_be(:project) { create(:project_empty_repo, :public, :in_group) }
+
+ let(:protected_group_branch) { create(:protected_branch, group: project.root_namespace, project: nil) }
+
+ it 'assigns instance variable `protected_group_branches`' do
+ get :show, params: base_params
+
+ expect(assigns[:protected_group_branches]).to include(protected_group_branch)
+ end
+ end
+ end
end
describe 'PUT cleanup' do
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index fc72356f1f4..bc58eaa1d6f 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -920,35 +920,6 @@ RSpec.describe ProjectsController do
with_them do
it_behaves_like 'feature update success'
end
-
- context 'for feature_access_level operations_access_level' do
- let(:feature_access_level) { :operations_access_level }
-
- include_examples 'feature update failure'
- end
-
- context 'with feature flag split_operations_visibility_permissions disabled' do
- before do
- stub_feature_flags(split_operations_visibility_permissions: false)
- end
-
- context 'for feature_access_level operations_access_level' do
- let(:feature_access_level) { :operations_access_level }
-
- include_examples 'feature update success'
- end
-
- where(:feature_access_level) do
- %i[
- environments_access_level feature_flags_access_level
- monitor_access_level
- ]
- end
-
- with_them do
- it_behaves_like 'feature update failure'
- end
- end
end
end
diff --git a/spec/features/monitor_sidebar_link_spec.rb b/spec/features/monitor_sidebar_link_spec.rb
index b77089c8d17..d5f987d15c2 100644
--- a/spec/features/monitor_sidebar_link_spec.rb
+++ b/spec/features/monitor_sidebar_link_spec.rb
@@ -19,21 +19,13 @@ RSpec.describe 'Monitor dropdown sidebar', :aggregate_failures, feature_category
let(:enabled) { Featurable::PRIVATE }
let(:disabled) { Featurable::DISABLED }
- where(:flag_enabled, :operations_access_level, :monitor_level, :render) do
- true | ref(:disabled) | ref(:enabled) | true
- true | ref(:disabled) | ref(:disabled) | false
- true | ref(:enabled) | ref(:enabled) | true
- true | ref(:enabled) | ref(:disabled) | false
- false | ref(:disabled) | ref(:enabled) | false
- false | ref(:disabled) | ref(:disabled) | false
- false | ref(:enabled) | ref(:enabled) | true
- false | ref(:enabled) | ref(:disabled) | true
+ where(:monitor_level, :render) do
+ ref(:enabled) | true
+ ref(:disabled) | false
end
with_them do
it 'renders when expected to' do
- stub_feature_flags(split_operations_visibility_permissions: flag_enabled)
- project.project_feature.update_attribute(:operations_access_level, operations_access_level)
project.project_feature.update_attribute(:monitor_access_level, monitor_level)
visit project_issues_path(project)
@@ -51,7 +43,6 @@ RSpec.describe 'Monitor dropdown sidebar', :aggregate_failures, feature_category
let(:access_level) { ProjectFeature::PUBLIC }
before do
- project.project_feature.update_attribute(:operations_access_level, access_level)
project.project_feature.update_attribute(:monitor_access_level, access_level)
end
@@ -67,41 +58,19 @@ RSpec.describe 'Monitor dropdown sidebar', :aggregate_failures, feature_category
expect(page).not_to have_link('Kubernetes', href: project_clusters_path(project))
end
- context 'with new monitor visiblity flag disabled' do
- stub_feature_flags(split_operations_visibility_permissions: false)
+ context 'when monitor project feature is PRIVATE' do
+ let(:access_level) { ProjectFeature::PRIVATE }
- context 'when operations project feature is PRIVATE' do
- let(:access_level) { ProjectFeature::PRIVATE }
-
- it 'does not show the `Monitor` menu' do
- expect(page).not_to have_selector('a.shortcuts-monitor')
- end
- end
-
- context 'when operations project feature is DISABLED' do
- let(:access_level) { ProjectFeature::DISABLED }
-
- it 'does not show the `Operations` menu' do
- expect(page).not_to have_selector('a.shortcuts-monitor')
- end
+ it 'does not show the `Monitor` menu' do
+ expect(page).not_to have_selector('a.shortcuts-monitor')
end
end
- context 'with new monitor visiblity flag enabled' do
- context 'when monitor project feature is PRIVATE' do
- let(:access_level) { ProjectFeature::PRIVATE }
+ context 'when monitor project feature is DISABLED' do
+ let(:access_level) { ProjectFeature::DISABLED }
- it 'does not show the `Monitor` menu' do
- expect(page).not_to have_selector('a.shortcuts-monitor')
- end
- end
-
- context 'when operations project feature is DISABLED' do
- let(:access_level) { ProjectFeature::DISABLED }
-
- it 'does not show the `Operations` menu' do
- expect(page).not_to have_selector('a.shortcuts-monitor')
- end
+ it 'does not show the `Monitor` menu' do
+ expect(page).not_to have_selector('a.shortcuts-monitor')
end
end
end
diff --git a/spec/features/projects/blobs/edit_spec.rb b/spec/features/projects/blobs/edit_spec.rb
index 9ef7f6f6e85..144b4ed85cd 100644
--- a/spec/features/projects/blobs/edit_spec.rb
+++ b/spec/features/projects/blobs/edit_spec.rb
@@ -97,11 +97,16 @@ RSpec.describe 'Editing file blob', :js, feature_category: :projects do
"Add a table"
]
- before do
+ it "does not have any buttons" do
+ stub_feature_flags(source_editor_toolbar: true)
visit project_edit_blob_path(project, tree_join(branch, readme_file_path))
+ buttons = page.all('.file-buttons .md-header-toolbar button[type="button"]')
+ expect(buttons.length).to eq(0)
end
- it "has defined set of toolbar buttons" do
+ it "has defined set of toolbar buttons when the flag is off" do
+ stub_feature_flags(source_editor_toolbar: false)
+ visit project_edit_blob_path(project, tree_join(branch, readme_file_path))
buttons = page.all('.file-buttons .md-header-toolbar button[type="button"]')
expect(buttons.length).to eq(toolbar_buttons.length)
toolbar_buttons.each_with_index do |button_title, i|
diff --git a/spec/features/projects/environments/environment_spec.rb b/spec/features/projects/environments/environment_spec.rb
index 38563a3b1cf..75913082803 100644
--- a/spec/features/projects/environments/environment_spec.rb
+++ b/spec/features/projects/environments/environment_spec.rb
@@ -10,12 +10,83 @@ RSpec.describe 'Environment', feature_category: :projects do
before do
sign_in(user)
project.add_role(user, role)
+ stub_feature_flags(environment_details_vue: false)
end
def auto_stop_button_selector
%q{button[title="Prevent environment from auto-stopping"]}
end
+ describe 'environment details page vue' do
+ let_it_be(:environment) { create(:environment, project: project) }
+ let!(:permissions) {}
+ let!(:deployment) {}
+ let!(:action) {}
+ let!(:cluster) {}
+
+ before do
+ stub_feature_flags(environment_details_vue: true)
+ end
+
+ context 'with auto-stop' do
+ let_it_be(:environment) { create(:environment, :will_auto_stop, name: 'staging', project: project) }
+
+ before do
+ visit_environment(environment)
+ end
+
+ it 'shows auto stop info', :js do
+ expect(page).to have_content('Auto stops')
+ end
+
+ it 'shows auto stop button', :js do
+ expect(page).to have_selector(auto_stop_button_selector)
+ expect(page.find(auto_stop_button_selector).find(:xpath, '..')['action']).to have_content(cancel_auto_stop_project_environment_path(environment.project, environment))
+ end
+
+ it 'allows user to cancel auto stop', :js do
+ page.find(auto_stop_button_selector).click
+ wait_for_all_requests
+ expect(page).to have_content('Auto stop successfully canceled.')
+ expect(page).not_to have_selector(auto_stop_button_selector)
+ end
+ end
+
+ context 'with deployments' do
+ before do
+ visit_environment(environment)
+ end
+
+ context 'when there is a successful deployment' do
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+ let(:build) { create(:ci_build, :success, pipeline: pipeline) }
+
+ let(:deployment) do
+ create(:deployment, :success, environment: environment, deployable: build)
+ end
+
+ it 'does show deployments', :js do
+ wait_for_requests
+ expect(page).to have_link("#{build.name} (##{build.id})")
+ end
+ end
+
+ context 'when there is a failed deployment' do
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+ let(:build) { create(:ci_build, pipeline: pipeline) }
+
+ let(:deployment) do
+ create(:deployment, :failed, environment: environment, deployable: build)
+ end
+
+ it 'does show deployments', :js do
+ wait_for_requests
+ expect(page).to have_link("#{build.name} (##{build.id})")
+ end
+ end
+ end
+ end
+
describe 'environment details page' do
let_it_be(:environment) { create(:environment, project: project) }
let!(:permissions) {}
diff --git a/spec/finders/todos_finder_spec.rb b/spec/finders/todos_finder_spec.rb
index 5611a67e977..bcead6b0170 100644
--- a/spec/finders/todos_finder_spec.rb
+++ b/spec/finders/todos_finder_spec.rb
@@ -327,9 +327,9 @@ RSpec.describe TodosFinder do
it 'returns the expected types' do
expected_result =
if Gitlab.ee?
- %w[Epic Issue MergeRequest DesignManagement::Design AlertManagement::Alert]
+ %w[Epic Issue WorkItem MergeRequest DesignManagement::Design AlertManagement::Alert]
else
- %w[Issue MergeRequest DesignManagement::Design AlertManagement::Alert]
+ %w[Issue WorkItem MergeRequest DesignManagement::Design AlertManagement::Alert]
end
expect(described_class.todo_types).to contain_exactly(*expected_result)
diff --git a/spec/frontend/editor/components/source_editor_toolbar_button_spec.js b/spec/frontend/editor/components/source_editor_toolbar_button_spec.js
index f62061f6eaf..ff377494312 100644
--- a/spec/frontend/editor/components/source_editor_toolbar_button_spec.js
+++ b/spec/frontend/editor/components/source_editor_toolbar_button_spec.js
@@ -15,6 +15,9 @@ describe('Source Editor Toolbar button', () => {
propsData: {
...props,
},
+ stubs: {
+ GlButton,
+ },
});
};
@@ -52,6 +55,41 @@ describe('Source Editor Toolbar button', () => {
const btn = findButton();
expect(btn.props()).toMatchObject(customProps);
});
+
+ describe('CSS class', () => {
+ let blueprintClasses;
+
+ beforeEach(() => {
+ createComponent();
+ blueprintClasses = findButton().element.classList;
+ });
+
+ it.each`
+ cssClass | expectedExtraClasses
+ ${undefined} | ${['']}
+ ${''} | ${['']}
+ ${'foo'} | ${['foo']}
+ ${'foo bar'} | ${['foo', 'bar']}
+ `(
+ 'does set CSS class correctly when `class` is "$cssClass"',
+ ({ cssClass, expectedExtraClasses }) => {
+ createComponent({
+ button: {
+ ...defaultBtn,
+ class: cssClass,
+ },
+ });
+ const btn = findButton().element;
+ expectedExtraClasses.forEach((c) => {
+ if (c) {
+ expect(btn.classList.contains(c)).toBe(true);
+ } else {
+ expect(btn.classList).toEqual(blueprintClasses);
+ }
+ });
+ },
+ );
+ });
});
describe('data attributes', () => {
diff --git a/spec/frontend/editor/source_editor_markdown_ext_spec.js b/spec/frontend/editor/source_editor_markdown_ext_spec.js
index 3e8c287df2f..33e4b4bfc8e 100644
--- a/spec/frontend/editor/source_editor_markdown_ext_spec.js
+++ b/spec/frontend/editor/source_editor_markdown_ext_spec.js
@@ -1,7 +1,9 @@
import MockAdapter from 'axios-mock-adapter';
import { Range, Position } from 'monaco-editor';
import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
+import { EXTENSION_MARKDOWN_BUTTONS } from '~/editor/constants';
import { EditorMarkdownExtension } from '~/editor/extensions/source_editor_markdown_ext';
+import { ToolbarExtension } from '~/editor/extensions/source_editor_toolbar_ext';
import SourceEditor from '~/editor/source_editor';
import axios from '~/lib/utils/axios_utils';
@@ -36,7 +38,7 @@ describe('Markdown Extension for Source Editor', () => {
blobPath: markdownPath,
blobContent: text,
});
- instance.use({ definition: EditorMarkdownExtension });
+ instance.use([{ definition: ToolbarExtension }, { definition: EditorMarkdownExtension }]);
});
afterEach(() => {
@@ -47,6 +49,16 @@ describe('Markdown Extension for Source Editor', () => {
resetHTMLFixture();
});
+ describe('toolbar', () => {
+ it('renders all the buttons', () => {
+ const btns = instance.toolbar.getAllItems();
+ expect(btns).toHaveLength(EXTENSION_MARKDOWN_BUTTONS.length);
+ EXTENSION_MARKDOWN_BUTTONS.forEach((btn, i) => {
+ expect(btns[i].id).toBe(btn.id);
+ });
+ });
+ });
+
describe('getSelectedText', () => {
it('does not fail if there is no selection and returns the empty string', () => {
jest.spyOn(instance, 'getSelection');
diff --git a/spec/frontend/environments/environment_details_page_spec.js b/spec/frontend/environments/environment_details_page_spec.js
new file mode 100644
index 00000000000..5a02b34250f
--- /dev/null
+++ b/spec/frontend/environments/environment_details_page_spec.js
@@ -0,0 +1,50 @@
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import { GlLoadingIcon, GlTableLite } from '@gitlab/ui';
+import resolvedEnvironmentDetails from 'test_fixtures/graphql/environments/graphql/queries/environment_details.query.graphql.json';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
+import createMockApollo from '../__helpers__/mock_apollo_helper';
+import waitForPromises from '../__helpers__/wait_for_promises';
+import EnvironmentsDetailPage from '../../../app/assets/javascripts/environments/environment_details/index.vue';
+import getEnvironmentDetails from '../../../app/assets/javascripts/environments/graphql/queries/environment_details.query.graphql';
+
+describe('~/environments/environment_details/page.vue', () => {
+ Vue.use(VueApollo);
+
+ let wrapper;
+
+ const createWrapper = () => {
+ const mockApollo = createMockApollo([
+ [getEnvironmentDetails, jest.fn().mockResolvedValue(resolvedEnvironmentDetails)],
+ ]);
+
+ return mountExtended(EnvironmentsDetailPage, {
+ apolloProvider: mockApollo,
+ propsData: {
+ projectFullPath: resolvedEnvironmentDetails.data.project.fullPath,
+ environmentName: resolvedEnvironmentDetails.data.project.environment.name,
+ },
+ });
+ };
+
+ describe('when fetching data', () => {
+ it('should show a loading indicator', () => {
+ wrapper = createWrapper();
+
+ expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
+ expect(wrapper.findComponent(GlTableLite).exists()).not.toBe(true);
+ });
+ });
+
+ describe('when data is fetched', () => {
+ beforeEach(async () => {
+ wrapper = createWrapper();
+ await waitForPromises();
+ });
+
+ it('should render a table when query is loaded', async () => {
+ expect(wrapper.findComponent(GlLoadingIcon).exists()).not.toBe(true);
+ expect(wrapper.findComponent(GlTableLite).exists()).toBe(true);
+ });
+ });
+});
diff --git a/spec/frontend/environments/helpers/__snapshots__/deployment_data_transformation_helper_spec.js.snap b/spec/frontend/environments/helpers/__snapshots__/deployment_data_transformation_helper_spec.js.snap
new file mode 100644
index 00000000000..401c10338c1
--- /dev/null
+++ b/spec/frontend/environments/helpers/__snapshots__/deployment_data_transformation_helper_spec.js.snap
@@ -0,0 +1,127 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`deployment_data_transformation_helper convertToDeploymentTableRow should be converted to proper table row data 1`] = `
+Object {
+ "commit": Object {
+ "author": Object {
+ "avatar_url": "/uploads/-/system/user/avatar/1/avatar.png",
+ "path": "http://gdk.test:3000/root",
+ "username": "Administrator",
+ },
+ "commitRef": Object {
+ "name": "main",
+ },
+ "commitUrl": "http://gdk.test:3000/gitlab-org/pipelinestest/-/commit/0cb48dd5deddb7632fd7c3defb16075fc6c3ca74",
+ "shortSha": "0cb48dd5",
+ "tag": false,
+ "title": "Update .gitlab-ci.yml file",
+ },
+ "created": "2022-10-17T07:44:17Z",
+ "deployed": "2022-10-17T07:44:43Z",
+ "id": "31",
+ "job": Object {
+ "label": "deploy-prod (#860)",
+ "webPath": "/gitlab-org/pipelinestest/-/jobs/860",
+ },
+ "status": "success",
+ "triggerer": Object {
+ "avatarUrl": "/uploads/-/system/user/avatar/1/avatar.png",
+ "id": "gid://gitlab/User/1",
+ "name": "Administrator",
+ "webUrl": "http://gdk.test:3000/root",
+ },
+}
+`;
+
+exports[`deployment_data_transformation_helper convertToDeploymentTableRow should be converted to proper table row data 2`] = `
+Object {
+ "commit": Object {
+ "author": Object {
+ "avatar_url": "/uploads/-/system/user/avatar/1/avatar.png",
+ "path": "http://gdk.test:3000/root",
+ "username": "Administrator",
+ },
+ "commitRef": Object {
+ "name": "main",
+ },
+ "commitUrl": "http://gdk.test:3000/gitlab-org/pipelinestest/-/commit/0cb48dd5deddb7632fd7c3defb16075fc6c3ca74",
+ "shortSha": "0cb48dd5",
+ "tag": false,
+ "title": "Update .gitlab-ci.yml file",
+ },
+ "created": "2022-10-17T07:44:17Z",
+ "deployed": "2022-10-17T07:44:43Z",
+ "id": "31",
+ "job": undefined,
+ "status": "success",
+ "triggerer": Object {
+ "avatarUrl": "/uploads/-/system/user/avatar/1/avatar.png",
+ "id": "gid://gitlab/User/1",
+ "name": "Administrator",
+ "webUrl": "http://gdk.test:3000/root",
+ },
+}
+`;
+
+exports[`deployment_data_transformation_helper convertToDeploymentTableRow should be converted to proper table row data 3`] = `
+Object {
+ "commit": Object {
+ "author": Object {
+ "avatar_url": "/uploads/-/system/user/avatar/1/avatar.png",
+ "path": "http://gdk.test:3000/root",
+ "username": "Administrator",
+ },
+ "commitRef": Object {
+ "name": "main",
+ },
+ "commitUrl": "http://gdk.test:3000/gitlab-org/pipelinestest/-/commit/0cb48dd5deddb7632fd7c3defb16075fc6c3ca74",
+ "shortSha": "0cb48dd5",
+ "tag": false,
+ "title": "Update .gitlab-ci.yml file",
+ },
+ "created": "2022-10-17T07:44:17Z",
+ "deployed": "",
+ "id": "31",
+ "job": null,
+ "status": "success",
+ "triggerer": Object {
+ "avatarUrl": "/uploads/-/system/user/avatar/1/avatar.png",
+ "id": "gid://gitlab/User/1",
+ "name": "Administrator",
+ "webUrl": "http://gdk.test:3000/root",
+ },
+}
+`;
+
+exports[`deployment_data_transformation_helper getAuthorFromCommit should be properly converted 1`] = `
+Object {
+ "avatar_url": "/uploads/-/system/user/avatar/1/avatar.png",
+ "path": "http://gdk.test:3000/root",
+ "username": "Administrator",
+}
+`;
+
+exports[`deployment_data_transformation_helper getAuthorFromCommit should be properly converted 2`] = `
+Object {
+ "avatar_url": "https://www.gravatar.com/avatar/91811aee1dec1b2655fa56f894e9e7c9?s=80&d=identicon",
+ "path": "mailto:azubov@gitlab.com",
+ "username": "Andrei Zubov",
+}
+`;
+
+exports[`deployment_data_transformation_helper getCommitFromDeploymentNode should get correclty formatted commit object 1`] = `
+Object {
+ "author": Object {
+ "avatar_url": "/uploads/-/system/user/avatar/1/avatar.png",
+ "path": "http://gdk.test:3000/root",
+ "username": "Administrator",
+ },
+ "commitRef": Object {
+ "name": "main",
+ },
+ "commitUrl": "http://gdk.test:3000/gitlab-org/pipelinestest/-/commit/0cb48dd5deddb7632fd7c3defb16075fc6c3ca74",
+ "shortSha": "0cb48dd5",
+ "tag": false,
+ "title": "Update .gitlab-ci.yml file",
+}
+`;
diff --git a/spec/frontend/environments/helpers/deployment_data_transformation_helper_spec.js b/spec/frontend/environments/helpers/deployment_data_transformation_helper_spec.js
new file mode 100644
index 00000000000..8bb87c0a208
--- /dev/null
+++ b/spec/frontend/environments/helpers/deployment_data_transformation_helper_spec.js
@@ -0,0 +1,96 @@
+import {
+ getAuthorFromCommit,
+ getCommitFromDeploymentNode,
+ convertToDeploymentTableRow,
+} from '~/environments/helpers/deployment_data_transformation_helper';
+
+describe('deployment_data_transformation_helper', () => {
+ const commitWithAuthor = {
+ id: 'gid://gitlab/CommitPresenter/0cb48dd5deddb7632fd7c3defb16075fc6c3ca74',
+ shortId: '0cb48dd5',
+ message: 'Update .gitlab-ci.yml file',
+ webUrl:
+ 'http://gdk.test:3000/gitlab-org/pipelinestest/-/commit/0cb48dd5deddb7632fd7c3defb16075fc6c3ca74',
+ authorGravatar:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ authorName: 'Administrator',
+ authorEmail: 'admin@example.com',
+ author: {
+ id: 'gid://gitlab/User/1',
+ name: 'Administrator',
+ avatarUrl: '/uploads/-/system/user/avatar/1/avatar.png',
+ webUrl: 'http://gdk.test:3000/root',
+ },
+ };
+
+ const commitWithourAuthor = {
+ id: 'gid://gitlab/CommitPresenter/02274a949a88c9aef68a29685d99bd9a661a7f9b',
+ shortId: '02274a94',
+ message: 'Commit message',
+ webUrl:
+ 'http://gdk.test:3000/gitlab-org/pipelinestest/-/commit/02274a949a88c9aef68a29685d99bd9a661a7f9b',
+ authorGravatar:
+ 'https://www.gravatar.com/avatar/91811aee1dec1b2655fa56f894e9e7c9?s=80&d=identicon',
+ authorName: 'Andrei Zubov',
+ authorEmail: 'azubov@gitlab.com',
+ author: null,
+ };
+
+ const deploymentNode = {
+ id: 'gid://gitlab/Deployment/76',
+ iid: '31',
+ status: 'SUCCESS',
+ createdAt: '2022-10-17T07:44:17Z',
+ ref: 'main',
+ tag: false,
+ job: {
+ name: 'deploy-prod',
+ refName: 'main',
+ id: 'gid://gitlab/Ci::Build/860',
+ webPath: '/gitlab-org/pipelinestest/-/jobs/860',
+ },
+ commit: commitWithAuthor,
+ triggerer: {
+ id: 'gid://gitlab/User/1',
+ webUrl: 'http://gdk.test:3000/root',
+ name: 'Administrator',
+ avatarUrl: '/uploads/-/system/user/avatar/1/avatar.png',
+ },
+ finishedAt: '2022-10-17T07:44:43Z',
+ };
+
+ const deploymentNodeWithNoJob = {
+ ...deploymentNode,
+ job: null,
+ finishedAt: null,
+ };
+
+ describe('getAuthorFromCommit', () => {
+ it.each([commitWithAuthor, commitWithourAuthor])('should be properly converted', (commit) => {
+ expect(getAuthorFromCommit(commit)).toMatchSnapshot();
+ });
+ });
+
+ describe('getCommitFromDeploymentNode', () => {
+ it('should throw an error when commit field is missing', () => {
+ const emptyDeploymentNode = {};
+
+ expect(() => getCommitFromDeploymentNode(emptyDeploymentNode)).toThrow();
+ });
+
+ it('should get correclty formatted commit object', () => {
+ expect(getCommitFromDeploymentNode(deploymentNode)).toMatchSnapshot();
+ });
+ });
+
+ describe('convertToDeploymentTableRow', () => {
+ const deploymentNodeWithEmptyJob = { ...deploymentNode, job: undefined };
+
+ it.each([deploymentNode, deploymentNodeWithEmptyJob, deploymentNodeWithNoJob])(
+ 'should be converted to proper table row data',
+ (node) => {
+ expect(convertToDeploymentTableRow(node)).toMatchSnapshot();
+ },
+ );
+ });
+});
diff --git a/spec/frontend/fixtures/environments.rb b/spec/frontend/fixtures/environments.rb
new file mode 100644
index 00000000000..3ca5b50ac9c
--- /dev/null
+++ b/spec/frontend/fixtures/environments.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Environments (JavaScript fixtures)', feature_category: :environment_management do
+ include ApiHelpers
+ include JavaScriptFixturesHelpers
+ include GraphqlHelpers
+
+ let_it_be(:admin) { create(:admin, username: 'administrator', email: 'admin@example.gitlab.com') }
+ let_it_be(:group) { create(:group, path: 'environments-group') }
+ let_it_be(:project) { create(:project, :repository, group: group, path: 'environments-project') }
+
+ let_it_be(:environment) { create(:environment, name: 'staging', project: project) }
+
+ let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
+ let_it_be(:build) { create(:ci_build, :success, pipeline: pipeline) }
+
+ let(:user) { create(:user) }
+ let(:role) { :developer }
+ let_it_be(:deployment) do
+ create(:deployment, :success, environment: environment, deployable: nil)
+ end
+
+ let_it_be(:deployment_success) do
+ create(:deployment, :success, environment: environment, deployable: build)
+ end
+
+ let_it_be(:deployment_failed) do
+ create(:deployment, :failed, environment: environment, deployable: build)
+ end
+
+ let_it_be(:deployment_running) do
+ create(:deployment, :running, environment: environment, deployable: build)
+ end
+
+ describe GraphQL::Query, type: :request do
+ environment_details_query_path = 'environments/graphql/queries/environment_details.query.graphql'
+
+ it "graphql/#{environment_details_query_path}.json" do
+ query = get_graphql_query_as_string(environment_details_query_path)
+
+ post_graphql(query, current_user: admin,
+ variables:
+ {
+ projectFullPath: project.full_path,
+ environmentName: environment.name,
+ pageSize: 10
+ })
+ expect_graphql_errors_to_be_empty
+ end
+ end
+end
diff --git a/spec/frontend/jobs/components/job/empty_state_spec.js b/spec/frontend/jobs/components/job/empty_state_spec.js
index e1b9aa743e0..c6ab259bf46 100644
--- a/spec/frontend/jobs/components/job/empty_state_spec.js
+++ b/spec/frontend/jobs/components/job/empty_state_spec.js
@@ -1,6 +1,7 @@
-import { mount } from '@vue/test-utils';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import EmptyState from '~/jobs/components/job/empty_state.vue';
-import { mockId } from './mock_data';
+import ManualVariablesForm from '~/jobs/components/job/manual_variables_form.vue';
+import { mockFullPath, mockId } from './mock_data';
describe('Empty State', () => {
let wrapper;
@@ -11,24 +12,28 @@ describe('Empty State', () => {
jobId: mockId,
title: 'This job has not started yet',
playable: false,
+ isRetryable: true,
};
const createWrapper = (props) => {
- wrapper = mount(EmptyState, {
+ wrapper = shallowMountExtended(EmptyState, {
propsData: {
...defaultProps,
...props,
},
+ provide: {
+ projectPath: mockFullPath,
+ },
});
};
const content = 'This job is in pending state and is waiting to be picked by a runner';
const findEmptyStateImage = () => wrapper.find('img');
- const findTitle = () => wrapper.find('[data-testid="job-empty-state-title"]');
- const findContent = () => wrapper.find('[data-testid="job-empty-state-content"]');
- const findAction = () => wrapper.find('[data-testid="job-empty-state-action"]');
- const findManualVarsForm = () => wrapper.find('[data-testid="manual-vars-form"]');
+ const findTitle = () => wrapper.findByTestId('job-empty-state-title');
+ const findContent = () => wrapper.findByTestId('job-empty-state-content');
+ const findAction = () => wrapper.findByTestId('job-empty-state-action');
+ const findManualVarsForm = () => wrapper.findComponent(ManualVariablesForm);
afterEach(() => {
if (wrapper?.destroy) {
diff --git a/spec/frontend/jobs/components/job/job_app_spec.js b/spec/frontend/jobs/components/job/job_app_spec.js
index 822528403cf..98f1979db1b 100644
--- a/spec/frontend/jobs/components/job/job_app_spec.js
+++ b/spec/frontend/jobs/components/job/job_app_spec.js
@@ -1,14 +1,15 @@
-import { GlLoadingIcon } from '@gitlab/ui';
-import { mount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
-import MockAdapter from 'axios-mock-adapter';
import Vuex from 'vuex';
-import delayedJobFixture from 'test_fixtures/jobs/delayed.json';
+import { GlLoadingIcon } from '@gitlab/ui';
+import MockAdapter from 'axios-mock-adapter';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { TEST_HOST } from 'helpers/test_constants';
import EmptyState from '~/jobs/components/job/empty_state.vue';
import EnvironmentsBlock from '~/jobs/components/job/environments_block.vue';
import ErasedBlock from '~/jobs/components/job/erased_block.vue';
import JobApp from '~/jobs/components/job/job_app.vue';
+import JobLog from '~/jobs/components/log/log.vue';
+import JobLogTopBar from '~/jobs/components/job/job_log_controllers.vue';
import Sidebar from '~/jobs/components/job/sidebar/sidebar.vue';
import StuckBlock from '~/jobs/components/job/stuck_block.vue';
import UnmetPrerequisitesBlock from '~/jobs/components/job/unmet_prerequisites_block.vue';
@@ -40,7 +41,10 @@ describe('Job App', () => {
};
const createComponent = () => {
- wrapper = mount(JobApp, { propsData: { ...props }, store });
+ wrapper = shallowMountExtended(JobApp, {
+ propsData: { ...props },
+ store,
+ });
};
const setupAndMount = async ({ jobData = {}, jobLogData = {} } = {}) => {
@@ -59,22 +63,16 @@ describe('Job App', () => {
const findLoadingComponent = () => wrapper.findComponent(GlLoadingIcon);
const findSidebar = () => wrapper.findComponent(Sidebar);
- const findJobContent = () => wrapper.find('[data-testid="job-content"');
const findStuckBlockComponent = () => wrapper.findComponent(StuckBlock);
- const findStuckBlockWithTags = () => wrapper.find('[data-testid="job-stuck-with-tags"');
- const findStuckBlockNoActiveRunners = () =>
- wrapper.find('[data-testid="job-stuck-no-active-runners"');
const findFailedJobComponent = () => wrapper.findComponent(UnmetPrerequisitesBlock);
const findEnvironmentsBlockComponent = () => wrapper.findComponent(EnvironmentsBlock);
const findErasedBlock = () => wrapper.findComponent(ErasedBlock);
- const findArchivedJob = () => wrapper.find('[data-testid="archived-job"]');
const findEmptyState = () => wrapper.findComponent(EmptyState);
- const findJobNewIssueLink = () => wrapper.find('[data-testid="job-new-issue"]');
- const findJobEmptyStateTitle = () => wrapper.find('[data-testid="job-empty-state-title"]');
- const findJobLogScrollTop = () => wrapper.find('[data-testid="job-controller-scroll-top"]');
- const findJobLogScrollBottom = () => wrapper.find('[data-testid="job-controller-scroll-bottom"]');
- const findJobLogController = () => wrapper.find('[data-testid="job-raw-link-controller"]');
- const findJobLogEraseLink = () => wrapper.find('[data-testid="job-log-erase-link"]');
+ const findJobLog = () => wrapper.findComponent(JobLog);
+ const findJobLogTopBar = () => wrapper.findComponent(JobLogTopBar);
+
+ const findJobContent = () => wrapper.findByTestId('job-content');
+ const findArchivedJob = () => wrapper.findByTestId('archived-job');
beforeEach(() => {
mock = new MockAdapter(axios);
@@ -116,36 +114,6 @@ describe('Job App', () => {
expect(wrapper.vm.shouldRenderCalloutMessage).toBe(true);
}));
});
-
- describe('triggered job', () => {
- beforeEach(() => {
- const aYearAgo = new Date();
- aYearAgo.setFullYear(aYearAgo.getFullYear() - 1);
-
- return setupAndMount({
- jobData: { started: aYearAgo.toISOString(), started_at: aYearAgo.toISOString() },
- });
- });
-
- it('should render provided job information', () => {
- expect(wrapper.find('.header-main-content').text().replace(/\s+/g, ' ').trim()).toContain(
- 'passed Job test triggered 1 year ago by Root',
- );
- });
-
- it('should render new issue link', () => {
- expect(findJobNewIssueLink().attributes('href')).toEqual(job.new_issue_path);
- });
- });
-
- describe('created job', () => {
- it('should render created key', () =>
- setupAndMount().then(() => {
- expect(
- wrapper.find('.header-main-content').text().replace(/\s+/g, ' ').trim(),
- ).toContain('passed Job test created 3 weeks ago by Root');
- }));
- });
});
describe('stuck block', () => {
@@ -169,57 +137,10 @@ describe('Job App', () => {
},
}).then(() => {
expect(findStuckBlockComponent().exists()).toBe(true);
- expect(findStuckBlockNoActiveRunners().exists()).toBe(true);
- }));
- });
-
- describe('when available runners can not run specified tag', () => {
- it('renders tags in stuck block when there are no runners', () =>
- setupAndMount({
- jobData: {
- status: {
- group: 'pending',
- icon: 'status_pending',
- label: 'pending',
- text: 'pending',
- details_path: 'path',
- },
- stuck: true,
- runners: {
- available: false,
- online: false,
- },
- },
- }).then(() => {
- expect(findStuckBlockComponent().text()).toContain(job.tags[0]);
- expect(findStuckBlockWithTags().exists()).toBe(true);
- }));
- });
-
- describe('when runners are offline and build has tags', () => {
- it('renders message about job being stuck because of no runners with the specified tags', () =>
- setupAndMount({
- jobData: {
- status: {
- group: 'pending',
- icon: 'status_pending',
- label: 'pending',
- text: 'pending',
- details_path: 'path',
- },
- stuck: true,
- runners: {
- available: true,
- online: true,
- },
- },
- }).then(() => {
- expect(findStuckBlockComponent().text()).toContain(job.tags[0]);
- expect(findStuckBlockWithTags().exists()).toBe(true);
}));
});
- it('does not renders stuck block when there are no runners', () =>
+ it('does not render stuck block when there are runners', () =>
setupAndMount({
jobData: {
runners: { available: true },
@@ -351,45 +272,13 @@ describe('Job App', () => {
setupAndMount({ jobData: { has_trace: true } }).then(() => {
expect(findEmptyState().exists()).toBe(false);
}));
-
- it('displays remaining time for a delayed job', () => {
- const oneHourInMilliseconds = 3600000;
- jest
- .spyOn(Date, 'now')
- .mockImplementation(
- () => new Date(delayedJobFixture.scheduled_at).getTime() - oneHourInMilliseconds,
- );
- return setupAndMount({ jobData: delayedJobFixture }).then(() => {
- expect(findEmptyState().exists()).toBe(true);
-
- const title = findJobEmptyStateTitle().text();
-
- expect(title).toEqual('This is a delayed job to run in 01:00:00');
- });
- });
});
describe('sidebar', () => {
- it('has no blank blocks', async () => {
- await setupAndMount({
- jobData: {
- duration: null,
- finished_at: null,
- erased_at: null,
- queued: null,
- runner: null,
- coverage: null,
- tags: [],
- cancel_path: null,
- },
- });
+ it('renders sidebar', async () => {
+ await setupAndMount();
- const blocks = wrapper.findAll('.blocks-container > *').wrappers;
- expect(blocks.length).toBeGreaterThan(0);
-
- blocks.forEach((block) => {
- expect(block.text().trim()).not.toBe('');
- });
+ expect(findSidebar().exists()).toBe(true);
});
});
});
@@ -410,31 +299,15 @@ describe('Job App', () => {
});
});
- describe('job log controls', () => {
- beforeEach(() =>
- setupAndMount({
- jobLogData: {
- html: '<span>Update</span>',
- status: 'success',
- append: false,
- size: 50,
- total: 100,
- complete: true,
- },
- }),
- );
-
- it('should render scroll buttons', () => {
- expect(findJobLogScrollTop().exists()).toBe(true);
- expect(findJobLogScrollBottom().exists()).toBe(true);
- });
+ describe('job log', () => {
+ beforeEach(() => setupAndMount());
- it('should render link to raw ouput', () => {
- expect(findJobLogController().exists()).toBe(true);
+ it('should render job log header', () => {
+ expect(findJobLogTopBar().exists()).toBe(true);
});
- it('should render link to erase job', () => {
- expect(findJobLogEraseLink().exists()).toBe(true);
+ it('should render job log', () => {
+ expect(findJobLog().exists()).toBe(true);
});
});
});
diff --git a/spec/frontend/jobs/components/job/job_sidebar_retry_button_spec.js b/spec/frontend/jobs/components/job/job_sidebar_retry_button_spec.js
index b04a5e07ea5..91821a38a78 100644
--- a/spec/frontend/jobs/components/job/job_sidebar_retry_button_spec.js
+++ b/spec/frontend/jobs/components/job/job_sidebar_retry_button_spec.js
@@ -16,7 +16,7 @@ describe('Job Sidebar Retry Button', () => {
wrapper = shallowMountExtended(JobsSidebarRetryButton, {
propsData: {
href: job.retry_path,
- isManualJob: true,
+ isManualJob: false,
modalId: 'modal-id',
...props,
},
diff --git a/spec/frontend/jobs/components/job/legacy_manual_variables_form_spec.js b/spec/frontend/jobs/components/job/legacy_manual_variables_form_spec.js
deleted file mode 100644
index 184562b2968..00000000000
--- a/spec/frontend/jobs/components/job/legacy_manual_variables_form_spec.js
+++ /dev/null
@@ -1,156 +0,0 @@
-import { GlSprintf, GlLink } from '@gitlab/ui';
-import { mount } from '@vue/test-utils';
-import Vue, { nextTick } from 'vue';
-import Vuex from 'vuex';
-import { extendedWrapper } from 'helpers/vue_test_utils_helper';
-import LegacyManualVariablesForm from '~/jobs/components/job/legacy_manual_variables_form.vue';
-
-Vue.use(Vuex);
-
-describe('Manual Variables Form', () => {
- let wrapper;
- let store;
-
- const requiredProps = {
- action: {
- path: '/play',
- method: 'post',
- button_title: 'Trigger this manual action',
- },
- };
-
- const createComponent = (props = {}) => {
- store = new Vuex.Store({
- actions: {
- triggerManualJob: jest.fn(),
- },
- });
-
- wrapper = extendedWrapper(
- mount(LegacyManualVariablesForm, {
- propsData: { ...requiredProps, ...props },
- store,
- stubs: {
- GlSprintf,
- },
- }),
- );
- };
-
- const findHelpText = () => wrapper.findComponent(GlSprintf);
- const findHelpLink = () => wrapper.findComponent(GlLink);
-
- const findTriggerBtn = () => wrapper.findByTestId('trigger-manual-job-btn');
- const findDeleteVarBtn = () => wrapper.findByTestId('delete-variable-btn');
- const findAllDeleteVarBtns = () => wrapper.findAllByTestId('delete-variable-btn');
- const findDeleteVarBtnPlaceholder = () => wrapper.findByTestId('delete-variable-btn-placeholder');
- const findCiVariableKey = () => wrapper.findByTestId('ci-variable-key');
- const findAllCiVariableKeys = () => wrapper.findAllByTestId('ci-variable-key');
- const findCiVariableValue = () => wrapper.findByTestId('ci-variable-value');
- const findAllVariables = () => wrapper.findAllByTestId('ci-variable-row');
-
- const setCiVariableKey = () => {
- findCiVariableKey().setValue('new key');
- findCiVariableKey().vm.$emit('change');
- nextTick();
- };
-
- const setCiVariableKeyByPosition = (position, value) => {
- findAllCiVariableKeys().at(position).setValue(value);
- findAllCiVariableKeys().at(position).vm.$emit('change');
- nextTick();
- };
-
- beforeEach(() => {
- createComponent();
- });
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- it('creates a new variable when user enters a new key value', async () => {
- expect(findAllVariables()).toHaveLength(1);
-
- await setCiVariableKey();
-
- expect(findAllVariables()).toHaveLength(2);
- });
-
- it('does not create extra empty variables', async () => {
- expect(findAllVariables()).toHaveLength(1);
-
- await setCiVariableKey();
-
- expect(findAllVariables()).toHaveLength(2);
-
- await setCiVariableKey();
-
- expect(findAllVariables()).toHaveLength(2);
- });
-
- it('removes the correct variable row', async () => {
- const variableKeyNameOne = 'key-one';
- const variableKeyNameThree = 'key-three';
-
- await setCiVariableKeyByPosition(0, variableKeyNameOne);
-
- await setCiVariableKeyByPosition(1, 'key-two');
-
- await setCiVariableKeyByPosition(2, variableKeyNameThree);
-
- expect(findAllVariables()).toHaveLength(4);
-
- await findAllDeleteVarBtns().at(1).trigger('click');
-
- expect(findAllVariables()).toHaveLength(3);
-
- expect(findAllCiVariableKeys().at(0).element.value).toBe(variableKeyNameOne);
- expect(findAllCiVariableKeys().at(1).element.value).toBe(variableKeyNameThree);
- expect(findAllCiVariableKeys().at(2).element.value).toBe('');
- });
-
- it('trigger button is disabled after trigger action', async () => {
- expect(findTriggerBtn().props('disabled')).toBe(false);
-
- await findTriggerBtn().trigger('click');
-
- expect(findTriggerBtn().props('disabled')).toBe(true);
- });
-
- it('delete variable button should only show when there is more than one variable', async () => {
- expect(findDeleteVarBtn().exists()).toBe(false);
-
- await setCiVariableKey();
-
- expect(findDeleteVarBtn().exists()).toBe(true);
- });
-
- it('delete variable button placeholder should only exist when a user cannot remove', async () => {
- expect(findDeleteVarBtnPlaceholder().exists()).toBe(true);
- });
-
- it('renders help text with provided link', () => {
- expect(findHelpText().exists()).toBe(true);
- expect(findHelpLink().attributes('href')).toBe(
- '/help/ci/variables/index#add-a-cicd-variable-to-a-project',
- );
- });
-
- it('passes variables in correct format', async () => {
- jest.spyOn(store, 'dispatch');
-
- await setCiVariableKey();
-
- await findCiVariableValue().setValue('new value');
-
- await findTriggerBtn().trigger('click');
-
- expect(store.dispatch).toHaveBeenCalledWith('triggerManualJob', [
- {
- key: 'new key',
- secret_value: 'new value',
- },
- ]);
- });
-});
diff --git a/spec/frontend/jobs/components/job/legacy_sidebar_header_spec.js b/spec/frontend/jobs/components/job/legacy_sidebar_header_spec.js
deleted file mode 100644
index 8fbb418232b..00000000000
--- a/spec/frontend/jobs/components/job/legacy_sidebar_header_spec.js
+++ /dev/null
@@ -1,102 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import { extendedWrapper } from 'helpers/vue_test_utils_helper';
-import JobRetryButton from '~/jobs/components/job/sidebar/job_sidebar_retry_button.vue';
-import LegacySidebarHeader from '~/jobs/components/job/sidebar/legacy_sidebar_header.vue';
-import createStore from '~/jobs/store';
-import job, { failedJobStatus } from '../../mock_data';
-
-describe('Legacy Sidebar Header', () => {
- let store;
- let wrapper;
-
- const findCancelButton = () => wrapper.findByTestId('cancel-button');
- const findRetryButton = () => wrapper.findComponent(JobRetryButton);
- const findEraseLink = () => wrapper.findByTestId('job-log-erase-link');
-
- const createWrapper = (props) => {
- store = createStore();
-
- wrapper = extendedWrapper(
- shallowMount(LegacySidebarHeader, {
- propsData: {
- job,
- ...props,
- },
- store,
- }),
- );
- };
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- describe('when job log is erasable', () => {
- beforeEach(() => {
- createWrapper();
- });
-
- it('renders erase job link', () => {
- expect(findEraseLink().exists()).toBe(true);
- });
-
- it('erase job link has correct path', () => {
- expect(findEraseLink().attributes('href')).toBe(job.erase_path);
- });
- });
-
- describe('when job log is not erasable', () => {
- beforeEach(() => {
- createWrapper({ job: { ...job, erase_path: null } });
- });
-
- it('does not render erase button', () => {
- expect(findEraseLink().exists()).toBe(false);
- });
- });
-
- describe('when the job is retryable', () => {
- beforeEach(() => {
- createWrapper();
- });
-
- it('should render the retry button', () => {
- expect(findRetryButton().props('href')).toBe(job.retry_path);
- });
-
- it('should have a different label when the job status is passed', () => {
- expect(findRetryButton().attributes('title')).toBe(
- LegacySidebarHeader.i18n.runAgainJobButtonLabel,
- );
- });
- });
-
- describe('when there is no retry path', () => {
- it('should not render a retry button', async () => {
- createWrapper({ job: { ...job, retry_path: null } });
-
- expect(findRetryButton().exists()).toBe(false);
- });
- });
-
- describe('when the job is cancelable', () => {
- beforeEach(() => {
- createWrapper();
- });
-
- it('should render link to cancel job', () => {
- expect(findCancelButton().props('icon')).toBe('cancel');
- expect(findCancelButton().attributes('href')).toBe(job.cancel_path);
- });
- });
-
- describe('when the job is failed', () => {
- describe('retry button', () => {
- it('should have a different label when the job status is failed', () => {
- createWrapper({ job: { ...job, status: failedJobStatus } });
-
- expect(findRetryButton().attributes('title')).toBe(LegacySidebarHeader.i18n.retryJobLabel);
- });
- });
- });
-});
diff --git a/spec/frontend/jobs/components/job/manual_variables_form_spec.js b/spec/frontend/jobs/components/job/manual_variables_form_spec.js
index 4384b2f4d7f..45a1e9dca76 100644
--- a/spec/frontend/jobs/components/job/manual_variables_form_spec.js
+++ b/spec/frontend/jobs/components/job/manual_variables_form_spec.js
@@ -35,6 +35,7 @@ describe('Manual Variables Form', () => {
propsData: {
...props,
jobId: mockId,
+ isRetryable: true,
},
provide: {
...defaultProvide,
diff --git a/spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js b/spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js
index 8e0ca544a1c..38f7a2e919d 100644
--- a/spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js
+++ b/spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js
@@ -24,7 +24,6 @@ const defaultProps = {
buildsAccessLevel: 20,
wikiAccessLevel: 20,
snippetsAccessLevel: 20,
- operationsAccessLevel: 20,
metricsDashboardAccessLevel: 20,
pagesAccessLevel: 10,
analyticsAccessLevel: 20,
@@ -136,9 +135,6 @@ describe('Settings Panel', () => {
wrapper.findComponent({ ref: 'metrics-visibility-settings' });
const findMetricsVisibilityInput = () =>
findMetricsVisibilitySettings().findComponent(ProjectFeatureSetting);
- const findOperationsSettings = () => wrapper.findComponent({ ref: 'operations-settings' });
- const findOperationsVisibilityInput = () =>
- findOperationsSettings().findComponent(ProjectFeatureSetting);
const findConfirmDangerButton = () => wrapper.findComponent(ConfirmDanger);
const findEnvironmentsSettings = () => wrapper.findComponent({ ref: 'environments-settings' });
const findFeatureFlagsSettings = () => wrapper.findComponent({ ref: 'feature-flags-settings' });
@@ -146,6 +142,8 @@ describe('Settings Panel', () => {
wrapper.findComponent({ ref: 'infrastructure-settings' });
const findReleasesSettings = () => wrapper.findComponent({ ref: 'environments-settings' });
const findMonitorSettings = () => wrapper.findComponent({ ref: 'monitor-settings' });
+ const findMonitorVisibilityInput = () =>
+ findMonitorSettings().findComponent(ProjectFeatureSetting);
afterEach(() => {
wrapper.destroy();
@@ -789,27 +787,27 @@ describe('Settings Panel', () => {
${featureAccessLevel.EVERYONE} | ${featureAccessLevel.NOT_ENABLED}
${featureAccessLevel.PROJECT_MEMBERS} | ${featureAccessLevel.NOT_ENABLED}
`(
- 'when updating Operations Settings access level from `$before` to `$after`, Metric Dashboard access is updated to `$after` as well',
+ 'when updating Monitor access level from `$before` to `$after`, Metric Dashboard access is updated to `$after` as well',
async ({ before, after }) => {
wrapper = mountComponent({
- currentSettings: { operationsAccessLevel: before, metricsDashboardAccessLevel: before },
+ currentSettings: { monitorAccessLevel: before, metricsDashboardAccessLevel: before },
});
- await findOperationsVisibilityInput().vm.$emit('change', after);
+ await findMonitorVisibilityInput().vm.$emit('change', after);
expect(findMetricsVisibilityInput().props('value')).toBe(after);
},
);
- it('when updating Operations Settings access level from `10` to `20`, Metric Dashboard access is not increased', async () => {
+ it('when updating Monitor access level from `10` to `20`, Metric Dashboard access is not increased', async () => {
wrapper = mountComponent({
currentSettings: {
- operationsAccessLevel: featureAccessLevel.PROJECT_MEMBERS,
+ monitorAccessLevel: featureAccessLevel.PROJECT_MEMBERS,
metricsDashboardAccessLevel: featureAccessLevel.PROJECT_MEMBERS,
},
});
- await findOperationsVisibilityInput().vm.$emit('change', featureAccessLevel.EVERYONE);
+ await findMonitorVisibilityInput().vm.$emit('change', featureAccessLevel.EVERYONE);
expect(findMetricsVisibilityInput().props('value')).toBe(featureAccessLevel.PROJECT_MEMBERS);
});
@@ -818,7 +816,7 @@ describe('Settings Panel', () => {
wrapper = mountComponent({
currentSettings: {
visibilityLevel: VISIBILITY_LEVEL_PUBLIC_INTEGER,
- operationsAccessLevel: featureAccessLevel.EVERYONE,
+ monitorAccessLevel: featureAccessLevel.EVERYONE,
metricsDashboardAccessLevel: featureAccessLevel.EVERYONE,
},
});
@@ -837,84 +835,32 @@ describe('Settings Panel', () => {
});
});
- describe('Operations', () => {
- it('should show the operations toggle', () => {
- wrapper = mountComponent();
-
- expect(findOperationsSettings().exists()).toBe(true);
- });
- });
-
describe('Environments', () => {
- describe('with feature flag', () => {
- it('should show the environments toggle', () => {
- wrapper = mountComponent({
- glFeatures: { splitOperationsVisibilityPermissions: true },
- });
+ it('should show the environments toggle', () => {
+ wrapper = mountComponent({});
- expect(findEnvironmentsSettings().exists()).toBe(true);
- });
- });
- describe('without feature flag', () => {
- it('should not show the environments toggle', () => {
- wrapper = mountComponent({});
-
- expect(findEnvironmentsSettings().exists()).toBe(false);
- });
+ expect(findEnvironmentsSettings().exists()).toBe(true);
});
});
describe('Feature Flags', () => {
- describe('with feature flag', () => {
- it('should show the feature flags toggle', () => {
- wrapper = mountComponent({
- glFeatures: { splitOperationsVisibilityPermissions: true },
- });
-
- expect(findFeatureFlagsSettings().exists()).toBe(true);
- });
- });
- describe('without feature flag', () => {
- it('should not show the feature flags toggle', () => {
- wrapper = mountComponent({});
+ it('should show the feature flags toggle', () => {
+ wrapper = mountComponent({});
- expect(findFeatureFlagsSettings().exists()).toBe(false);
- });
+ expect(findFeatureFlagsSettings().exists()).toBe(true);
});
});
describe('Infrastructure', () => {
- describe('with feature flag', () => {
- it('should show the infrastructure toggle', () => {
- wrapper = mountComponent({
- glFeatures: { splitOperationsVisibilityPermissions: true },
- });
+ it('should show the infrastructure toggle', () => {
+ wrapper = mountComponent({});
- expect(findInfrastructureSettings().exists()).toBe(true);
- });
- });
- describe('without feature flag', () => {
- it('should not show the infrastructure toggle', () => {
- wrapper = mountComponent({});
-
- expect(findInfrastructureSettings().exists()).toBe(false);
- });
+ expect(findInfrastructureSettings().exists()).toBe(true);
});
});
describe('Releases', () => {
- describe('with feature flag', () => {
- it('should show the releases toggle', () => {
- wrapper = mountComponent({
- glFeatures: { splitOperationsVisibilityPermissions: true },
- });
-
- expect(findReleasesSettings().exists()).toBe(true);
- });
- });
- describe('without feature flag', () => {
- it('should not show the releases toggle', () => {
- wrapper = mountComponent({});
+ it('should show the releases toggle', () => {
+ wrapper = mountComponent({});
- expect(findReleasesSettings().exists()).toBe(false);
- });
+ expect(findReleasesSettings().exists()).toBe(true);
});
});
describe('Monitor', () => {
@@ -922,37 +868,20 @@ describe('Settings Panel', () => {
[10, 'Only Project Members'],
[20, 'Everyone With Access'],
];
- describe('with feature flag', () => {
- it('shows Monitor toggle instead of Operations toggle', () => {
- wrapper = mountComponent({
- glFeatures: { splitOperationsVisibilityPermissions: true },
- });
+ it('shows Monitor toggle instead of Operations toggle', () => {
+ wrapper = mountComponent({});
- expect(findMonitorSettings().exists()).toBe(true);
- expect(findOperationsSettings().exists()).toBe(false);
- expect(findMonitorSettings().findComponent(ProjectFeatureSetting).props('options')).toEqual(
- expectedAccessLevel,
- );
- });
- it('when monitorAccessLevel is for project members, it is also for everyone', () => {
- wrapper = mountComponent({
- glFeatures: { splitOperationsVisibilityPermissions: true },
- currentSettings: { monitorAccessLevel: featureAccessLevel.PROJECT_MEMBERS },
- });
-
- expect(findMetricsVisibilityInput().props('value')).toBe(featureAccessLevel.EVERYONE);
- });
+ expect(findMonitorSettings().exists()).toBe(true);
+ expect(findMonitorSettings().findComponent(ProjectFeatureSetting).props('options')).toEqual(
+ expectedAccessLevel,
+ );
});
- describe('without feature flag', () => {
- it('shows Operations toggle instead of Monitor toggle', () => {
- wrapper = mountComponent({});
-
- expect(findMonitorSettings().exists()).toBe(false);
- expect(findOperationsSettings().exists()).toBe(true);
- expect(
- findOperationsSettings().findComponent(ProjectFeatureSetting).props('options'),
- ).toEqual(expectedAccessLevel);
+ it('when monitorAccessLevel is for project members, it is also for everyone', () => {
+ wrapper = mountComponent({
+ currentSettings: { monitorAccessLevel: featureAccessLevel.PROJECT_MEMBERS },
});
+
+ expect(findMetricsVisibilityInput().props('value')).toBe(featureAccessLevel.EVERYONE);
});
});
});
diff --git a/spec/helpers/environment_helper_spec.rb b/spec/helpers/environment_helper_spec.rb
index 1fcbcd8c4f9..c8d67d6dac2 100644
--- a/spec/helpers/environment_helper_spec.rb
+++ b/spec/helpers/environment_helper_spec.rb
@@ -50,6 +50,7 @@ RSpec.describe EnvironmentHelper do
expect(subject).to eq({
name: environment.name,
id: environment.id,
+ project_full_path: project.full_path,
external_url: environment.external_url,
can_update_environment: true,
can_destroy_environment: true,
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index 6d7385446a2..5f4dba6fc79 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -963,7 +963,6 @@ RSpec.describe ProjectsHelper do
lfsEnabled: !!project.lfs_enabled,
emailsDisabled: project.emails_disabled?,
metricsDashboardAccessLevel: project.project_feature.metrics_dashboard_access_level,
- operationsAccessLevel: project.project_feature.operations_access_level,
showDefaultAwardEmojis: project.show_default_award_emojis?,
securityAndComplianceAccessLevel: project.security_and_compliance_access_level,
containerRegistryAccessLevel: project.project_feature.container_registry_access_level,
diff --git a/spec/lib/bitbucket_server/connection_spec.rb b/spec/lib/bitbucket_server/connection_spec.rb
index ae73955e1d1..8341ca10f43 100644
--- a/spec/lib/bitbucket_server/connection_spec.rb
+++ b/spec/lib/bitbucket_server/connection_spec.rb
@@ -56,6 +56,12 @@ RSpec.describe BitbucketServer::Connection do
expect { subject.post(url, payload) }.to raise_error(described_class::ConnectionError)
end
+
+ it 'throws an exception if the URI is invalid' do
+ stub_request(:post, url).with(headers: { 'Accept' => 'application/json' }).to_raise(URI::InvalidURIError)
+
+ expect { subject.post(url, payload) }.to raise_error(described_class::ConnectionError)
+ end
end
describe '#delete' do
diff --git a/spec/lib/gitlab/database/migration_helpers/automatic_lock_writes_on_tables_spec.rb b/spec/lib/gitlab/database/migration_helpers/automatic_lock_writes_on_tables_spec.rb
index e78334a702f..9fd49b312eb 100644
--- a/spec/lib/gitlab/database/migration_helpers/automatic_lock_writes_on_tables_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers/automatic_lock_writes_on_tables_spec.rb
@@ -185,7 +185,8 @@ RSpec.describe Gitlab::Database::MigrationHelpers::AutomaticLockWritesOnTables,
it "raises an error about undefined gitlab_schema" do
expected_error_message = <<~ERROR
No gitlab_schema is defined for the table #{table_name}. Please consider
- adding it to the file config 'lib/gitlab/database/gitlab_schemas.yml'
+ adding it to the database dictionary.
+ More info: https://docs.gitlab.com/ee/development/database/database_dictionary.html
ERROR
expect { run_migration }.to raise_error(expected_error_message)
diff --git a/spec/lib/gitlab/ssh/signature_spec.rb b/spec/lib/gitlab/ssh/signature_spec.rb
index 4868ed68db6..5149972dbf9 100644
--- a/spec/lib/gitlab/ssh/signature_spec.rb
+++ b/spec/lib/gitlab/ssh/signature_spec.rb
@@ -5,20 +5,20 @@ require 'spec_helper'
RSpec.describe Gitlab::Ssh::Signature do
# ssh-keygen -t ed25519
let_it_be(:committer_email) { 'ssh-commit-test@example.com' }
- let_it_be(:public_key_text) { 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJKOfqOH0fDde+Ua/1SObkXB1CEDF5M6UfARMpW3F87u' }
+ let_it_be(:public_key_text) { 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHZ8NHEnCIpC4mnot+BRxv6L+fq+TnN1CgsRrHWLmfwb' }
let_it_be_with_reload(:user) { create(:user, email: committer_email) }
let_it_be_with_reload(:key) { create(:key, usage_type: :signing, key: public_key_text, user: user) }
let(:signed_text) { 'This message was signed by an ssh key' }
let(:signature_text) do
- # ssh-keygen -Y sign -n file -f id_test message.txt
+ # ssh-keygen -Y sign -n git -f id_test message.txt
<<~SIG
-----BEGIN SSH SIGNATURE-----
- U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgko5+o4fR8N175Rr/VI5uRcHUIQ
- MXkzpR8BEylbcXzu4AAAAEZmlsZQAAAAAAAAAGc2hhNTEyAAAAUwAAAAtzc2gtZWQyNTUx
- OQAAAECQa95KgBkgbMwIPNwHRjHu0WYrKvAc5O/FaBXlTDcPWQHi8WRDhbPNN6MqSYLg/S
- hsei6Y8VYPv85StrEHYdoF
+ U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgdnw0cScIikLiaei34FHG/ov5+r
+ 5Oc3UKCxGsdYuZ/BsAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5
+ AAAAQDWOEauf0jXyA9caa5bOgK5QZD6c69pm+EbG3GMw5QBL3N/Gt+r413McCSJFohWWBk
+ Lxemg8NzZ0nB7lTFbaxQc=
-----END SSH SIGNATURE-----
SIG
end
@@ -51,37 +51,37 @@ RSpec.describe Gitlab::Ssh::Signature do
context 'when using an RSA key' do
let(:public_key_text) do
<<~KEY.delete("\n")
- ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCr3ucg9tLf87S2TxgeDaO4Cs5Mzv7wwi5w
- OnSG8hE/Zj7xzf0kXAYns/dHhPilkQMCulMQuGGprGzDJXZ9WrrVDHgBj2+kLB8cc+XYIb29
- HPsoz5a1T776wWrzs5cw3Vbb0ZEMPG27SfJ+HtIqnIAcgBoRxgP/+I9we7tVxrTuog/9jSzU
- H1IscwfwgKdUrvN5cyhqqxWspwZVlf6s4jaVjC9sKlF7u9CBCxqM2G7GZRKH2sEV2Tw0mT4z
- 39UQ5uz9+4hxWChosiQChrT9zSJDGWQm3WGn5ubYPeB/xINEKkFxuEupnSK7l8PQxeLAwlcN
- YHKMkHdO16O6PlpxvcLR1XVy4F12NXCxFjTr8GmFvJTvevf9iuFRmYQpffqm+EMN0shuhPag
- Z1poVK7ZMO49b4HD6csGwDjXEgNAnyi7oPV1WMHVy+xi2j+yaAgiVk50kgTwp9sGkHTiMTM8
- YWjCq+Hb+HXLINmqO5V1QChT7PAFYycmQ0Fe2x39eLLMHy0=
+ ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDkq6ko8LMxf2NwyJKh+77KSDc7/ynPgUJD
+ IopkhqftuHFYe2Y+V3MBJnpzfSRwR2xGfXQUUzLU9AGyfZIO/ZLK2yvfhlO3k//5PbAaZb3y
+ urlnF9T1d2nhtfi8wuzsEn7Boh6qdoWPFIsloAL/X0PXH1HWKmzyNer92HKGrnWFfaaEMo0n
+ T3ureAhRG4IONyUcOK+DyoH+YbxXSlHnLO2oHHlWaP9RrJCHbfAQbfDhaZCI0cNkXXOwUwA4
+ yWGzDibfXZTvaYxpjbz1xoHmCAq8IrobCgkQaEg3PH3vPGnbP0TpViXjMnZyBZyT7tg9WHBV
+ kAsl0CizyUgZHPAPYuqKy5JNlnjVjeqYeIgdN4Tj7hpJ1n0hVpRk4zQNYRmAAj3GNqgPAsd0
+ 3i4rW8cqmhO0fmhP5DgQ7Mt5S9AgcTcCr6niPacK34XrwKiRjxXmCLjr36q8wuRU3QdMt+MK
+ Zxk/qJdAUIltz+nuGiwct0w+sWefYzmiRXu6hljBBrRAvnU=
KEY
end
let(:signature_text) do
<<~SIG
-----BEGIN SSH SIGNATURE-----
- U1NIU0lHAAAAAQAAAZcAAAAHc3NoLXJzYQAAAAMBAAEAAAGBAKve5yD20t/ztLZPGB4No7
- gKzkzO/vDCLnA6dIbyET9mPvHN/SRcBiez90eE+KWRAwK6UxC4YamsbMMldn1autUMeAGP
- b6QsHxxz5dghvb0c+yjPlrVPvvrBavOzlzDdVtvRkQw8bbtJ8n4e0iqcgByAGhHGA//4j3
- B7u1XGtO6iD/2NLNQfUixzB/CAp1Su83lzKGqrFaynBlWV/qziNpWML2wqUXu70IELGozY
- bsZlEofawRXZPDSZPjPf1RDm7P37iHFYKGiyJAKGtP3NIkMZZCbdYafm5tg94H/Eg0QqQX
- G4S6mdIruXw9DF4sDCVw1gcoyQd07Xo7o+WnG9wtHVdXLgXXY1cLEWNOvwaYW8lO969/2K
- 4VGZhCl9+qb4Qw3SyG6E9qBnWmhUrtkw7j1vgcPpywbAONcSA0CfKLug9XVYwdXL7GLaP7
- JoCCJWTnSSBPCn2waQdOIxMzxhaMKr4dv4dcsg2ao7lXVAKFPs8AVjJyZDQV7bHf14sswf
- LQAAAARmaWxlAAAAAAAAAAZzaGE1MTIAAAGUAAAADHJzYS1zaGEyLTUxMgAAAYAXgXpXWw
- A1fYHTUON+e1yrTw8AKB4ymfqpR9Zr1OUmYUKJ9xXvvyNCfKHL6XD14CkMu1Tx8Z3TTPG9
- C6uAXBniKRwwaLVOKffZMshf5sbjcy65KkqBPC7n/cDiCAeoJ8Y05trEDV62+pOpB2lLdv
- pwwg2o0JaoLbdRcKCD0pw1u0O7VDDngTKFZ4ghHrEslxwlFruht1h9hs3rmdITlT0RMNuU
- PHGAIB56u4E4UeoMd3D5rga+4Boj0s6551VgP3vCmcz9ZojPHhTCQdUZU1yHdEBTadYTq6
- UWHhQwDCUDkSNKCRxWo6EyKZQeTakedAt4qkdSpSUCKOJGWKmPOfAm2/sDEmSxffRdxRRg
- QUe8lklyFTZd6U/ZkJ/y7VR46fcSkEqLSLd9jAZT/3HJXbZfULpwsTcvcLcJLkCuzHEaU1
- LRyJBsanLCYHTv7ep5PvIuAngUWrXK2eb7oacVs94mWXfs1PG482Ym4+bZA5u0QliGTVaC
- M2EMhRTf0cqFuA4=
+ U1NIU0lHAAAAAQAAAZcAAAAHc3NoLXJzYQAAAAMBAAEAAAGBAOSrqSjwszF/Y3DIkqH7vs
+ pINzv/Kc+BQkMiimSGp+24cVh7Zj5XcwEmenN9JHBHbEZ9dBRTMtT0AbJ9kg79ksrbK9+G
+ U7eT//k9sBplvfK6uWcX1PV3aeG1+LzC7OwSfsGiHqp2hY8UiyWgAv9fQ9cfUdYqbPI16v
+ 3YcoaudYV9poQyjSdPe6t4CFEbgg43JRw4r4PKgf5hvFdKUecs7agceVZo/1GskIdt8BBt
+ 8OFpkIjRw2Rdc7BTADjJYbMOJt9dlO9pjGmNvPXGgeYICrwiuhsKCRBoSDc8fe88ads/RO
+ lWJeMydnIFnJPu2D1YcFWQCyXQKLPJSBkc8A9i6orLkk2WeNWN6ph4iB03hOPuGknWfSFW
+ lGTjNA1hGYACPcY2qA8Cx3TeLitbxyqaE7R+aE/kOBDsy3lL0CBxNwKvqeI9pwrfhevAqJ
+ GPFeYIuOvfqrzC5FTdB0y34wpnGT+ol0BQiW3P6e4aLBy3TD6xZ59jOaJFe7qGWMEGtEC+
+ dQAAAANnaXQAAAAAAAAABnNoYTUxMgAAAZQAAAAMcnNhLXNoYTItNTEyAAABgEnuYyYOlM
+ CSR+wvmBY7eKHzFor5ByM7N4F7VZAGKK/vbS3C38xDdiJZwsZUscpe5WspJVCWUTkFxXjn
+ GW7vseIfJBVkyqnu2uN8X1j/VDLFESEajcchPhPxtfAMK1/NL99O7rCrYX2pmpkm9tWsFk
+ NX5B93sRyDUnHAOkB+zdqU8P0xdzc8kmBl5OOqu1rSjZIgnQjcauEIRIUN+rFuiRRmIvJp
+ UvMhkKSsRCH93btGW7A6x5e4iPzP+Em0UFYJdOx2lvu9aVAktQzysGwDN+9c4IC+07UHKT
+ UIE5jSbR1QKfavcywNQnCltQ2bTxpnm4A6QHKcdr9Q57dV014FgtmtT/Pw03iyl5MwbEqW
+ 7YEHSkMyAcd1rjEpOCN2pJjjbrOKLePG0R2ffgvVJnTWGFklCxsJ1/7IASHst1wg1/gu1g
+ Kx/TEv+gOKpehAgs2Sz/4kZtFuHO2dbHYC3UrPR5HT8JnQWeCfiT0qwsVQ6xribw0jEYyd
+ ZBNWKkPdNocAbA==
-----END SSH SIGNATURE-----
SIG
end
@@ -98,10 +98,10 @@ RSpec.describe Gitlab::Ssh::Signature do
let(:signature_text) do
<<~SIG
-----BEGIN SSH SIGNATURE-----
- U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgko5+o4fR8N175Rr/VI5uRcHUIQ
- MXkzpR8BEylbcXzu4AAAAEZmlsZQAAAAAAAAAGc2hhNTEyAAAAUwAAAAtzc2gtZWQyNTUx
- OQAAAEC1y2I7o3KqKFlnM+MLkhIo+uRX3YQOYCqycfibyfvmkZTcwqMxgNBInBM9pY3VvS
- sbW2iEdgz34agHbi+1BHIM
+ U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgdnw0cScIikLiaei34FHG/ov5+r
+ 5Oc3UKCxGsdYuZ/BsAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5
+ AAAAQP2liwaQ44PC9oXf5Xzjq20WLdWEK9nyonvDGtduGUXMOL4yP5A6WvKz7kSt7Vba/U
+ MNK0nmnNc7Aokfh/2eRQE=
-----END SSH SIGNATURE-----
SIG
end
@@ -194,6 +194,21 @@ RSpec.describe Gitlab::Ssh::Signature do
it_behaves_like 'unverified signature'
end
+ context 'when signature is for a different namespace' do
+ let(:signature_text) do
+ <<~SIG
+ -----BEGIN SSH SIGNATURE-----
+ U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgdnw0cScIikLiaei34FHG/ov5+r
+ 5Oc3UKCxGsdYuZ/BsAAAAEZmlsZQAAAAAAAAAGc2hhNTEyAAAAUwAAAAtzc2gtZWQyNTUx
+ OQAAAEAd6Psg4D/5IdSVTy35D4t2iNX4udJnX8JrUCjQl0GoPl1vzPjgyvxdzdoQl6bh1w
+ 4rror3RuzUYBGzIioIc1MP
+ -----END SSH SIGNATURE-----
+ SIG
+ end
+
+ it_behaves_like 'unverified signature'
+ end
+
context 'when signature is for a different message' do
let(:signature_text) do
<<~SIG
diff --git a/spec/lib/gitlab/usage_data_counters/code_review_events_spec.rb b/spec/lib/gitlab/usage_data_counters/code_review_events_spec.rb
index e122d9a3026..63a1da490ed 100644
--- a/spec/lib/gitlab/usage_data_counters/code_review_events_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/code_review_events_spec.rb
@@ -13,7 +13,7 @@ RSpec.describe 'Code review events' do
definition.attributes.dig(:options, :events)
end.uniq
- exceptions = %w[i_code_review_mr_diffs i_code_review_mr_with_invalid_approvers i_code_review_mr_single_file_diffs i_code_review_total_suggestions_applied i_code_review_total_suggestions_added i_code_review_create_note_in_ipynb_diff i_code_review_create_note_in_ipynb_diff_mr i_code_review_create_note_in_ipynb_diff_commit]
+ exceptions = %w[i_code_review_create_mr i_code_review_mr_diffs i_code_review_mr_with_invalid_approvers i_code_review_mr_single_file_diffs i_code_review_total_suggestions_applied i_code_review_total_suggestions_added i_code_review_create_note_in_ipynb_diff i_code_review_create_note_in_ipynb_diff_mr i_code_review_create_note_in_ipynb_diff_commit]
code_review_aggregated_events += exceptions
expect(code_review_events - code_review_aggregated_events).to be_empty
diff --git a/spec/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter_spec.rb
index 88399264cd2..9a1ffd8d01d 100644
--- a/spec/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter_spec.rb
@@ -50,11 +50,29 @@ RSpec.describe Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter, :cl
end
describe '.track_create_mr_action' do
- subject { described_class.track_create_mr_action(user: user) }
+ subject { described_class.track_create_mr_action(user: user, merge_request: merge_request) }
+
+ let(:merge_request) { create(:merge_request) }
+ let(:target_project) { merge_request.target_project }
+
+ it_behaves_like 'a tracked merge request unique event' do
+ let(:action) { described_class::MR_USER_CREATE_ACTION }
+ end
it_behaves_like 'a tracked merge request unique event' do
let(:action) { described_class::MR_CREATE_ACTION }
end
+
+ it_behaves_like 'Snowplow event tracking with RedisHLL context' do
+ let(:action) { :create }
+ let(:category) { described_class.name }
+ let(:project) { target_project }
+ let(:namespace) { project.namespace.reload }
+ let(:user) { project.creator }
+ let(:feature_flag_name) { :route_hll_to_snowplow_phase2 }
+ let(:label) { 'redis_hll_counters.code_review.i_code_review_create_mr_monthly' }
+ let(:property) { described_class::MR_CREATE_ACTION }
+ end
end
describe '.track_close_mr_action' do
diff --git a/spec/lib/sidebars/projects/menus/deployments_menu_spec.rb b/spec/lib/sidebars/projects/menus/deployments_menu_spec.rb
index 685ba0c31c7..ce971915174 100644
--- a/spec/lib/sidebars/projects/menus/deployments_menu_spec.rb
+++ b/spec/lib/sidebars/projects/menus/deployments_menu_spec.rb
@@ -47,44 +47,16 @@ RSpec.describe Sidebars::Projects::Menus::DeploymentsMenu do
end
end
- shared_examples 'split_operations_visibility_permissions FF disabled' do
- before do
- stub_feature_flags(split_operations_visibility_permissions: false)
- end
-
- it { is_expected.not_to be_nil }
-
- context 'and the feature is disabled' do
- before do
- project.update_attribute("#{item_id}_access_level", 'disabled')
- end
-
- it { is_expected.not_to be_nil }
- end
-
- context 'and operations is disabled' do
- before do
- project.update_attribute(:operations_access_level, 'disabled')
- end
-
- it do
- is_expected.to be_nil if [:environments, :feature_flags].include?(item_id)
- end
- end
- end
-
describe 'Feature Flags' do
let(:item_id) { :feature_flags }
it_behaves_like 'access rights checks'
- it_behaves_like 'split_operations_visibility_permissions FF disabled'
end
describe 'Environments' do
let(:item_id) { :environments }
it_behaves_like 'access rights checks'
- it_behaves_like 'split_operations_visibility_permissions FF disabled'
end
describe 'Releases' do
diff --git a/spec/lib/sidebars/projects/menus/infrastructure_menu_spec.rb b/spec/lib/sidebars/projects/menus/infrastructure_menu_spec.rb
index 64408ac3b88..116948b7cb0 100644
--- a/spec/lib/sidebars/projects/menus/infrastructure_menu_spec.rb
+++ b/spec/lib/sidebars/projects/menus/infrastructure_menu_spec.rb
@@ -31,43 +31,18 @@ RSpec.describe Sidebars::Projects::Menus::InfrastructureMenu do
let(:enabled) { Featurable::PRIVATE }
let(:disabled) { Featurable::DISABLED }
- where(:operations_access_level, :infrastructure_access_level, :render) do
- ref(:disabled) | ref(:enabled) | true
- ref(:disabled) | ref(:disabled) | false
- ref(:enabled) | ref(:enabled) | true
- ref(:enabled) | ref(:disabled) | false
+ where(:infrastructure_access_level, :render) do
+ ref(:enabled) | true
+ ref(:disabled) | false
end
with_them do
it 'renders based on the infrastructure access level' do
- project.project_feature.update!(operations_access_level: operations_access_level)
project.project_feature.update!(infrastructure_access_level: infrastructure_access_level)
expect(subject.render?).to be render
end
end
-
- context 'when `split_operations_visibility_permissions` feature flag is disabled' do
- before do
- stub_feature_flags(split_operations_visibility_permissions: false)
- end
-
- where(:operations_access_level, :infrastructure_access_level, :render) do
- ref(:disabled) | ref(:enabled) | false
- ref(:disabled) | ref(:disabled) | false
- ref(:enabled) | ref(:enabled) | true
- ref(:enabled) | ref(:disabled) | true
- end
-
- with_them do
- it 'renders based on the operations access level' do
- project.project_feature.update!(operations_access_level: operations_access_level)
- project.project_feature.update!(infrastructure_access_level: infrastructure_access_level)
-
- expect(subject.render?).to be render
- end
- end
- end
end
end
diff --git a/spec/lib/sidebars/projects/menus/monitor_menu_spec.rb b/spec/lib/sidebars/projects/menus/monitor_menu_spec.rb
index f6a8dd7367d..a1e6ae13e68 100644
--- a/spec/lib/sidebars/projects/menus/monitor_menu_spec.rb
+++ b/spec/lib/sidebars/projects/menus/monitor_menu_spec.rb
@@ -16,40 +16,30 @@ RSpec.describe Sidebars::Projects::Menus::MonitorMenu do
let(:enabled) { Featurable::PRIVATE }
let(:disabled) { Featurable::DISABLED }
- where(:flag_enabled, :operations_access_level, :monitor_level, :render) do
- true | ref(:disabled) | ref(:enabled) | true
- true | ref(:disabled) | ref(:disabled) | false
- true | ref(:enabled) | ref(:enabled) | true
- true | ref(:enabled) | ref(:disabled) | false
- false | ref(:disabled) | ref(:enabled) | false
- false | ref(:disabled) | ref(:disabled) | false
- false | ref(:enabled) | ref(:enabled) | true
- false | ref(:enabled) | ref(:disabled) | true
+ where(:monitor_level, :render) do
+ ref(:enabled) | true
+ ref(:disabled) | false
end
with_them do
it 'renders when expected to' do
- stub_feature_flags(split_operations_visibility_permissions: flag_enabled)
- project.project_feature.update!(operations_access_level: operations_access_level)
project.project_feature.update!(monitor_access_level: monitor_level)
expect(subject.render?).to be render
end
end
- context 'when operation feature is enabled' do
- context 'when menu does not have any renderable menu items' do
- it 'returns false' do
- allow(subject).to receive(:has_renderable_items?).and_return(false)
+ context 'when menu does not have any renderable menu items' do
+ it 'returns false' do
+ allow(subject).to receive(:has_renderable_items?).and_return(false)
- expect(subject.render?).to be false
- end
+ expect(subject.render?).to be false
end
+ end
- context 'when menu has menu items' do
- it 'returns true' do
- expect(subject.render?).to be true
- end
+ context 'when menu has menu items' do
+ it 'returns true' do
+ expect(subject.render?).to be true
end
end
end
diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb
index 973ed66b8d8..9b2d10283f1 100644
--- a/spec/policies/project_policy_spec.rb
+++ b/spec/policies/project_policy_spec.rb
@@ -1965,87 +1965,6 @@ RSpec.describe ProjectPolicy do
it_behaves_like 'Self-managed Core resource access tokens'
- describe 'operations feature' do
- using RSpec::Parameterized::TableSyntax
-
- let(:guest_permissions) { [:read_environment, :read_deployment] }
-
- let(:developer_permissions) do
- guest_permissions + [
- :read_feature_flag, :read_sentry_issue, :read_alert_management_alert, :read_terraform_state,
- :metrics_dashboard, :read_pod_logs, :read_prometheus, :create_feature_flag,
- :create_environment, :create_deployment, :update_feature_flag, :update_environment,
- :update_sentry_issue, :update_alert_management_alert, :update_deployment,
- :destroy_feature_flag, :destroy_environment, :admin_feature_flag
- ]
- end
-
- let(:maintainer_permissions) do
- developer_permissions + [
- :read_cluster, :create_cluster, :update_cluster, :admin_environment,
- :admin_cluster, :admin_terraform_state, :admin_deployment
- ]
- end
-
- before do
- stub_feature_flags(split_operations_visibility_permissions: false)
- end
-
- where(:project_visibility, :access_level, :role, :allowed) do
- :public | ProjectFeature::ENABLED | :maintainer | true
- :public | ProjectFeature::ENABLED | :developer | true
- :public | ProjectFeature::ENABLED | :guest | true
- :public | ProjectFeature::ENABLED | :anonymous | true
- :public | ProjectFeature::PRIVATE | :maintainer | true
- :public | ProjectFeature::PRIVATE | :developer | true
- :public | ProjectFeature::PRIVATE | :guest | true
- :public | ProjectFeature::PRIVATE | :anonymous | false
- :public | ProjectFeature::DISABLED | :maintainer | false
- :public | ProjectFeature::DISABLED | :developer | false
- :public | ProjectFeature::DISABLED | :guest | false
- :public | ProjectFeature::DISABLED | :anonymous | false
- :internal | ProjectFeature::ENABLED | :maintainer | true
- :internal | ProjectFeature::ENABLED | :developer | true
- :internal | ProjectFeature::ENABLED | :guest | true
- :internal | ProjectFeature::ENABLED | :anonymous | false
- :internal | ProjectFeature::PRIVATE | :maintainer | true
- :internal | ProjectFeature::PRIVATE | :developer | true
- :internal | ProjectFeature::PRIVATE | :guest | true
- :internal | ProjectFeature::PRIVATE | :anonymous | false
- :internal | ProjectFeature::DISABLED | :maintainer | false
- :internal | ProjectFeature::DISABLED | :developer | false
- :internal | ProjectFeature::DISABLED | :guest | false
- :internal | ProjectFeature::DISABLED | :anonymous | false
- :private | ProjectFeature::ENABLED | :maintainer | true
- :private | ProjectFeature::ENABLED | :developer | true
- :private | ProjectFeature::ENABLED | :guest | false
- :private | ProjectFeature::ENABLED | :anonymous | false
- :private | ProjectFeature::PRIVATE | :maintainer | true
- :private | ProjectFeature::PRIVATE | :developer | true
- :private | ProjectFeature::PRIVATE | :guest | false
- :private | ProjectFeature::PRIVATE | :anonymous | false
- :private | ProjectFeature::DISABLED | :maintainer | false
- :private | ProjectFeature::DISABLED | :developer | false
- :private | ProjectFeature::DISABLED | :guest | false
- :private | ProjectFeature::DISABLED | :anonymous | false
- end
-
- with_them do
- let(:current_user) { user_subject(role) }
- let(:project) { project_subject(project_visibility) }
-
- it 'allows/disallows the abilities based on the operation feature access level' do
- project.project_feature.update!(operations_access_level: access_level)
-
- if allowed
- expect_allowed(*permissions_abilities(role))
- else
- expect_disallowed(*permissions_abilities(role))
- end
- end
- end
- end
-
describe 'environments feature' do
using RSpec::Parameterized::TableSyntax
diff --git a/spec/requests/api/graphql/ci/runner_spec.rb b/spec/requests/api/graphql/ci/runner_spec.rb
index 2a0b8ed0ec6..ca08e780758 100644
--- a/spec/requests/api/graphql/ci/runner_spec.rb
+++ b/spec/requests/api/graphql/ci/runner_spec.rb
@@ -260,7 +260,7 @@ RSpec.describe 'Query.runner(id)', feature_category: :runner_fleet do
context 'when JOB_COUNT_LIMIT is in effect' do
before do
- stub_const('Types::Ci::RunnerType::JOB_COUNT_LIMIT', 1)
+ stub_const('Types::Ci::RunnerType::JOB_COUNT_LIMIT', 0)
end
it 'retrieves correct capped jobCount values' do
diff --git a/spec/services/merge_requests/after_create_service_spec.rb b/spec/services/merge_requests/after_create_service_spec.rb
index 2155b4ffad1..f477b2166d9 100644
--- a/spec/services/merge_requests/after_create_service_spec.rb
+++ b/spec/services/merge_requests/after_create_service_spec.rb
@@ -30,7 +30,7 @@ RSpec.describe MergeRequests::AfterCreateService do
it 'calls the merge request activity counter' do
expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
.to receive(:track_create_mr_action)
- .with(user: merge_request.author)
+ .with(user: merge_request.author, merge_request: merge_request)
expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
.to receive(:track_mr_including_ci_config)
diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb
index 5e65956c03d..3cda6bc2627 100644
--- a/spec/services/projects/update_service_spec.rb
+++ b/spec/services/projects/update_service_spec.rb
@@ -440,25 +440,6 @@ RSpec.describe Projects::UpdateService do
expect(feature.feature_flags_access_level).not_to eq(ProjectFeature::DISABLED)
expect(feature.environments_access_level).not_to eq(ProjectFeature::DISABLED)
end
-
- context 'when split_operations_visibility_permissions feature is disabled' do
- before do
- stub_feature_flags(split_operations_visibility_permissions: false)
- end
-
- it 'syncs the changes to the related fields' do
- result = update_project(project, user, project_feature_attributes: feature_params)
-
- expect(result).to eq({ status: :success })
- feature = project.project_feature
-
- expect(feature.operations_access_level).to eq(ProjectFeature::DISABLED)
- expect(feature.monitor_access_level).to eq(ProjectFeature::DISABLED)
- expect(feature.infrastructure_access_level).to eq(ProjectFeature::DISABLED)
- expect(feature.feature_flags_access_level).to eq(ProjectFeature::DISABLED)
- expect(feature.environments_access_level).to eq(ProjectFeature::DISABLED)
- end
- end
end
context 'when updating a project that contains container images' do
diff --git a/yarn.lock b/yarn.lock
index b9f595bb38a..e963b385caf 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1064,15 +1064,15 @@
resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.6.tgz#d5e0706cf8c6acd8c6032f8d54070af261bbbb2f"
integrity sha512-ws57AidsDvREKrZKYffXddNkyaF14iHNHm8VQnZH6t99E8gczjNN0GpvcGny0imC80yQ0tHz1xVUKk/KFQSUyA==
-"@esbuild/android-arm@0.15.9":
- version "0.15.9"
- resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.15.9.tgz#7e1221604ab88ed5021ead74fa8cca4405e1e431"
- integrity sha512-VZPy/ETF3fBG5PiinIkA0W/tlsvlEgJccyN2DzWZEl0DlVKRbu91PvY2D6Lxgluj4w9QtYHjOWjAT44C+oQ+EQ==
+"@esbuild/android-arm@0.15.18":
+ version "0.15.18"
+ resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.15.18.tgz#266d40b8fdcf87962df8af05b76219bc786b4f80"
+ integrity sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==
-"@esbuild/linux-loong64@0.15.9":
- version "0.15.9"
- resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.15.9.tgz#b658a97babf1f40783354af7039b84c3fdfc3fc3"
- integrity sha512-O+NfmkfRrb3uSsTa4jE3WApidSe3N5++fyOVGP1SmMZi4A3BZELkhUUvj5hwmMuNdlpzAZ8iAPz2vmcR7DCFQA==
+"@esbuild/linux-loong64@0.15.18":
+ version "0.15.18"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.15.18.tgz#128b76ecb9be48b60cf5cfc1c63a4f00691a3239"
+ integrity sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==
"@eslint/eslintrc@^1.3.3":
version "1.3.3"
@@ -5309,75 +5309,75 @@ es-to-primitive@^1.2.1:
is-date-object "^1.0.1"
is-symbol "^1.0.2"
-esbuild-android-64@0.15.9:
- version "0.15.9"
- resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.15.9.tgz#4a7eb320ca8d3a305f14792061fd9614ccebb7c0"
- integrity sha512-HQCX7FJn9T4kxZQkhPjNZC7tBWZqJvhlLHPU2SFzrQB/7nDXjmTIFpFTjt7Bd1uFpeXmuwf5h5fZm+x/hLnhbw==
-
-esbuild-android-arm64@0.15.9:
- version "0.15.9"
- resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.15.9.tgz#c948e5686df20857ad361ec67e070d40d7cab985"
- integrity sha512-E6zbLfqbFVCNEKircSHnPiSTsm3fCRxeIMPfrkS33tFjIAoXtwegQfVZqMGR0FlsvVxp2NEDOUz+WW48COCjSg==
-
-esbuild-darwin-64@0.15.9:
- version "0.15.9"
- resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.15.9.tgz#25f564fa4b39c1cec84dc46bce5634fdbce1d5e4"
- integrity sha512-gI7dClcDN/HHVacZhTmGjl0/TWZcGuKJ0I7/xDGJwRQQn7aafZGtvagOFNmuOq+OBFPhlPv1T6JElOXb0unkSQ==
-
-esbuild-darwin-arm64@0.15.9:
- version "0.15.9"
- resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.9.tgz#60faea3ed95d15239536aa88d06bb82b29278a86"
- integrity sha512-VZIMlcRN29yg/sv7DsDwN+OeufCcoTNaTl3Vnav7dL/nvsApD7uvhVRbgyMzv0zU/PP0xRhhIpTyc7lxEzHGSw==
-
-esbuild-freebsd-64@0.15.9:
- version "0.15.9"
- resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.9.tgz#0339ef1c90a919175e7816788224517896657a0e"
- integrity sha512-uM4z5bTvuAXqPxrI204txhlsPIolQPWRMLenvGuCPZTnnGlCMF2QLs0Plcm26gcskhxewYo9LkkmYSS5Czrb5A==
-
-esbuild-freebsd-arm64@0.15.9:
- version "0.15.9"
- resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.9.tgz#32abfc0be3ae3dd38e5a86a9beadbbcf592f1b57"
- integrity sha512-HHDjT3O5gWzicGdgJ5yokZVN9K9KG05SnERwl9nBYZaCjcCgj/sX8Ps1jvoFSfNCO04JSsHSOWo4qvxFuj8FoA==
-
-esbuild-linux-32@0.15.9:
- version "0.15.9"
- resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.15.9.tgz#93581348a4da7ed2b29bc5539f2605ad7fcee77b"
- integrity sha512-AQIdE8FugGt1DkcekKi5ycI46QZpGJ/wqcMr7w6YUmOmp2ohQ8eO4sKUsOxNOvYL7hGEVwkndSyszR6HpVHLFg==
-
-esbuild-linux-64@0.15.9:
- version "0.15.9"
- resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.15.9.tgz#0d171e7946c95d0d3ed4826026af2c5632d7dcc4"
- integrity sha512-4RXjae7g6Qs7StZyiYyXTZXBlfODhb1aBVAjd+ANuPmMhWthQilWo7rFHwJwL7DQu1Fjej2sODAVwLbcIVsAYQ==
-
-esbuild-linux-arm64@0.15.9:
- version "0.15.9"
- resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.9.tgz#9838795a3720cbe736d3bc20621bd366eac22f24"
- integrity sha512-a+bTtxJmYmk9d+s2W4/R1SYKDDAldOKmWjWP0BnrWtDbvUBNOm++du0ysPju4mZVoEFgS1yLNW+VXnG/4FNwdQ==
-
-esbuild-linux-arm@0.15.9:
- version "0.15.9"
- resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.15.9.tgz#dce96cd817bc7376f6af3967649c4ab1f2f79506"
- integrity sha512-3Zf2GVGUOI7XwChH3qrnTOSqfV1V4CAc/7zLVm4lO6JT6wbJrTgEYCCiNSzziSju+J9Jhf9YGWk/26quWPC6yQ==
-
-esbuild-linux-mips64le@0.15.9:
- version "0.15.9"
- resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.9.tgz#0335a0739e61aa97cb9b4a018e3facfcca9cdcfd"
- integrity sha512-Zn9HSylDp89y+TRREMDoGrc3Z4Hs5u56ozZLQCiZAUx2+HdbbXbWdjmw3FdTJ/i7t5Cew6/Q+6kfO3KCcFGlyw==
-
-esbuild-linux-ppc64le@0.15.9:
- version "0.15.9"
- resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.9.tgz#18482afb95b8a705e2da0a59d7131bff221281f9"
- integrity sha512-OEiOxNAMH9ENFYqRsWUj3CWyN3V8P3ZXyfNAtX5rlCEC/ERXrCEFCJji/1F6POzsXAzxvUJrTSTCy7G6BhA6Fw==
-
-esbuild-linux-riscv64@0.15.9:
- version "0.15.9"
- resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.9.tgz#03b6f9708272c117006b9ce1c9ae8aab91b5a5b6"
- integrity sha512-ukm4KsC3QRausEFjzTsOZ/qqazw0YvJsKmfoZZm9QW27OHjk2XKSQGGvx8gIEswft/Sadp03/VZvAaqv5AIwNA==
-
-esbuild-linux-s390x@0.15.9:
- version "0.15.9"
- resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.9.tgz#65fb645623d575780f155f0ee52935e62f9cca4f"
- integrity sha512-uDOQEH55wQ6ahcIKzQr3VyjGc6Po/xblLGLoUk3fVL1qjlZAibtQr6XRfy5wPJLu/M2o0vQKLq4lyJ2r1tWKcw==
+esbuild-android-64@0.15.18:
+ version "0.15.18"
+ resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.15.18.tgz#20a7ae1416c8eaade917fb2453c1259302c637a5"
+ integrity sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==
+
+esbuild-android-arm64@0.15.18:
+ version "0.15.18"
+ resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.15.18.tgz#9cc0ec60581d6ad267568f29cf4895ffdd9f2f04"
+ integrity sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==
+
+esbuild-darwin-64@0.15.18:
+ version "0.15.18"
+ resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.15.18.tgz#428e1730ea819d500808f220fbc5207aea6d4410"
+ integrity sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==
+
+esbuild-darwin-arm64@0.15.18:
+ version "0.15.18"
+ resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.18.tgz#b6dfc7799115a2917f35970bfbc93ae50256b337"
+ integrity sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==
+
+esbuild-freebsd-64@0.15.18:
+ version "0.15.18"
+ resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.18.tgz#4e190d9c2d1e67164619ae30a438be87d5eedaf2"
+ integrity sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==
+
+esbuild-freebsd-arm64@0.15.18:
+ version "0.15.18"
+ resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.18.tgz#18a4c0344ee23bd5a6d06d18c76e2fd6d3f91635"
+ integrity sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==
+
+esbuild-linux-32@0.15.18:
+ version "0.15.18"
+ resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.15.18.tgz#9a329731ee079b12262b793fb84eea762e82e0ce"
+ integrity sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==
+
+esbuild-linux-64@0.15.18:
+ version "0.15.18"
+ resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.15.18.tgz#532738075397b994467b514e524aeb520c191b6c"
+ integrity sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==
+
+esbuild-linux-arm64@0.15.18:
+ version "0.15.18"
+ resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.18.tgz#5372e7993ac2da8f06b2ba313710d722b7a86e5d"
+ integrity sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==
+
+esbuild-linux-arm@0.15.18:
+ version "0.15.18"
+ resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.15.18.tgz#e734aaf259a2e3d109d4886c9e81ec0f2fd9a9cc"
+ integrity sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==
+
+esbuild-linux-mips64le@0.15.18:
+ version "0.15.18"
+ resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.18.tgz#c0487c14a9371a84eb08fab0e1d7b045a77105eb"
+ integrity sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==
+
+esbuild-linux-ppc64le@0.15.18:
+ version "0.15.18"
+ resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.18.tgz#af048ad94eed0ce32f6d5a873f7abe9115012507"
+ integrity sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==
+
+esbuild-linux-riscv64@0.15.18:
+ version "0.15.18"
+ resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.18.tgz#423ed4e5927bd77f842bd566972178f424d455e6"
+ integrity sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==
+
+esbuild-linux-s390x@0.15.18:
+ version "0.15.18"
+ resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.18.tgz#21d21eaa962a183bfb76312e5a01cc5ae48ce8eb"
+ integrity sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==
esbuild-loader@^2.20.0:
version "2.20.0"
@@ -5391,63 +5391,63 @@ esbuild-loader@^2.20.0:
tapable "^2.2.0"
webpack-sources "^2.2.0"
-esbuild-netbsd-64@0.15.9:
- version "0.15.9"
- resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.9.tgz#7894297bb9e11f3d2f6f31efecd1be4e181f0d54"
- integrity sha512-yWgxaYTQz+TqX80wXRq6xAtb7GSBAp6gqLKfOdANg9qEmAI1Bxn04IrQr0Mzm4AhxvGKoHzjHjMgXbCCSSDxcw==
-
-esbuild-openbsd-64@0.15.9:
- version "0.15.9"
- resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.9.tgz#0f9d4c6b6772ae50d491d68ad4cc028300dda7c0"
- integrity sha512-JmS18acQl4iSAjrEha1MfEmUMN4FcnnrtTaJ7Qg0tDCOcgpPPQRLGsZqhes0vmx8VA6IqRyScqXvaL7+Q0Uf3A==
-
-esbuild-sunos-64@0.15.9:
- version "0.15.9"
- resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.15.9.tgz#c32b7ce574b08f814de810ce7c1e34b843768126"
- integrity sha512-UKynGSWpzkPmXW3D2UMOD9BZPIuRaSqphxSCwScfEE05Be3KAmvjsBhht1fLzKpiFVJb0BYMd4jEbWMyJ/z1hQ==
-
-esbuild-windows-32@0.15.9:
- version "0.15.9"
- resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.15.9.tgz#37a8f7cfccdb2177cd46613a1a1e1fcb419d36df"
- integrity sha512-aqXvu4/W9XyTVqO/hw3rNxKE1TcZiEYHPsXM9LwYmKSX9/hjvfIJzXwQBlPcJ/QOxedfoMVH0YnhhQ9Ffb0RGA==
-
-esbuild-windows-64@0.15.9:
- version "0.15.9"
- resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.15.9.tgz#5fe1e76fc13dd7f520febecaea110b6f1649c7b2"
- integrity sha512-zm7h91WUmlS4idMtjvCrEeNhlH7+TNOmqw5dJPJZrgFaxoFyqYG6CKDpdFCQXdyKpD5yvzaQBOMVTCBVKGZDEg==
-
-esbuild-windows-arm64@0.15.9:
- version "0.15.9"
- resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.9.tgz#98504428f7ba7d2cfc11940be68ee1139173fdce"
- integrity sha512-yQEVIv27oauAtvtuhJVfSNMztJJX47ismRS6Sv2QMVV9RM+6xjbMWuuwM2nxr5A2/gj/mu2z9YlQxiwoFRCfZA==
-
-esbuild@0.15.9, esbuild@^0.15.6:
- version "0.15.9"
- resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.15.9.tgz#33fb18b67b85004b6f7616bec955ca4b3e58935d"
- integrity sha512-OnYr1rkMVxtmMHIAKZLMcEUlJmqcbxBz9QoBU8G9v455na0fuzlT/GLu6l+SRghrk0Mm2fSSciMmzV43Q8e0Gg==
+esbuild-netbsd-64@0.15.18:
+ version "0.15.18"
+ resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.18.tgz#ae75682f60d08560b1fe9482bfe0173e5110b998"
+ integrity sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==
+
+esbuild-openbsd-64@0.15.18:
+ version "0.15.18"
+ resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.18.tgz#79591a90aa3b03e4863f93beec0d2bab2853d0a8"
+ integrity sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==
+
+esbuild-sunos-64@0.15.18:
+ version "0.15.18"
+ resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.15.18.tgz#fd528aa5da5374b7e1e93d36ef9b07c3dfed2971"
+ integrity sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==
+
+esbuild-windows-32@0.15.18:
+ version "0.15.18"
+ resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.15.18.tgz#0e92b66ecdf5435a76813c4bc5ccda0696f4efc3"
+ integrity sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==
+
+esbuild-windows-64@0.15.18:
+ version "0.15.18"
+ resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.15.18.tgz#0fc761d785414284fc408e7914226d33f82420d0"
+ integrity sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==
+
+esbuild-windows-arm64@0.15.18:
+ version "0.15.18"
+ resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.18.tgz#5b5bdc56d341d0922ee94965c89ee120a6a86eb7"
+ integrity sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==
+
+esbuild@0.15.18, esbuild@^0.15.6:
+ version "0.15.18"
+ resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.15.18.tgz#ea894adaf3fbc036d32320a00d4d6e4978a2f36d"
+ integrity sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==
optionalDependencies:
- "@esbuild/android-arm" "0.15.9"
- "@esbuild/linux-loong64" "0.15.9"
- esbuild-android-64 "0.15.9"
- esbuild-android-arm64 "0.15.9"
- esbuild-darwin-64 "0.15.9"
- esbuild-darwin-arm64 "0.15.9"
- esbuild-freebsd-64 "0.15.9"
- esbuild-freebsd-arm64 "0.15.9"
- esbuild-linux-32 "0.15.9"
- esbuild-linux-64 "0.15.9"
- esbuild-linux-arm "0.15.9"
- esbuild-linux-arm64 "0.15.9"
- esbuild-linux-mips64le "0.15.9"
- esbuild-linux-ppc64le "0.15.9"
- esbuild-linux-riscv64 "0.15.9"
- esbuild-linux-s390x "0.15.9"
- esbuild-netbsd-64 "0.15.9"
- esbuild-openbsd-64 "0.15.9"
- esbuild-sunos-64 "0.15.9"
- esbuild-windows-32 "0.15.9"
- esbuild-windows-64 "0.15.9"
- esbuild-windows-arm64 "0.15.9"
+ "@esbuild/android-arm" "0.15.18"
+ "@esbuild/linux-loong64" "0.15.18"
+ esbuild-android-64 "0.15.18"
+ esbuild-android-arm64 "0.15.18"
+ esbuild-darwin-64 "0.15.18"
+ esbuild-darwin-arm64 "0.15.18"
+ esbuild-freebsd-64 "0.15.18"
+ esbuild-freebsd-arm64 "0.15.18"
+ esbuild-linux-32 "0.15.18"
+ esbuild-linux-64 "0.15.18"
+ esbuild-linux-arm "0.15.18"
+ esbuild-linux-arm64 "0.15.18"
+ esbuild-linux-mips64le "0.15.18"
+ esbuild-linux-ppc64le "0.15.18"
+ esbuild-linux-riscv64 "0.15.18"
+ esbuild-linux-s390x "0.15.18"
+ esbuild-netbsd-64 "0.15.18"
+ esbuild-openbsd-64 "0.15.18"
+ esbuild-sunos-64 "0.15.18"
+ esbuild-windows-32 "0.15.18"
+ esbuild-windows-64 "0.15.18"
+ esbuild-windows-arm64 "0.15.18"
escalade@^3.1.1:
version "3.1.1"