diff options
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 Binary files differdeleted file mode 100644 index 5fc38e08172..00000000000 --- a/doc/development/documentation/styleguide/img/tier_badge.png +++ /dev/null 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 Binary files differdeleted file mode 100644 index 7397403f4bf..00000000000 --- a/doc/user/packages/nuget_repository/img/visual_studio_adding_nuget_source.png +++ /dev/null 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 Binary files differdeleted file mode 100644 index 8c14a14e304..00000000000 --- a/doc/user/packages/nuget_repository/img/visual_studio_nuget_source_added.png +++ /dev/null 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 Binary files differdeleted file mode 100644 index 84aa2e571c7..00000000000 --- a/doc/user/project/pages/img/remove_fork_relationship_v13_1.png +++ /dev/null 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" |