diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-03-13 15:09:21 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-03-13 15:09:21 +0000 |
commit | c36152ff8c41fad2f413f253eb7ac5c927e47c56 (patch) | |
tree | bbf300da207de3e8bbf272d44111ceedb18f5833 /app/assets/javascripts/ci_variable_list | |
parent | 286fe61013674fe2d245ffc8d2233baf09923e70 (diff) | |
download | gitlab-ce-c36152ff8c41fad2f413f253eb7ac5c927e47c56.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/ci_variable_list')
7 files changed, 250 insertions, 83 deletions
diff --git a/app/assets/javascripts/ci_variable_list/components/ci_variable_modal.vue b/app/assets/javascripts/ci_variable_list/components/ci_variable_modal.vue index 3e58fc40755..637f0237b63 100644 --- a/app/assets/javascripts/ci_variable_list/components/ci_variable_modal.vue +++ b/app/assets/javascripts/ci_variable_list/components/ci_variable_modal.vue @@ -7,6 +7,7 @@ import { GlFormSelect, GlFormGroup, GlFormInput, + GlFormTextarea, GlFormCheckbox, GlLink, GlIcon, @@ -19,6 +20,7 @@ export default { GlFormSelect, GlFormGroup, GlFormInput, + GlFormTextarea, GlFormCheckbox, GlLink, GlIcon, @@ -34,17 +36,29 @@ export default { 'maskableRegex', ]), canSubmit() { + if (this.variableData.masked && this.maskedState === false) { + return false; + } return this.variableData.key !== '' && this.variableData.secret_value !== ''; }, canMask() { const regex = RegExp(this.maskableRegex); return regex.test(this.variableData.secret_value); }, + displayMaskedError() { + return !this.canMask && this.variableData.masked && this.variableData.secret_value !== ''; + }, + maskedState() { + if (this.displayMaskedError) { + return false; + } + return null; + }, variableData() { return this.variableBeingEdited || this.variable; }, modalActionText() { - return this.variableBeingEdited ? __('Update Variable') : __('Add variable'); + return this.variableBeingEdited ? __('Update variable') : __('Add variable'); }, primaryAction() { return { @@ -52,11 +66,23 @@ export default { attributes: { variant: 'success', disabled: !this.canSubmit }, }; }, + deleteAction() { + if (this.variableBeingEdited) { + return { + text: __('Delete variable'), + attributes: { variant: 'danger', category: 'secondary' }, + }; + } + return null; + }, cancelAction() { return { text: __('Cancel'), }; }, + maskedFeedback() { + return __('This variable can not be masked'); + }, }, methods: { ...mapActions([ @@ -65,6 +91,7 @@ export default { 'resetEditing', 'displayInputValue', 'clearModal', + 'deleteVariable', ]), updateOrAddVariable() { if (this.variableBeingEdited) { @@ -89,74 +116,93 @@ export default { :modal-id="$options.modalId" :title="modalActionText" :action-primary="primaryAction" + :action-secondary="deleteAction" :action-cancel="cancelAction" @ok="updateOrAddVariable" @hidden="resetModalHandler" + @secondary="deleteVariable(variableBeingEdited)" > <form> - <gl-form-group label="Type" label-for="ci-variable-type"> - <gl-form-select - id="ci-variable-type" - v-model="variableData.variable_type" - :options="typeOptions" + <gl-form-group :label="__('Key')" label-for="ci-variable-key"> + <gl-form-input + id="ci-variable-key" + v-model="variableData.key" + data-qa-selector="variable_key" + /> + </gl-form-group> + + <gl-form-group + :label="__('Value')" + label-for="ci-variable-value" + :state="maskedState" + :invalid-feedback="maskedFeedback" + > + <gl-form-textarea + id="ci-variable-value" + v-model="variableData.secret_value" + rows="3" + max-rows="6" + data-qa-selector="variable_value" /> </gl-form-group> <div class="d-flex"> - <gl-form-group label="Key" label-for="ci-variable-key" class="w-50 append-right-15"> - <gl-form-input - id="ci-variable-key" - v-model="variableData.key" - type="text" - data-qa-selector="variable_key" + <gl-form-group + :label="__('Type')" + label-for="ci-variable-type" + class="w-50 append-right-15" + :class="{ 'w-100': isGroup }" + > + <gl-form-select + id="ci-variable-type" + v-model="variableData.variable_type" + :options="typeOptions" /> </gl-form-group> - <gl-form-group label="Value" label-for="ci-variable-value" class="w-50"> - <gl-form-input - id="ci-variable-value" - v-model="variableData.secret_value" - type="text" - data-qa-selector="variable_value" + <gl-form-group + v-if="!isGroup" + :label="__('Environment scope')" + label-for="ci-variable-env" + class="w-50" + > + <gl-form-select + id="ci-variable-env" + v-model="variableData.environment_scope" + :options="environments" /> </gl-form-group> </div> - <gl-form-group v-if="!isGroup" label="Environment scope" label-for="ci-variable-env"> - <gl-form-select - id="ci-variable-env" - v-model="variableData.environment_scope" - :options="environments" - /> - </gl-form-group> - - <gl-form-group label="Flags" label-for="ci-variable-flags"> + <gl-form-group :label="__('Flags')" label-for="ci-variable-flags"> <gl-form-checkbox v-model="variableData.protected" class="mb-0"> {{ __('Protect variable') }} <gl-link href="/help/ci/variables/README#protected-environment-variables"> <gl-icon name="question" :size="12" /> </gl-link> - <p class="prepend-top-4 clgray"> - {{ __('Allow variables to run on protected branches and tags.') }} + <p class="prepend-top-4 text-secondary"> + {{ __('Export variable to pipelines running on protected branches and tags only.') }} </p> </gl-form-checkbox> <gl-form-checkbox ref="masked-ci-variable" v-model="variableData.masked" - :disabled="!canMask" data-qa-selector="variable_masked" > {{ __('Mask variable') }} <gl-link href="/help/ci/variables/README#masked-variables"> <gl-icon name="question" :size="12" /> </gl-link> - <p class="prepend-top-4 append-bottom-0 clgray"> - {{ - __( - 'Variables will be masked in job logs. Requires values to meet regular expression requirements.', - ) - }} + <p class="prepend-top-4 append-bottom-0 text-secondary"> + {{ __('Variable will be masked in job logs.') }} + <span + :class="{ + 'bold text-plain': displayMaskedError, + }" + > + {{ __('Requires values to meet regular expression requirements.') }}</span + > <gl-link href="/help/ci/variables/README#masked-variables">{{ __('More information') }}</gl-link> diff --git a/app/assets/javascripts/ci_variable_list/components/ci_variable_popover.vue b/app/assets/javascripts/ci_variable_list/components/ci_variable_popover.vue new file mode 100644 index 00000000000..c4b1bc18f5a --- /dev/null +++ b/app/assets/javascripts/ci_variable_list/components/ci_variable_popover.vue @@ -0,0 +1,55 @@ +<script> +import { GlPopover, GlIcon, GlButton, GlTooltipDirective } from '@gitlab/ui'; + +export default { + maxTextLength: 95, + components: { + GlPopover, + GlIcon, + GlButton, + }, + directives: { + GlTooltip: GlTooltipDirective, + }, + props: { + target: { + type: String, + required: true, + }, + value: { + type: String, + required: true, + }, + tooltipText: { + type: String, + required: true, + }, + }, + computed: { + displayValue() { + if (this.value.length > this.$options.maxTextLength) { + return `${this.value.substring(0, this.$options.maxTextLength)}...`; + } + return this.value; + }, + }, +}; +</script> + +<template> + <div id="popover-container"> + <gl-popover :target="target" triggers="hover" placement="top" container="popover-container"> + <div class="d-flex justify-content-between position-relative"> + <div class="pr-5 w-100 ci-popover-value">{{ displayValue }}</div> + <gl-button + v-gl-tooltip + class="btn-transparent btn-clipboard position-absolute position-top-0 position-right-0" + :title="tooltipText" + :data-clipboard-text="value" + > + <gl-icon name="copy-to-clipboard" /> + </gl-button> + </div> + </gl-popover> + </div> +</template> diff --git a/app/assets/javascripts/ci_variable_list/components/ci_variable_table.vue b/app/assets/javascripts/ci_variable_list/components/ci_variable_table.vue index e240323d2c5..3f2f89ada6f 100644 --- a/app/assets/javascripts/ci_variable_list/components/ci_variable_table.vue +++ b/app/assets/javascripts/ci_variable_list/components/ci_variable_table.vue @@ -3,44 +3,58 @@ import { GlTable, GlButton, GlModalDirective, GlIcon } from '@gitlab/ui'; import { s__, __ } from '~/locale'; import { mapState, mapActions } from 'vuex'; import { ADD_CI_VARIABLE_MODAL_ID } from '../constants'; +import CiVariablePopover from './ci_variable_popover.vue'; export default { modalId: ADD_CI_VARIABLE_MODAL_ID, + trueIcon: 'mobile-issue-close', + falseIcon: 'close', + iconSize: 16, fields: [ { key: 'variable_type', label: s__('CiVariables|Type'), + customStyle: { width: '70px' }, }, { key: 'key', label: s__('CiVariables|Key'), + tdClass: 'text-plain', + sortable: true, + customStyle: { width: '40%' }, }, { key: 'value', label: s__('CiVariables|Value'), tdClass: 'qa-ci-variable-input-value', + customStyle: { width: '40%' }, }, { key: 'protected', label: s__('CiVariables|Protected'), + customStyle: { width: '100px' }, }, { key: 'masked', label: s__('CiVariables|Masked'), + customStyle: { width: '100px' }, }, { key: 'environment_scope', - label: s__('CiVariables|Environment Scope'), + label: s__('CiVariables|Environments'), + customStyle: { width: '20%' }, }, { key: 'actions', label: '', + customStyle: { width: '35px' }, }, ], components: { GlTable, GlButton, GlIcon, + CiVariablePopover, }, directives: { GlModalDirective, @@ -64,7 +78,7 @@ export default { this.fetchVariables(); }, methods: { - ...mapActions(['fetchVariables', 'deleteVariable', 'toggleValues', 'editVariable']), + ...mapActions(['fetchVariables', 'toggleValues', 'editVariable']), }, }; </script> @@ -74,42 +88,82 @@ export default { <gl-table :fields="fields" :items="variables" - responsive - show-empty tbody-tr-class="js-ci-variable-row" + sort-by="key" + sort-direction="asc" + stacked="lg" + fixed + show-empty + sort-icon-left + no-sort-reset > - <template #cell(value)="data"> - <span v-if="valuesHidden">*****************</span> - <span v-else>{{ data.value }}</span> + <template #table-colgroup="scope"> + <col v-for="field in scope.fields" :key="field.key" :style="field.customStyle" /> + </template> + <template #cell(key)="{ item }"> + <div class="d-flex truncated-container"> + <span :id="`ci-variable-key-${item.id}`" class="d-inline-block mw-100 text-truncate">{{ + item.key + }}</span> + <ci-variable-popover + :target="`ci-variable-key-${item.id}`" + :value="item.key" + :tooltip-text="__('Copy key')" + /> + </div> + </template> + <template #cell(value)="{ item }"> + <span v-if="valuesHidden">*********************</span> + <div v-else class="d-flex truncated-container"> + <span :id="`ci-variable-value-${item.id}`" class="d-inline-block mw-100 text-truncate">{{ + item.value + }}</span> + <ci-variable-popover + :target="`ci-variable-value-${item.id}`" + :value="item.value" + :tooltip-text="__('Copy value')" + /> + </div> + </template> + <template #cell(protected)="{ item }"> + <gl-icon v-if="item.protected" :size="$options.iconSize" :name="$options.trueIcon" /> + <gl-icon v-else :size="$options.iconSize" :name="$options.falseIcon" /> + </template> + <template #cell(masked)="{ item }"> + <gl-icon v-if="item.masked" :size="$options.iconSize" :name="$options.trueIcon" /> + <gl-icon v-else :size="$options.iconSize" :name="$options.falseIcon" /> </template> - <template #cell(actions)="data"> + <template #cell(environment_scope)="{ item }"> + <div class="d-flex truncated-container"> + <span :id="`ci-variable-env-${item.id}`" class="d-inline-block mw-100 text-truncate">{{ + item.environment_scope + }}</span> + <ci-variable-popover + :target="`ci-variable-env-${item.id}`" + :value="item.environment_scope" + :tooltip-text="__('Copy environment')" + /> + </div> + </template> + <template #cell(actions)="{ item }"> <gl-button ref="edit-ci-variable" v-gl-modal-directive="$options.modalId" - @click="editVariable(data.item)" + @click="editVariable(item)" > - <gl-icon name="pencil" /> - </gl-button> - <gl-button - ref="delete-ci-variable" - category="secondary" - variant="danger" - @click="deleteVariable(data.item)" - > - <gl-icon name="remove" /> + <gl-icon :size="$options.iconSize" name="pencil" /> </gl-button> </template> <template #empty> - <p ref="empty-variables" class="settings-message text-center empty-variables"> - {{ - __( - 'There are currently no variables, add a variable with the Add Variable button below.', - ) - }} + <p ref="empty-variables" class="text-center empty-variables text-plain"> + {{ __('There are no variables yet.') }} </p> </template> </gl-table> - <div class="ci-variable-actions d-flex justify-content-end"> + <div + class="ci-variable-actions d-flex justify-content-end" + :class="{ 'justify-content-center': !tableIsNotEmpty }" + > <gl-button v-if="tableIsNotEmpty" ref="secret-value-reveal-button" diff --git a/app/assets/javascripts/ci_variable_list/constants.js b/app/assets/javascripts/ci_variable_list/constants.js index bfc9cbbd840..b2fa980c546 100644 --- a/app/assets/javascripts/ci_variable_list/constants.js +++ b/app/assets/javascripts/ci_variable_list/constants.js @@ -1,2 +1,16 @@ -// eslint-disable-next-line import/prefer-default-export +import { __ } from '~/locale'; + +// eslint-disable import/prefer-default-export export const ADD_CI_VARIABLE_MODAL_ID = 'add-ci-variable'; + +export const displayText = { + variableText: __('Var'), + fileText: __('File'), + allEnvironmentsText: __('All'), +}; + +export const types = { + variableType: 'env_var', + fileType: 'file', + allEnvironmentsType: '*', +}; diff --git a/app/assets/javascripts/ci_variable_list/store/mutations.js b/app/assets/javascripts/ci_variable_list/store/mutations.js index 74e2bcfa2db..c75eb4a91fd 100644 --- a/app/assets/javascripts/ci_variable_list/store/mutations.js +++ b/app/assets/javascripts/ci_variable_list/store/mutations.js @@ -1,5 +1,5 @@ import * as types from './mutation_types'; -import { __ } from '~/locale'; +import { displayText } from '../constants'; export default { [types.REQUEST_VARIABLES](state) { @@ -61,7 +61,7 @@ export default { [types.RECEIVE_ENVIRONMENTS_SUCCESS](state, environments) { state.isLoading = false; state.environments = environments; - state.environments.unshift(__('All environments')); + state.environments.unshift(displayText.allEnvironmentsText); }, [types.VARIABLE_BEING_EDITED](state, variable) { @@ -70,12 +70,12 @@ export default { [types.CLEAR_MODAL](state) { state.variable = { - variable_type: __('Variable'), + variable_type: displayText.variableText, key: '', secret_value: '', protected: false, masked: false, - environment_scope: __('All environments'), + environment_scope: displayText.allEnvironmentsText, }; }, diff --git a/app/assets/javascripts/ci_variable_list/store/state.js b/app/assets/javascripts/ci_variable_list/store/state.js index c5e0bbfdbf4..5166321d6a7 100644 --- a/app/assets/javascripts/ci_variable_list/store/state.js +++ b/app/assets/javascripts/ci_variable_list/store/state.js @@ -1,4 +1,4 @@ -import { __ } from '~/locale'; +import { displayText } from '../constants'; export default () => ({ endpoint: null, @@ -8,17 +8,17 @@ export default () => ({ isLoading: false, isDeleting: false, variable: { - variable_type: __('Variable'), + variable_type: displayText.variableText, key: '', secret_value: '', protected: false, masked: false, - environment_scope: __('All environments'), + environment_scope: displayText.allEnvironmentsText, }, variables: null, valuesHidden: true, error: null, environments: [], - typeOptions: [__('Variable'), __('File')], + typeOptions: [displayText.variableText, displayText.fileText], variableBeingEdited: null, }); diff --git a/app/assets/javascripts/ci_variable_list/store/utils.js b/app/assets/javascripts/ci_variable_list/store/utils.js index 0b9932d9bb5..3cd8c85024b 100644 --- a/app/assets/javascripts/ci_variable_list/store/utils.js +++ b/app/assets/javascripts/ci_variable_list/store/utils.js @@ -1,23 +1,22 @@ -import { __ } from '~/locale'; import { cloneDeep } from 'lodash'; +import { displayText, types } from '../constants'; -const variableType = 'env_var'; -const fileType = 'file'; - -const variableTypeHandler = type => (type === 'Variable' ? variableType : fileType); +const variableTypeHandler = type => + type === displayText.variableText ? types.variableType : types.fileType; export const prepareDataForDisplay = variables => { const variablesToDisplay = []; variables.forEach(variable => { const variableCopy = variable; - if (variableCopy.variable_type === variableType) { - variableCopy.variable_type = __('Variable'); + if (variableCopy.variable_type === types.variableType) { + variableCopy.variable_type = displayText.variableText; } else { - variableCopy.variable_type = __('File'); + variableCopy.variable_type = displayText.fileText; } + variableCopy.secret_value = variableCopy.value; - if (variableCopy.environment_scope === '*') { - variableCopy.environment_scope = __('All environments'); + if (variableCopy.environment_scope === types.allEnvironmentsType) { + variableCopy.environment_scope = displayText.allEnvironmentsText; } variablesToDisplay.push(variableCopy); }); @@ -29,9 +28,8 @@ export const prepareDataForApi = (variable, destroy = false) => { variableCopy.protected = variableCopy.protected.toString(); variableCopy.masked = variableCopy.masked.toString(); variableCopy.variable_type = variableTypeHandler(variableCopy.variable_type); - - if (variableCopy.environment_scope === __('All environments')) { - variableCopy.environment_scope = __('*'); + if (variableCopy.environment_scope === displayText.allEnvironmentsText) { + variableCopy.environment_scope = types.allEnvironmentsType; } if (destroy) { |