diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-12-16 18:09:43 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-12-16 18:09:43 +0000 |
commit | 0122d00cac3c058508a2fcb258f12ecfb7bc9055 (patch) | |
tree | b2a0e138ed4113e80588df57b542dc76d5360e2b /app | |
parent | f1357e5566b2c1f4f5e7b933b72a2d24431905e9 (diff) | |
download | gitlab-ce-0122d00cac3c058508a2fcb258f12ecfb7bc9055.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
35 files changed, 628 insertions, 584 deletions
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' |