diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-08-18 13:50:51 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-08-18 13:50:51 +0300 |
commit | db384e6b19af03b4c3c82a5760d83a3fd79f7982 (patch) | |
tree | 34beaef37df5f47ccbcf5729d7583aae093cffa0 /app/assets/javascripts/ci | |
parent | 54fd7b1bad233e3944434da91d257fa7f63c3996 (diff) |
Add latest changes from gitlab-org/gitlab@16-3-stable-eev16.3.0-rc42
Diffstat (limited to 'app/assets/javascripts/ci')
30 files changed, 662 insertions, 332 deletions
diff --git a/app/assets/javascripts/ci/ci_variable_list/components/ci_group_variables.vue b/app/assets/javascripts/ci/ci_variable_list/components/ci_group_variables.vue index 2045b127a82..842d88e1267 100644 --- a/app/assets/javascripts/ci/ci_variable_list/components/ci_group_variables.vue +++ b/app/assets/javascripts/ci/ci_variable_list/components/ci_group_variables.vue @@ -23,15 +23,6 @@ export default { graphqlId() { return convertToGraphQLId(TYPENAME_GROUP, this.groupId); }, - queriesAvailable() { - if (this.glFeatures.ciGroupEnvScopeGraphql) { - return this.$options.queryData; - } - - return { - ciVariables: this.$options.queryData.ciVariables, - }; - }, }, mutationData: { [ADD_MUTATION_ACTION]: addGroupVariable, @@ -59,6 +50,6 @@ export default { entity="group" :full-path="groupPath" :mutation-data="$options.mutationData" - :query-data="queriesAvailable" + :query-data="$options.queryData" /> </template> diff --git a/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_drawer.vue b/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_drawer.vue new file mode 100644 index 00000000000..0ce11da658c --- /dev/null +++ b/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_drawer.vue @@ -0,0 +1,233 @@ +<script> +import { + GlButton, + GlDrawer, + GlFormCheckbox, + GlFormCombobox, + GlFormGroup, + GlFormSelect, + GlFormTextarea, + GlIcon, + GlLink, + GlSprintf, +} from '@gitlab/ui'; +import { __, s__ } from '~/locale'; +import { DRAWER_Z_INDEX } from '~/lib/utils/constants'; +import { getContentWrapperHeight } from '~/lib/utils/dom_utils'; +import { helpPagePath } from '~/helpers/help_page_helper'; +import { + defaultVariableState, + ENVIRONMENT_SCOPE_LINK_TITLE, + EXPANDED_VARIABLES_NOTE, + FLAG_LINK_TITLE, + VARIABLE_ACTIONS, + variableOptions, +} from '../constants'; +import CiEnvironmentsDropdown from './ci_environments_dropdown.vue'; +import { awsTokenList } from './ci_variable_autocomplete_tokens'; + +const i18n = { + addVariable: s__('CiVariables|Add Variable'), + cancel: __('Cancel'), + environments: __('Environments'), + environmentScopeLinkTitle: ENVIRONMENT_SCOPE_LINK_TITLE, + expandedField: s__('CiVariables|Expand variable reference'), + expandedDescription: EXPANDED_VARIABLES_NOTE, + flags: __('Flags'), + flagsLinkTitle: FLAG_LINK_TITLE, + key: __('Key'), + maskedField: s__('CiVariables|Mask variable'), + maskedDescription: s__( + 'CiVariables|Variable will be masked in job logs. Requires values to meet regular expression requirements.', + ), + protectedField: s__('CiVariables|Protect variable'), + protectedDescription: s__( + 'CiVariables|Export variable to pipelines running on protected branches and tags only.', + ), + type: __('Type'), + value: __('Value'), +}; + +export default { + DRAWER_Z_INDEX, + components: { + CiEnvironmentsDropdown, + GlButton, + GlDrawer, + GlFormCheckbox, + GlFormCombobox, + GlFormGroup, + GlFormSelect, + GlFormTextarea, + GlIcon, + GlLink, + GlSprintf, + }, + inject: ['environmentScopeLink'], + props: { + areEnvironmentsLoading: { + type: Boolean, + required: true, + }, + environments: { + type: Array, + required: false, + default: () => [], + }, + hasEnvScopeQuery: { + type: Boolean, + required: true, + }, + mode: { + type: String, + required: true, + validator(val) { + return VARIABLE_ACTIONS.includes(val); + }, + }, + }, + data() { + return { + key: defaultVariableState.key, + variableType: defaultVariableState.variableType, + }; + }, + computed: { + getDrawerHeaderHeight() { + return getContentWrapperHeight(); + }, + }, + methods: { + close() { + this.$emit('close-form'); + }, + }, + awsTokenList, + flagLink: helpPagePath('ci/variables/index', { + anchor: 'define-a-cicd-variable-in-the-ui', + }), + i18n, + variableOptions, +}; +</script> +<template> + <gl-drawer + open + data-testid="ci-variable-drawer" + :header-height="getDrawerHeaderHeight" + :z-index="$options.DRAWER_Z_INDEX" + @close="close" + > + <template #title> + <h2 class="gl-m-0">{{ $options.i18n.addVariable }}</h2> + </template> + <gl-form-group + :label="$options.i18n.type" + label-for="ci-variable-type" + class="gl-border-none gl-mb-n5" + > + <gl-form-select + id="ci-variable-type" + v-model="variableType" + :options="$options.variableOptions" + /> + </gl-form-group> + <gl-form-group + class="gl-border-none gl-mb-n5" + label-for="ci-variable-env" + data-testid="environment-scope" + > + <template #label> + <div class="gl-display-flex gl-align-items-center"> + <span class="gl-mr-2"> + {{ $options.i18n.environments }} + </span> + <gl-link + class="gl-display-flex" + :title="$options.i18n.environmentScopeLinkTitle" + :href="environmentScopeLink" + target="_blank" + data-testid="environment-scope-link" + > + <gl-icon name="question-o" :size="14" /> + </gl-link> + </div> + </template> + <ci-environments-dropdown + class="gl-mb-5" + :are-environments-loading="areEnvironmentsLoading" + :environments="environments" + :has-env-scope-query="hasEnvScopeQuery" + selected-environment-scope="" + /> + </gl-form-group> + <gl-form-group class="gl-border-none gl-mb-n8"> + <template #label> + <div class="gl-display-flex gl-align-items-center gl-mb-n3"> + <span class="gl-mr-2"> + {{ $options.i18n.flags }} + </span> + <gl-link + class="gl-display-flex" + :title="$options.i18n.flagsLinkTitle" + :href="$options.flagLink" + target="_blank" + > + <gl-icon name="question-o" :size="14" /> + </gl-link> + </div> + </template> + <gl-form-checkbox data-testid="ci-variable-protected-checkbox"> + {{ $options.i18n.protectedField }} + <p class="gl-text-secondary"> + {{ $options.i18n.protectedDescription }} + </p> + </gl-form-checkbox> + <gl-form-checkbox data-testid="ci-variable-masked-checkbox"> + {{ $options.i18n.maskedField }} + <p class="gl-text-secondary">{{ $options.i18n.maskedDescription }}</p> + </gl-form-checkbox> + <gl-form-checkbox data-testid="ci-variable-expanded-checkbox"> + {{ $options.i18n.expandedField }} + <p class="gl-text-secondary"> + <gl-sprintf :message="$options.i18n.expandedDescription" class="gl-text-secondary"> + <template #code="{ content }"> + <code>{{ content }}</code> + </template> + </gl-sprintf> + </p> + </gl-form-checkbox> + </gl-form-group> + <gl-form-combobox + v-model="key" + :token-list="$options.awsTokenList" + :label-text="$options.i18n.key" + class="gl-border-none gl-pb-0! gl-mb-n5" + data-testid="pipeline-form-ci-variable-key" + data-qa-selector="ci_variable_key_field" + /> + <gl-form-group + :label="$options.i18n.value" + label-for="ci-variable-value" + class="gl-border-none gl-mb-n2" + > + <gl-form-textarea + id="ci-variable-value" + class="gl-border-none gl-font-monospace!" + rows="3" + max-rows="10" + data-testid="pipeline-form-ci-variable-value" + data-qa-selector="ci_variable_value_field" + spellcheck="false" + /> + </gl-form-group> + <div class="gl-display-flex gl-justify-content-end"> + <gl-button category="primary" class="gl-mr-3" data-testid="cancel-button" @click="close" + >{{ $options.i18n.cancel }} + </gl-button> + <gl-button category="primary" variant="confirm" data-testid="confirm-button" + >{{ $options.i18n.addVariable }} + </gl-button> + </div> + </gl-drawer> +</template> diff --git a/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_modal.vue b/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_modal.vue index 3af48635f3f..86c0f34215e 100644 --- a/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_modal.vue +++ b/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_modal.vue @@ -25,6 +25,7 @@ import { AWS_TOKEN_CONSTANTS, ADD_CI_VARIABLE_MODAL_ID, AWS_TIP_DISMISSED_COOKIE_NAME, + AWS_TIP_TITLE, AWS_TIP_MESSAGE, CONTAINS_VARIABLE_REFERENCE_MESSAGE, defaultVariableState, @@ -62,10 +63,6 @@ export default { }, mixins: [glFeatureFlagsMixin(), trackingMixin], inject: [ - 'awsLogoSvgPath', - 'awsTipCommandsLink', - 'awsTipDeployLink', - 'awsTipLearnLink', 'containsVariableReferenceLink', 'environmentScopeLink', 'isProtectedByDefault', @@ -241,7 +238,7 @@ export default { this.resetVariableData(); this.resetValidationErrorEvents(); - this.$emit('hideModal'); + this.$emit('close-form'); }, resetVariableData() { this.variable = { ...defaultVariableState }; @@ -295,6 +292,7 @@ export default { }, }, i18n: { + awsTipTitle: AWS_TIP_TITLE, awsTipMessage: AWS_TIP_MESSAGE, containsVariableReferenceMessage: CONTAINS_VARIABLE_REFERENCE_MESSAGE, defaultScope: allEnvironments.text, @@ -305,6 +303,9 @@ export default { flagLink: helpPagePath('ci/variables/index', { anchor: 'define-a-cicd-variable-in-the-ui', }), + oidcLink: helpPagePath('ci/cloud_services/index', { + anchor: 'oidc-authorization-with-your-cloud-provider', + }), modalId: ADD_CI_VARIABLE_MODAL_ID, tokens: awsTokens, tokenList: awsTokenList, @@ -322,6 +323,23 @@ export default { @hidden="resetModalHandler" @shown="onShow" > + <gl-collapse :visible="isTipVisible"> + <gl-alert + :title="$options.i18n.awsTipTitle" + variant="warning" + class="gl-mb-5" + data-testid="aws-guidance-tip" + @dismiss="dismissTip" + > + <gl-sprintf :message="$options.i18n.awsTipMessage"> + <template #link="{ content }"> + <gl-link :href="$options.oidcLink"> + {{ content }} + </gl-link> + </template> + </gl-sprintf> + </gl-alert> + </gl-collapse> <form> <gl-form-combobox v-model="variable.key" @@ -468,45 +486,7 @@ export default { </gl-form-checkbox> </gl-form-group> </form> - <gl-collapse :visible="isTipVisible"> - <gl-alert - :title="__('Deploying to AWS is easy with GitLab')" - variant="tip" - data-testid="aws-guidance-tip" - @dismiss="dismissTip" - > - <div class="gl-display-flex gl-flex-direction-row gl-flex-wrap gl-md-flex-nowrap gl-gap-3"> - <div> - <p> - <gl-sprintf :message="$options.i18n.awsTipMessage"> - <template #deployLink="{ content }"> - <gl-link :href="awsTipDeployLink" target="_blank">{{ content }}</gl-link> - </template> - <template #commandsLink="{ content }"> - <gl-link :href="awsTipCommandsLink" target="_blank">{{ content }}</gl-link> - </template> - </gl-sprintf> - </p> - <p> - <gl-button - :href="awsTipLearnLink" - target="_blank" - category="secondary" - variant="confirm" - class="gl-overflow-wrap-break" - >{{ __('Learn more about deploying to AWS') }}</gl-button - > - </p> - </div> - <img - class="gl-mt-3" - :alt="__('Amazon Web Services Logo')" - :src="awsLogoSvgPath" - height="32" - /> - </div> - </gl-alert> - </gl-collapse> + <gl-alert v-if="containsVariableReference" :title="__('Value might contain a variable reference')" diff --git a/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_settings.vue b/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_settings.vue index b8a95f9081a..f4e1da9b34f 100644 --- a/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_settings.vue +++ b/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_settings.vue @@ -1,13 +1,17 @@ <script> +import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { ADD_VARIABLE_ACTION, EDIT_VARIABLE_ACTION, VARIABLE_ACTIONS } from '../constants'; +import CiVariableDrawer from './ci_variable_drawer.vue'; import CiVariableTable from './ci_variable_table.vue'; import CiVariableModal from './ci_variable_modal.vue'; export default { components: { + CiVariableDrawer, CiVariableTable, CiVariableModal, }, + mixins: [glFeatureFlagsMixin()], props: { areEnvironmentsLoading: { type: Boolean, @@ -62,23 +66,32 @@ export default { }; }, computed: { - showModal() { + showForm() { return VARIABLE_ACTIONS.includes(this.mode); }, + useDrawerForm() { + return this.glFeatures?.ciVariableDrawer; + }, + showDrawer() { + return this.showForm && this.useDrawerForm; + }, + showModal() { + return this.showForm && !this.useDrawerForm; + }, }, methods: { addVariable(variable) { this.$emit('add-variable', variable); }, + closeForm() { + this.mode = null; + }, deleteVariable(variable) { this.$emit('delete-variable', variable); }, updateVariable(variable) { this.$emit('update-variable', variable); }, - hideModal() { - this.mode = null; - }, setSelectedVariable(variable = null) { if (!variable) { this.selectedVariable = {}; @@ -104,6 +117,7 @@ export default { @handle-prev-page="$emit('handle-prev-page')" @handle-next-page="$emit('handle-next-page')" @set-selected-variable="setSelectedVariable" + @delete-variable="deleteVariable" @sort-changed="(val) => $emit('sort-changed', val)" /> <ci-variable-modal @@ -118,10 +132,18 @@ export default { :selected-variable="selectedVariable" @add-variable="addVariable" @delete-variable="deleteVariable" - @hideModal="hideModal" + @close-form="closeForm" @update-variable="updateVariable" @search-environment-scope="$emit('search-environment-scope', $event)" /> + <ci-variable-drawer + v-if="showDrawer" + :are-environments-loading="areEnvironmentsLoading" + :has-env-scope-query="hasEnvScopeQuery" + :mode="mode" + v-on="$listeners" + @close-form="closeForm" + /> </div> </div> </template> diff --git a/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_table.vue b/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_table.vue index ec7a921664f..a14cd1e387a 100644 --- a/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_table.vue +++ b/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_table.vue @@ -3,11 +3,14 @@ import { GlAlert, GlBadge, GlButton, + GlCard, + GlIcon, GlLoadingIcon, GlModalDirective, GlKeysetPagination, GlLink, GlTable, + GlModal, GlTooltipDirective, } from '@gitlab/ui'; import { __, s__, sprintf } from '~/locale'; @@ -45,9 +48,8 @@ export default { }, { key: 'actions', - label: '', - tdClass: 'text-right', - thClass: 'gl-w-5p', + label: __('Actions'), + thClass: 'gl-text-right', }, ], inheritedVarsFields: [ @@ -73,10 +75,13 @@ export default { GlAlert, GlBadge, GlButton, + GlCard, GlKeysetPagination, GlLink, + GlIcon, GlLoadingIcon, GlTable, + GlModal, }, directives: { GlModalDirective, @@ -84,6 +89,14 @@ export default { }, mixins: [glFeatureFlagsMixin()], inject: ['isInheritedGroupVars'], + i18n: { + title: s__('CiVariables|CI/CD Variables'), + addButton: s__('CiVariables|Add variable'), + editButton: __('Edit'), + deleteButton: __('Delete'), + modalDeleteTitle: s__('CiVariables|Delete variable'), + modalDeleteMessage: s__('CiVariables|Do you want to delete the variable %{key}?'), + }, props: { entity: { type: String, @@ -107,6 +120,20 @@ export default { required: true, }, }, + deleteModal: { + actionPrimary: { + text: __('Delete'), + attributes: { + variant: 'danger', + }, + }, + actionSecondary: { + text: __('Cancel'), + attributes: { + variant: 'default', + }, + }, + }, data() { return { areValuesHidden: true, @@ -165,6 +192,9 @@ export default { setSelectedVariable(index = -1) { this.$emit('set-selected-variable', this.variables[index] ?? null); }, + deleteSelectedVariable(index = -1) { + this.$emit('delete-variable', this.variables[index] ?? null); + }, getAttributes(item) { const attributes = []; if (item.variableType === variableTypes.fileType) { @@ -181,188 +211,219 @@ export default { } return attributes; }, + removeVariableMessage(key) { + return sprintf(this.$options.i18n.modalDeleteMessage, { + key, + }); + }, }, maximumVariableLimitReached: MAXIMUM_VARIABLE_LIMIT_REACHED, }; </script> <template> - <div class="ci-variable-table" :data-testid="tableDataTestId"> - <gl-loading-icon v-if="isLoading" /> - <gl-alert - v-if="showAlert" - :dismissible="false" - :title="$options.maximumVariableLimitReached" - variant="info" - > - {{ exceedsVariableLimitText }} - </gl-alert> - <div - v-if="showPagination && !isInheritedGroupVars" - class="ci-variable-actions gl-display-flex gl-justify-content-end gl-my-3" + <div> + <gl-card + class="gl-new-card ci-variable-table" + header-class="gl-new-card-header" + body-class="gl-new-card-body gl-px-0" + :data-testid="tableDataTestId" > - <gl-button v-if="!isTableEmpty" @click="toggleHiddenState">{{ valuesButtonText }}</gl-button> - <gl-button - v-gl-modal-directive="$options.modalId" - class="gl-mx-3" - data-qa-selector="add_ci_variable_button" - variant="confirm" - category="primary" - :aria-label="__('Add')" - :disabled="exceedsVariableLimit" - @click="setSelectedVariable()" - >{{ __('Add variable') }}</gl-button - > - </div> - <gl-table - v-if="!isLoading" - :fields="fields" - :items="variablesWithAttributes" - tbody-tr-class="js-ci-variable-row" - sort-by="key" - sort-direction="asc" - stacked="lg" - fixed - show-empty - sort-icon-left - no-sort-reset - no-local-sorting - @sort-changed="(val) => $emit('sort-changed', val)" - > - <template #table-colgroup="scope"> - <col v-for="field in scope.fields" :key="field.key" :style="field.customStyle" /> - </template> - <template #cell(key)="{ item }"> - <div - class="gl-display-flex gl-align-items-flex-start gl-justify-content-end gl-lg-justify-content-start gl-mr-n3" - > - <span - :id="`ci-variable-key-${item.id}`" - class="gl-display-inline-block gl-max-w-full gl-word-break-word" - >{{ item.key }}</span - > + <template #header> + <div class="gl-new-card-title-wrapper"> + <h5 class="gl-new-card-title">{{ $options.i18n.title }}</h5> + <span class="gl-new-card-count"> + <gl-icon name="code" class="gl-mr-2" /> + {{ variables.length }} + </span> + </div> + <div v-if="!isInheritedGroupVars" class="gl-new-card-actions gl-font-size-0"> <gl-button - v-gl-tooltip + v-if="!isTableEmpty" category="tertiary" - icon="copy-to-clipboard" - class="gl-my-n3 gl-ml-2" - :title="__('Copy key')" - :data-clipboard-text="item.key" - :aria-label="__('Copy to clipboard')" - /> - </div> - </template> - <template v-if="!isInheritedGroupVars" #cell(value)="{ item }"> - <div - class="gl-display-flex gl-align-items-flex-start gl-justify-content-end gl-lg-justify-content-start gl-mr-n3" - > - <span v-if="areValuesHidden" data-testid="hiddenValue">*****</span> - <span - v-else - :id="`ci-variable-value-${item.id}`" - class="gl-display-inline-block gl-max-w-full gl-text-truncate" - data-testid="revealedValue" - >{{ item.value }}</span + size="small" + class="gl-mr-3" + @click="toggleHiddenState" + >{{ valuesButtonText }}</gl-button > <gl-button - v-gl-tooltip - category="tertiary" - icon="copy-to-clipboard" - class="gl-my-n3 gl-ml-2" - :title="__('Copy value')" - :data-clipboard-text="item.value" - :aria-label="__('Copy to clipboard')" - /> + v-gl-modal-directive="$options.modalId" + size="small" + :disabled="exceedsVariableLimit" + data-qa-selector="add_ci_variable_button" + data-testid="add-ci-variable-button" + @click="setSelectedVariable()" + >{{ $options.i18n.addButton }}</gl-button + > </div> </template> - <template #cell(attributes)="{ item }"> - <span data-testid="ci-variable-table-row-attributes"> - <gl-badge - v-for="attribute in item.attributes" - :key="`${item.key}-${attribute}`" - class="gl-mr-2" - variant="info" - size="sm" + + <gl-loading-icon v-if="isLoading" class="gl-p-4" /> + <gl-alert + v-if="showAlert" + :dismissible="false" + :title="$options.maximumVariableLimitReached" + variant="info" + > + {{ exceedsVariableLimitText }} + </gl-alert> + <gl-table + v-if="!isLoading" + :fields="fields" + :items="variablesWithAttributes" + tbody-tr-class="js-ci-variable-row" + sort-by="key" + sort-direction="asc" + stacked="md" + fixed + show-empty + sort-icon-left + no-sort-reset + no-local-sorting + @sort-changed="(val) => $emit('sort-changed', val)" + > + <template #table-colgroup="scope"> + <col v-for="field in scope.fields" :key="field.key" :style="field.customStyle" /> + </template> + <template #cell(key)="{ item }"> + <div + class="gl-display-flex gl-align-items-flex-start gl-justify-content-end gl-lg-justify-content-start gl-mr-n3" > - {{ attribute }} - </gl-badge> - </span> - </template> - <template #cell(environmentScope)="{ item }"> - <div - class="gl-display-flex gl-align-items-flex-start gl-justify-content-end gl-lg-justify-content-start gl-mr-n3" - > - <span - :id="`ci-variable-env-${item.id}`" - class="gl-display-inline-block gl-max-w-full gl-word-break-word" - >{{ convertEnvironmentScopeValue(item.environmentScope) }}</span + <span + :id="`ci-variable-key-${item.id}`" + class="gl-display-inline-block gl-max-w-full gl-word-break-word" + >{{ item.key }}</span + > + <gl-button + v-gl-tooltip + category="tertiary" + icon="copy-to-clipboard" + class="gl-my-n3 gl-ml-2" + :title="__('Copy key')" + :data-clipboard-text="item.key" + :aria-label="__('Copy to clipboard')" + /> + </div> + </template> + <template v-if="!isInheritedGroupVars" #cell(value)="{ item }"> + <div + class="gl-display-flex gl-align-items-flex-start gl-justify-content-end gl-lg-justify-content-start gl-mr-n3" > - <gl-button - v-gl-tooltip - category="tertiary" - icon="copy-to-clipboard" - class="gl-my-n3 gl-ml-2" - :title="__('Copy environment')" - :data-clipboard-text="convertEnvironmentScopeValue(item.environmentScope)" - :aria-label="__('Copy to clipboard')" - /> - </div> - </template> - <template v-if="isInheritedGroupVars" #cell(group)="{ item }"> - <div - class="gl-display-flex gl-align-items-flex-start gl-justify-content-end gl-lg-justify-content-start gl-mr-n3" - > - <gl-link - :id="`ci-variable-group-${item.id}`" - data-testid="ci-variable-table-row-cicd-path" - class="gl-display-inline-block gl-max-w-full gl-word-break-word" - :href="item.groupCiCdSettingsPath" + <span v-if="areValuesHidden" data-testid="hiddenValue">*****</span> + <span + v-else + :id="`ci-variable-value-${item.id}`" + class="gl-display-inline-block gl-max-w-full gl-text-truncate" + data-testid="revealedValue" + >{{ item.value }}</span + > + <gl-button + v-gl-tooltip + category="tertiary" + icon="copy-to-clipboard" + class="gl-my-n3 gl-ml-2" + :title="__('Copy value')" + :data-clipboard-text="item.value" + :aria-label="__('Copy to clipboard')" + /> + </div> + </template> + <template #cell(attributes)="{ item }"> + <span data-testid="ci-variable-table-row-attributes"> + <gl-badge + v-for="attribute in item.attributes" + :key="`${item.key}-${attribute}`" + class="gl-mr-2" + variant="info" + size="sm" + > + {{ attribute }} + </gl-badge> + </span> + </template> + <template #cell(environmentScope)="{ item }"> + <div + class="gl-display-flex gl-align-items-flex-start gl-justify-content-end gl-lg-justify-content-start gl-mr-n3" > - {{ item.groupName }} - </gl-link> - </div> - </template> - <template v-if="!isInheritedGroupVars" #cell(actions)="{ item }"> - <gl-button - v-gl-modal-directive="$options.modalId" - icon="pencil" - :aria-label="__('Edit')" - data-qa-selector="edit_ci_variable_button" - @click="setSelectedVariable(item.index)" - /> - </template> - <template #empty> - <p class="gl-text-center gl-py-6 gl-text-black-normal gl-mb-0"> - {{ __('There are no variables yet.') }} - </p> - </template> - </gl-table> - <gl-alert - v-if="showAlert" - :dismissible="false" - :title="$options.maximumVariableLimitReached" - variant="info" - > - {{ exceedsVariableLimitText }} - </gl-alert> + <span + :id="`ci-variable-env-${item.id}`" + class="gl-display-inline-block gl-max-w-full gl-word-break-word" + >{{ convertEnvironmentScopeValue(item.environmentScope) }}</span + > + <gl-button + v-gl-tooltip + category="tertiary" + icon="copy-to-clipboard" + class="gl-my-n3 gl-ml-2" + :title="__('Copy environment')" + :data-clipboard-text="convertEnvironmentScopeValue(item.environmentScope)" + :aria-label="__('Copy to clipboard')" + /> + </div> + </template> + <template v-if="isInheritedGroupVars" #cell(group)="{ item }"> + <div + class="gl-display-flex gl-align-items-flex-start gl-justify-content-end gl-lg-justify-content-start gl-mr-n3" + > + <gl-link + :id="`ci-variable-group-${item.id}`" + data-testid="ci-variable-table-row-cicd-path" + class="gl-display-inline-block gl-max-w-full gl-word-break-word" + :href="item.groupCiCdSettingsPath" + > + {{ item.groupName }} + </gl-link> + </div> + </template> + <template v-if="!isInheritedGroupVars" #cell(actions)="{ item }"> + <div class="gl-display-flex gl-justify-content-end gl-mt-n2 gl-mb-n2"> + <gl-button + v-gl-modal-directive="$options.modalId" + icon="pencil" + size="small" + class="gl-mr-3" + :aria-label="$options.i18n.editButton" + data-qa-selector="edit_ci_variable_button" + @click="setSelectedVariable(item.index)" + /> + <gl-button + v-gl-modal-directive="`delete-variable-${item.index}`" + variant="danger" + category="secondary" + icon="remove" + size="small" + :aria-label="$options.i18n.deleteButton" + data-qa-selector="delete_ci_variable_button" + /> + <gl-modal + ref="modal" + :modal-id="`delete-variable-${item.index}`" + :title="$options.i18n.modalDeleteTitle" + :action-primary="$options.deleteModal.actionPrimary" + :action-secondary="$options.deleteModal.actionSecondary" + @primary="deleteSelectedVariable(item.index)" + > + {{ removeVariableMessage(item.key) }} + </gl-modal> + </div> + </template> + <template #empty> + <p class="gl-text-secondary gl-text-center gl-py-1 gl-mb-0"> + {{ __('There are no variables yet.') }} + </p> + </template> + </gl-table> + <gl-alert + v-if="showAlert" + :dismissible="false" + :title="$options.maximumVariableLimitReached" + variant="info" + > + {{ exceedsVariableLimitText }} + </gl-alert> + </gl-card> <div v-if="!isInheritedGroupVars"> - <div v-if="!showPagination" class="ci-variable-actions gl-display-flex gl-mt-5"> - <gl-button - v-gl-modal-directive="$options.modalId" - class="gl-mr-3" - data-qa-selector="add_ci_variable_button" - variant="confirm" - category="primary" - :aria-label="__('Add')" - :disabled="exceedsVariableLimit" - @click="setSelectedVariable()" - >{{ __('Add variable') }}</gl-button - > - <gl-button v-if="!isTableEmpty" @click="toggleHiddenState">{{ - valuesButtonText - }}</gl-button> - </div> - <div v-else class="gl-display-flex gl-justify-content-center gl-mt-6"> + <div v-if="showPagination" class="gl-display-flex gl-justify-content-center gl-mt-5"> <gl-keyset-pagination v-bind="pageInfo" :prev-text="__('Previous')" diff --git a/app/assets/javascripts/ci/ci_variable_list/constants.js b/app/assets/javascripts/ci/ci_variable_list/constants.js index d702dd073ec..825b39e0cf9 100644 --- a/app/assets/javascripts/ci/ci_variable_list/constants.js +++ b/app/assets/javascripts/ci/ci_variable_list/constants.js @@ -7,14 +7,6 @@ export const SORT_DIRECTIONS = { ASC: 'KEY_ASC', DESC: 'KEY_DESC', }; - -// This const will be deprecated once we remove VueX from the section -export const displayText = { - variableText: __('Variable'), - fileText: __('File'), - allEnvironmentsText: __('All (default)'), -}; - export const variableTypes = { envType: 'ENV_VAR', fileType: 'FILE', @@ -26,8 +18,8 @@ export const allEnvironments = { }; export const variableOptions = [ - { value: variableTypes.envType, text: variableTypes.envType }, - { value: variableTypes.fileType, text: variableTypes.fileType }, + { value: variableTypes.envType, text: __('Variable (default)') }, + { value: variableTypes.fileType, text: __('File') }, ]; export const defaultVariableState = { @@ -48,8 +40,9 @@ export const instanceString = 'Instance'; export const projectString = 'Project'; export const AWS_TIP_DISMISSED_COOKIE_NAME = 'ci_variable_list_constants_aws_tip_dismissed'; -export const AWS_TIP_MESSAGE = __( - '%{deployLinkStart}Use a template to deploy to ECS%{deployLinkEnd}, or use a docker image to %{commandsLinkStart}run AWS commands in GitLab CI/CD%{commandsLinkEnd}.', +export const AWS_TIP_TITLE = s__('CiVariable|Use OIDC to securely connect to cloud services'); +export const AWS_TIP_MESSAGE = s__( + 'CiVariable|GitLab CI/CD supports OpenID Connect (OIDC) to give your build and deployment jobs access to cloud credentials and services. %{linkStart}How do I configure OIDC for my cloud provider?%{linkEnd}', ); export const EVENT_LABEL = 'ci_variable_modal'; diff --git a/app/assets/javascripts/ci/ci_variable_list/index.js b/app/assets/javascripts/ci/ci_variable_list/index.js index e47b41ceae5..9342f57f2d8 100644 --- a/app/assets/javascripts/ci/ci_variable_list/index.js +++ b/app/assets/javascripts/ci/ci_variable_list/index.js @@ -10,10 +10,6 @@ import { generateCacheConfig, resolvers } from './graphql/settings'; const mountCiVariableListApp = (containerEl) => { const { - awsLogoSvgPath, - awsTipCommandsLink, - awsTipDeployLink, - awsTipLearnLink, containsVariableReferenceLink, endpoint, environmentScopeLink, @@ -57,10 +53,6 @@ const mountCiVariableListApp = (containerEl) => { el: containerEl, apolloProvider, provide: { - awsLogoSvgPath, - awsTipCommandsLink, - awsTipDeployLink, - awsTipLearnLink, containsVariableReferenceLink, endpoint, environmentScopeLink, diff --git a/app/assets/javascripts/ci/pipeline_editor/components/editor/ci_editor_header.vue b/app/assets/javascripts/ci/pipeline_editor/components/editor/ci_editor_header.vue index 6ba8884f9a6..bc0cad75c60 100644 --- a/app/assets/javascripts/ci/pipeline_editor/components/editor/ci_editor_header.vue +++ b/app/assets/javascripts/ci/pipeline_editor/components/editor/ci_editor_header.vue @@ -107,7 +107,6 @@ export default { v-if="glFeatures.ciJobAssistantDrawer" icon="bulb" size="small" - data-testid="job-assistant-drawer-toggle" data-qa-selector="job_assistant_drawer_toggle" @click="toggleJobAssistantDrawer" > diff --git a/app/assets/javascripts/ci/pipeline_editor/components/header/pipeline_editor_mini_graph.vue b/app/assets/javascripts/ci/pipeline_editor/components/header/pipeline_editor_mini_graph.vue index 656b1a6c347..f1c9770714a 100644 --- a/app/assets/javascripts/ci/pipeline_editor/components/header/pipeline_editor_mini_graph.vue +++ b/app/assets/javascripts/ci/pipeline_editor/components/header/pipeline_editor_mini_graph.vue @@ -1,7 +1,7 @@ <script> import { __ } from '~/locale'; import { keepLatestDownstreamPipelines } from '~/pipelines/components/parsing_utils'; -import PipelineMiniGraph from '~/pipelines/components/pipeline_mini_graph/pipeline_mini_graph.vue'; +import LegacyPipelineMiniGraph from '~/pipelines/components/pipeline_mini_graph/legacy_pipeline_mini_graph.vue'; import getLinkedPipelinesQuery from '~/pipelines/graphql/queries/get_linked_pipelines.query.graphql'; import { PIPELINE_FAILURE } from '../../constants'; @@ -10,7 +10,7 @@ export default { linkedPipelinesFetchError: __('Unable to fetch upstream and downstream pipelines.'), }, components: { - PipelineMiniGraph, + LegacyPipelineMiniGraph, }, inject: ['projectFullPath'], props: { @@ -84,7 +84,7 @@ export default { </script> <template> - <pipeline-mini-graph + <legacy-pipeline-mini-graph v-if="hasPipelineStages" :downstream-pipelines="downstreamPipelines" :pipeline-path="pipelinePath" diff --git a/app/assets/javascripts/ci/pipeline_editor/components/header/pipeline_status.vue b/app/assets/javascripts/ci/pipeline_editor/components/header/pipeline_status.vue index bb79a4d74da..3bce50224d9 100644 --- a/app/assets/javascripts/ci/pipeline_editor/components/header/pipeline_status.vue +++ b/app/assets/javascripts/ci/pipeline_editor/components/header/pipeline_status.vue @@ -11,7 +11,7 @@ import { } from '~/pipelines/components/graph/utils'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import CiIcon from '~/vue_shared/components/ci_icon.vue'; -import GraphqlPipelineMiniGraph from '~/pipelines/components/pipeline_mini_graph/graphql_pipeline_mini_graph.vue'; +import PipelineMiniGraph from '~/pipelines/components/pipeline_mini_graph/pipeline_mini_graph.vue'; import PipelineEditorMiniGraph from './pipeline_editor_mini_graph.vue'; const POLL_INTERVAL = 10000; @@ -34,8 +34,8 @@ export default { GlLink, GlLoadingIcon, GlSprintf, - GraphqlPipelineMiniGraph, PipelineEditorMiniGraph, + PipelineMiniGraph, }, directives: { GlTooltip: GlTooltipDirective, @@ -179,7 +179,7 @@ export default { </span> </div> <div class="gl-display-flex gl-flex-wrap-wrap"> - <graphql-pipeline-mini-graph + <pipeline-mini-graph v-if="isUsingPipelineMiniGraphQueries" :full-path="projectFullPath" :iid="pipeline.iid" diff --git a/app/assets/javascripts/ci/pipeline_editor/components/ui/pipeline_editor_empty_state.vue b/app/assets/javascripts/ci/pipeline_editor/components/ui/pipeline_editor_empty_state.vue index d7b8e7151d9..25e4e99bf54 100644 --- a/app/assets/javascripts/ci/pipeline_editor/components/ui/pipeline_editor_empty_state.vue +++ b/app/assets/javascripts/ci/pipeline_editor/components/ui/pipeline_editor_empty_state.vue @@ -61,6 +61,7 @@ export default { <gl-button variant="confirm" class="gl-mt-3" + data-testid="create_new_ci_button" data-qa-selector="create_new_ci_button" @click="createEmptyConfigFile" > diff --git a/app/assets/javascripts/ci/pipeline_editor/components/validate/ci_validate.vue b/app/assets/javascripts/ci/pipeline_editor/components/validate/ci_validate.vue index ba33888e2fb..7583fa7a3b5 100644 --- a/app/assets/javascripts/ci/pipeline_editor/components/validate/ci_validate.vue +++ b/app/assets/javascripts/ci/pipeline_editor/components/validate/ci_validate.vue @@ -201,7 +201,6 @@ export default { :title="$options.i18n.pipelineSourceTooltip" :toggle-text="$options.i18n.pipelineSourceDefault" disabled - data-testid="pipeline-source" /> <validate-pipeline-popover /> <gl-icon diff --git a/app/assets/javascripts/ci/pipeline_editor/pipeline_editor_home.vue b/app/assets/javascripts/ci/pipeline_editor/pipeline_editor_home.vue index 0495546529a..41e5199e204 100644 --- a/app/assets/javascripts/ci/pipeline_editor/pipeline_editor_home.vue +++ b/app/assets/javascripts/ci/pipeline_editor/pipeline_editor_home.vue @@ -3,9 +3,9 @@ import { GlModal } from '@gitlab/ui'; import { __ } from '~/locale'; import { DRAWER_Z_INDEX } from '~/lib/utils/constants'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; +import JobAssistantDrawer from 'jh_else_ce/ci/pipeline_editor/components/job_assistant_drawer/job_assistant_drawer.vue'; import CommitSection from './components/commit/commit_section.vue'; import PipelineEditorDrawer from './components/drawer/pipeline_editor_drawer.vue'; -import JobAssistantDrawer from './components/job_assistant_drawer/job_assistant_drawer.vue'; import PipelineEditorFileNav from './components/file_nav/pipeline_editor_file_nav.vue'; import PipelineEditorFileTree from './components/file_tree/container.vue'; import PipelineEditorHeader from './components/header/pipeline_editor_header.vue'; diff --git a/app/assets/javascripts/ci/pipeline_new/components/pipeline_new_form.vue b/app/assets/javascripts/ci/pipeline_new/components/pipeline_new_form.vue index 6fd5c8130ad..cc7d9bd2340 100644 --- a/app/assets/javascripts/ci/pipeline_new/components/pipeline_new_form.vue +++ b/app/assets/javascripts/ci/pipeline_new/components/pipeline_new_form.vue @@ -476,7 +476,6 @@ export default { <gl-dropdown-item v-for="option in configVariablesWithDescription.options[variable.key]" :key="option" - data-testid="pipeline-form-ci-variable-value-dropdown-items" data-qa-selector="ci_variable_value_dropdown_item" @click="setVariableAttribute(variable.key, 'value', option)" > diff --git a/app/assets/javascripts/ci/pipeline_schedules/components/pipeline_schedules.vue b/app/assets/javascripts/ci/pipeline_schedules/components/pipeline_schedules.vue index 0700d9e5439..c993b65f6c0 100644 --- a/app/assets/javascripts/ci/pipeline_schedules/components/pipeline_schedules.vue +++ b/app/assets/javascripts/ci/pipeline_schedules/components/pipeline_schedules.vue @@ -94,6 +94,7 @@ export default { return { schedules: { list: [], + currentUser: {}, }, scope, hasError: false, @@ -135,6 +136,14 @@ export default { }, ]; }, + onAllTab() { + // scope is undefined on first load, scope is only defined + // after tab switching + return this.scope === ALL_SCOPE || !this.scope; + }, + showEmptyState() { + return !this.isLoading && this.schedulesCount === 0 && this.onAllTab; + }, }, watch: { // this watcher ensures that the count on the all tab @@ -258,8 +267,10 @@ export default { </gl-sprintf> </gl-alert> + <pipeline-schedule-empty-state v-if="showEmptyState" /> + <gl-tabs - v-if="isLoading || schedulesCount > 0" + v-else sync-active-tab-with-query-params query-param-name="scope" nav-class="gl-flex-grow-1 gl-align-items-center gl-mt-2" @@ -284,6 +295,7 @@ export default { </template> <gl-loading-icon v-if="isLoading" size="lg" /> + <pipeline-schedules-table v-else :schedules="schedules.list" @@ -306,8 +318,6 @@ export default { </template> </gl-tabs> - <pipeline-schedule-empty-state v-else-if="!isLoading && schedulesCount === 0" /> - <take-ownership-modal :visible="showTakeOwnershipModal" @takeOwnership="takeOwnership" diff --git a/app/assets/javascripts/ci/pipeline_schedules/components/pipeline_schedules_form.vue b/app/assets/javascripts/ci/pipeline_schedules/components/pipeline_schedules_form.vue index d84a9a4a4b5..396ff9808f2 100644 --- a/app/assets/javascripts/ci/pipeline_schedules/components/pipeline_schedules_form.vue +++ b/app/assets/javascripts/ci/pipeline_schedules/components/pipeline_schedules_form.vue @@ -112,6 +112,7 @@ export default { cronTimezone: '', variables: [], schedule: {}, + showVarValues: false, }; }, i18n: { @@ -140,6 +141,8 @@ export default { scheduleFetchError: s__( 'PipelineSchedules|An error occurred while trying to fetch the pipeline schedule.', ), + revealText: __('Reveal values'), + hideText: __('Hide values'), }, typeOptions: { [VARIABLE_TYPE]: __('Variable'), @@ -167,11 +170,24 @@ export default { getEnabledRefTypes() { return [REF_TYPE_BRANCHES, REF_TYPE_TAGS]; }, + filledVariables() { + return this.variables.filter((variable) => variable.key !== '' && !variable.empty); + }, preparedVariablesUpdate() { - return this.variables.filter((variable) => variable.key !== ''); + return this.filledVariables.map((variable) => { + return { + id: variable.id, + key: variable.key, + value: variable.value, + variableType: variable.variableType, + destroy: variable.destroy, + }; + }); }, preparedVariablesCreate() { - return this.preparedVariablesUpdate.map((variable) => { + const vars = this.variables.filter((variable) => variable.key !== ''); + + return vars.map((variable) => { return { key: variable.key, value: variable.value, @@ -187,6 +203,15 @@ export default { ? this.$options.i18n.editScheduleBtnText : this.$options.i18n.createScheduleBtnText; }, + varSecurityBtnText() { + return this.showVarValues ? this.$options.i18n.hideText : this.$options.i18n.revealText; + }, + hasExistingScheduleVariables() { + return this.schedule?.variables?.nodes.length > 0; + }, + showVarSecurityBtn() { + return this.editing && this.hasExistingScheduleVariables; + }, }, created() { this.addEmptyVariable(); @@ -204,6 +229,7 @@ export default { key: '', value: '', destroy: false, + empty: true, }); }, setVariableAttribute(key, attribute, value) { @@ -289,6 +315,14 @@ export default { setTimezone(timezone) { this.cronTimezone = timezone.identifier || ''; }, + displayHiddenChars(variable) { + return ( + this.editing && this.hasExistingScheduleVariables && !this.showVarValues && !variable.empty + ); + }, + resetVariable(index) { + this.variables[index].empty = false; + }, }, }; </script> @@ -342,7 +376,7 @@ export default { /> </gl-form-group> <!--Variable List--> - <gl-form-group :label="$options.i18n.variables"> + <gl-form-group class="gl-mb-2" :label="$options.i18n.variables"> <div v-for="(variable, index) in variables" :key="`var-${index}`" @@ -372,10 +406,21 @@ export default { :class="$options.formElementClasses" data-testid="pipeline-form-ci-variable-key" data-qa-selector="ci_variable_key_field" - @change="addEmptyVariable()" + @change="addEmptyVariable(variable)" /> <gl-form-textarea + v-if="displayHiddenChars(variable)" + value="*****************" + disabled + class="gl-mb-3 gl-h-7!" + :style="$options.textAreaStyle" + :no-resize="false" + data-testid="pipeline-form-ci-variable-hidden-value" + /> + + <gl-form-textarea + v-else v-model="variable.value" :placeholder="s__('CiVariables|Input variable value')" class="gl-mb-3 gl-h-7!" @@ -383,6 +428,7 @@ export default { :no-resize="false" data-testid="pipeline-form-ci-variable-value" data-qa-selector="ci_variable_value_field" + @change="resetVariable(index)" /> <template v-if="variables.length > 1"> @@ -406,6 +452,18 @@ export default { </div> </div> </gl-form-group> + + <gl-button + v-if="showVarSecurityBtn" + class="gl-mb-5" + category="secondary" + variant="confirm" + data-testid="variable-security-btn" + @click="showVarValues = !showVarValues" + > + {{ varSecurityBtnText }} + </gl-button> + <!--Activated--> <gl-form-checkbox id="schedule-active" v-model="activated" class="gl-mb-3"> {{ $options.i18n.activated }} diff --git a/app/assets/javascripts/ci/pipeline_schedules/components/table/pipeline_schedules_table.vue b/app/assets/javascripts/ci/pipeline_schedules/components/table/pipeline_schedules_table.vue index b97914f8c26..368cfb9c10d 100644 --- a/app/assets/javascripts/ci/pipeline_schedules/components/table/pipeline_schedules_table.vue +++ b/app/assets/javascripts/ci/pipeline_schedules/components/table/pipeline_schedules_table.vue @@ -1,5 +1,5 @@ <script> -import { GlTableLite } from '@gitlab/ui'; +import { GlTable } from '@gitlab/ui'; import { s__ } from '~/locale'; import PipelineScheduleActions from './cells/pipeline_schedule_actions.vue'; import PipelineScheduleLastPipeline from './cells/pipeline_schedule_last_pipeline.vue'; @@ -8,6 +8,9 @@ import PipelineScheduleOwner from './cells/pipeline_schedule_owner.vue'; import PipelineScheduleTarget from './cells/pipeline_schedule_target.vue'; export default { + i18n: { + emptyText: s__('PipelineSchedules|No pipeline schedules'), + }, fields: [ { key: 'description', @@ -47,7 +50,7 @@ export default { }, ], components: { - GlTableLite, + GlTable, PipelineScheduleActions, PipelineScheduleLastPipeline, PipelineScheduleNextRun, @@ -68,10 +71,12 @@ export default { </script> <template> - <gl-table-lite + <gl-table :fields="$options.fields" :items="schedules" :tbody-tr-attr="{ 'data-testid': 'pipeline-schedule-table-row' }" + :empty-text="$options.i18n.emptyText" + show-empty stacked="md" > <template #table-colgroup="{ fields }"> @@ -109,5 +114,5 @@ export default { @playPipelineSchedule="$emit('playPipelineSchedule', $event)" /> </template> - </gl-table-lite> + </gl-table> </template> diff --git a/app/assets/javascripts/ci/reports/codequality_report/store/index.js b/app/assets/javascripts/ci/reports/codequality_report/store/index.js index 5bfcd69edec..c2f706e56e6 100644 --- a/app/assets/javascripts/ci/reports/codequality_report/store/index.js +++ b/app/assets/javascripts/ci/reports/codequality_report/store/index.js @@ -1,4 +1,5 @@ import Vue from 'vue'; +// eslint-disable-next-line no-restricted-imports import Vuex from 'vuex'; import * as actions from './actions'; import * as getters from './getters'; diff --git a/app/assets/javascripts/ci/reports/components/report_item.vue b/app/assets/javascripts/ci/reports/components/report_item.vue index ca1f3301691..7342df6da67 100644 --- a/app/assets/javascripts/ci/reports/components/report_item.vue +++ b/app/assets/javascripts/ci/reports/components/report_item.vue @@ -53,7 +53,7 @@ export default { }; </script> <template> - <li class="report-block-list-issue gl-p-3!" data-qa-selector="report_item_row"> + <li class="report-block-list-issue gl-p-3!" data-testid="report-item-row"> <component :is="iconComponent" v-if="showReportSectionStatusIcon" diff --git a/app/assets/javascripts/ci/reports/components/report_section.vue b/app/assets/javascripts/ci/reports/components/report_section.vue index 468c8916b8d..a4ec7b6a325 100644 --- a/app/assets/javascripts/ci/reports/components/report_section.vue +++ b/app/assets/javascripts/ci/reports/components/report_section.vue @@ -185,10 +185,7 @@ export default { <div class="media"> <status-icon :status="statusIconName" :size="24" class="align-self-center" /> <div class="media-body gl-display-flex gl-align-items-flex-start gl-flex-direction-row!"> - <div - data-testid="report-section-code-text" - class="js-code-text code-text gl-align-self-center gl-flex-grow-1" - > + <div class="js-code-text code-text gl-align-self-center gl-flex-grow-1"> <div class="gl-display-flex gl-align-items-center"> <p class="gl-line-height-normal gl-m-0">{{ headerText }}</p> <slot :name="slotName"></slot> diff --git a/app/assets/javascripts/ci/runner/admin_runners/admin_runners_app.vue b/app/assets/javascripts/ci/runner/admin_runners/admin_runners_app.vue index 2168685e703..e6813211fe9 100644 --- a/app/assets/javascripts/ci/runner/admin_runners/admin_runners_app.vue +++ b/app/assets/javascripts/ci/runner/admin_runners/admin_runners_app.vue @@ -52,6 +52,8 @@ export default { RunnerTypeTabs, RunnerActionsCell, RunnerJobStatusBadge, + RunnerDashboardLink: () => + import('ee_component/ci/runner/components/runner_dashboard_link.vue'), }, mixins: [glFeatureFlagMixin()], props: { @@ -188,12 +190,12 @@ export default { nav-class="gl-border-none!" /> - <div class="gl-w-full gl-md-w-auto gl-display-flex"> + <div class="gl-w-full gl-md-w-auto gl-display-flex gl-gap-3"> + <runner-dashboard-link /> <gl-button :href="newRunnerPath" variant="confirm"> {{ s__('Runners|New instance runner') }} </gl-button> <registration-dropdown - class="gl-ml-3" :registration-token="registrationToken" :type="$options.INSTANCE_TYPE" placement="right" diff --git a/app/assets/javascripts/ci/runner/admin_runners/index.js b/app/assets/javascripts/ci/runner/admin_runners/index.js index 54eb37f8c90..d4df1393487 100644 --- a/app/assets/javascripts/ci/runner/admin_runners/index.js +++ b/app/assets/javascripts/ci/runner/admin_runners/index.js @@ -1,6 +1,9 @@ import { GlToast } from '@gitlab/ui'; import Vue from 'vue'; import VueApollo from 'vue-apollo'; + +import { provide } from 'ee_else_ce/ci/runner/admin_runners/provide'; + import { visitUrl } from '~/lib/utils/url_utility'; import { updateOutdatedUrl } from '~/ci/runner/runner_search_utils'; import createDefaultClient from '~/lib/graphql'; @@ -29,14 +32,7 @@ export const initAdminRunners = (selector = '#js-admin-runners') => { return null; } - const { - runnerInstallHelpPage, - newRunnerPath, - registrationToken, - onlineContactTimeoutSecs, - staleTimeoutSecs, - } = el.dataset; - + const { newRunnerPath, registrationToken } = el.dataset; const { cacheConfig, typeDefs, localMutations } = createLocalState(); const apolloProvider = new VueApollo({ @@ -47,10 +43,8 @@ export const initAdminRunners = (selector = '#js-admin-runners') => { el, apolloProvider, provide: { - runnerInstallHelpPage, + ...provide(el.dataset), localMutations, - onlineContactTimeoutSecs, - staleTimeoutSecs, }, render(h) { return h(AdminRunnersApp, { diff --git a/app/assets/javascripts/ci/runner/admin_runners/provide.js b/app/assets/javascripts/ci/runner/admin_runners/provide.js new file mode 100644 index 00000000000..81a39801718 --- /dev/null +++ b/app/assets/javascripts/ci/runner/admin_runners/provide.js @@ -0,0 +1,22 @@ +/** + * Provides global values to the admin runners app. + * + * @param {Object} `data-` HTML attributes of the mounting point + * @returns An object with properties to use provide/inject of the root app. + * See EE version + */ +export const provide = (elDataset) => { + const { + runnerInstallHelpPage, + onlineContactTimeoutSecs, + staleTimeoutSecs, + tagSuggestionsPath, + } = elDataset; + + return { + runnerInstallHelpPage, + onlineContactTimeoutSecs, + staleTimeoutSecs, + tagSuggestionsPath, + }; +}; diff --git a/app/assets/javascripts/ci/runner/components/registration/registration_instructions.vue b/app/assets/javascripts/ci/runner/components/registration/registration_instructions.vue index 69021dde0e9..771ecb1a0d4 100644 --- a/app/assets/javascripts/ci/runner/components/registration/registration_instructions.vue +++ b/app/assets/javascripts/ci/runner/components/registration/registration_instructions.vue @@ -163,7 +163,7 @@ export default { " > <template #link="{ content }"> - <gl-link data-testid="runner-install-link" @click="toggleDrawer">{{ content }}</gl-link> + <gl-link @click="toggleDrawer">{{ content }}</gl-link> </template> </gl-sprintf> </p> diff --git a/app/assets/javascripts/ci/runner/components/registration/registration_token.vue b/app/assets/javascripts/ci/runner/components/registration/registration_token.vue index 339c92a427f..50d2fcfa961 100644 --- a/app/assets/javascripts/ci/runner/components/registration/registration_token.vue +++ b/app/assets/javascripts/ci/runner/components/registration/registration_token.vue @@ -45,6 +45,7 @@ export default { :label-for="inputId" :copy-button-title="$options.I18N_COPY_BUTTON_TITLE" :form-input-group-props="formInputGroupProps" + readonly @copy="onCopy" > <template v-for="slot in Object.keys($scopedSlots)" #[slot]> diff --git a/app/assets/javascripts/ci/runner/components/runner_details.vue b/app/assets/javascripts/ci/runner/components/runner_details.vue index 8c1280cffb9..fac90fb0370 100644 --- a/app/assets/javascripts/ci/runner/components/runner_details.vue +++ b/app/assets/javascripts/ci/runner/components/runner_details.vue @@ -94,10 +94,7 @@ export default { <div> <runner-upgrade-status-alert class="gl-my-4" :runner="runner" /> <div class="gl-pt-4"> - <dl - class="gl-mb-0 gl-display-grid runner-details-grid-template" - data-testid="runner-details-list" - > + <dl class="gl-mb-0 gl-display-grid runner-details-grid-template"> <runner-detail :label="s__('Runners|Description')" :value="runner.description" /> <runner-detail :label="s__('Runners|Last contact')" diff --git a/app/assets/javascripts/ci/runner/components/runner_filtered_search_bar.vue b/app/assets/javascripts/ci/runner/components/runner_filtered_search_bar.vue index ee56fea8282..3634dcf1c93 100644 --- a/app/assets/javascripts/ci/runner/components/runner_filtered_search_bar.vue +++ b/app/assets/javascripts/ci/runner/components/runner_filtered_search_bar.vue @@ -93,6 +93,8 @@ export default { :tokens="validTokens" :initial-sort-by="initialSortBy" :search-input-placeholder="__('Search or filter results...')" + :search-text-option-label="s__('Runners|Search description...')" + terms-as-tokens data-testid="runners-filtered-search" @onFilter="onFilter" @onSort="onSort" diff --git a/app/assets/javascripts/ci/runner/components/search_tokens/paused_token_config.js b/app/assets/javascripts/ci/runner/components/search_tokens/paused_token_config.js index 71a145dd4a3..5bec9804002 100644 --- a/app/assets/javascripts/ci/runner/components/search_tokens/paused_token_config.js +++ b/app/assets/javascripts/ci/runner/components/search_tokens/paused_token_config.js @@ -3,26 +3,15 @@ import { OPERATORS_IS } from '~/vue_shared/components/filtered_search_bar/consta import BaseToken from '~/vue_shared/components/filtered_search_bar/tokens/base_token.vue'; import { PARAM_KEY_PAUSED, I18N_PAUSED } from '../../constants'; -const options = [ - { value: 'true', title: __('Yes') }, - { value: 'false', title: __('No') }, -]; - export const pausedTokenConfig = { icon: 'pause', title: I18N_PAUSED, type: PARAM_KEY_PAUSED, token: BaseToken, unique: true, - options: options.map(({ value, title }) => ({ - value, - // Replace whitespace with a special character to avoid - // splitting this value. - // Replacing in each option, as translations may also - // contain spaces! - // see: https://gitlab.com/gitlab-org/gitlab/-/issues/344142 - // see: https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1438 - title: title.replace(/\s/g, '\u00a0'), - })), + options: [ + { value: 'true', title: __('Yes') }, + { value: 'false', title: __('No') }, + ], operators: OPERATORS_IS, }; diff --git a/app/assets/javascripts/ci/runner/components/search_tokens/status_token_config.js b/app/assets/javascripts/ci/runner/components/search_tokens/status_token_config.js index 4bc32909777..1e4774bff72 100644 --- a/app/assets/javascripts/ci/runner/components/search_tokens/status_token_config.js +++ b/app/assets/javascripts/ci/runner/components/search_tokens/status_token_config.js @@ -15,28 +15,17 @@ import { PARAM_KEY_STATUS, } from '../../constants'; -const options = [ - { value: STATUS_ONLINE, title: I18N_STATUS_ONLINE }, - { value: STATUS_OFFLINE, title: I18N_STATUS_OFFLINE }, - { value: STATUS_NEVER_CONTACTED, title: I18N_STATUS_NEVER_CONTACTED }, - { value: STATUS_STALE, title: I18N_STATUS_STALE }, -]; - export const statusTokenConfig = { icon: 'status', title: TOKEN_TITLE_STATUS, type: PARAM_KEY_STATUS, token: BaseToken, unique: true, - options: options.map(({ value, title }) => ({ - value, - // Replace whitespace with a special character to avoid - // splitting this value. - // Replacing in each option, as translations may also - // contain spaces! - // see: https://gitlab.com/gitlab-org/gitlab/-/issues/344142 - // see: https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1438 - title: title.replace(/\s/g, '\u00a0'), - })), + options: [ + { value: STATUS_ONLINE, title: I18N_STATUS_ONLINE }, + { value: STATUS_OFFLINE, title: I18N_STATUS_OFFLINE }, + { value: STATUS_NEVER_CONTACTED, title: I18N_STATUS_NEVER_CONTACTED }, + { value: STATUS_STALE, title: I18N_STATUS_STALE }, + ], operators: OPERATORS_IS, }; diff --git a/app/assets/javascripts/ci/runner/components/search_tokens/tag_token.vue b/app/assets/javascripts/ci/runner/components/search_tokens/tag_token.vue index 1de7775090a..dd1cca0a05c 100644 --- a/app/assets/javascripts/ci/runner/components/search_tokens/tag_token.vue +++ b/app/assets/javascripts/ci/runner/components/search_tokens/tag_token.vue @@ -7,20 +7,13 @@ import { s__ } from '~/locale'; import BaseToken from '~/vue_shared/components/filtered_search_bar/tokens/base_token.vue'; import { RUNNER_TAG_BG_CLASS } from '../../constants'; -// TODO This should be implemented via a GraphQL API -// The API should -// 1) scope to the rights of the user -// 2) stay up to date to the removal of old tags -// 3) consider the scope of search, like searching within the tags of a group -// See: https://gitlab.com/gitlab-org/gitlab/-/issues/333796 -export const TAG_SUGGESTIONS_PATH = '/admin/runners/tag_list.json'; - export default { components: { BaseToken, GlFilteredSearchSuggestion, GlToken, }, + inject: ['tagSuggestionsPath'], props: { config: { type: Object, @@ -36,7 +29,7 @@ export default { methods: { getTagsOptions(search) { return axios - .get(TAG_SUGGESTIONS_PATH, { + .get(this.tagSuggestionsPath, { params: { search, }, |