diff options
Diffstat (limited to 'app')
24 files changed, 358 insertions, 149 deletions
diff --git a/app/assets/javascripts/boards/mixins/sortable_default_options.js b/app/assets/javascripts/boards/mixins/sortable_default_options.js index f77f131c71a..68ea28e68d9 100644 --- a/app/assets/javascripts/boards/mixins/sortable_default_options.js +++ b/app/assets/javascripts/boards/mixins/sortable_default_options.js @@ -26,7 +26,6 @@ export function getBoardSortableDefaultOptions(obj) { scrollSpeed: 20, onStart: sortableStart, onEnd: sortableEnd, - fallbackTolerance: 1, }); Object.keys(obj).forEach(key => { 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) { diff --git a/app/assets/javascripts/contributors/components/contributors.vue b/app/assets/javascripts/contributors/components/contributors.vue index 8dbf0a68c43..19516a13d15 100644 --- a/app/assets/javascripts/contributors/components/contributors.vue +++ b/app/assets/javascripts/contributors/components/contributors.vue @@ -66,12 +66,12 @@ export default { individualChartsData() { const maxNumberOfIndividualContributorsCharts = 100; - return Object.keys(this.parsedData.byAuthor) - .map(name => { - const author = this.parsedData.byAuthor[name]; + return Object.keys(this.parsedData.byAuthorEmail) + .map(email => { + const author = this.parsedData.byAuthorEmail[email]; return { - name, - email: author.email, + name: author.name, + email, commits: author.commits, dates: [ { diff --git a/app/assets/javascripts/contributors/stores/getters.js b/app/assets/javascripts/contributors/stores/getters.js index 9e02e3ed9e7..9b0def9b3ca 100644 --- a/app/assets/javascripts/contributors/stores/getters.js +++ b/app/assets/javascripts/contributors/stores/getters.js @@ -1,17 +1,17 @@ export const showChart = state => Boolean(!state.loading && state.chartData); export const parsedData = state => { - const byAuthor = {}; + const byAuthorEmail = {}; const total = {}; state.chartData.forEach(({ date, author_name, author_email }) => { total[date] = total[date] ? total[date] + 1 : 1; - const authorData = byAuthor[author_name]; + const authorData = byAuthorEmail[author_email]; if (!authorData) { - byAuthor[author_name] = { - email: author_email.toLowerCase(), + byAuthorEmail[author_email] = { + name: author_name, commits: 1, dates: { [date]: 1, @@ -25,7 +25,7 @@ export const parsedData = state => { return { total, - byAuthor, + byAuthorEmail, }; }; diff --git a/app/assets/javascripts/reports/store/utils.js b/app/assets/javascripts/reports/store/utils.js index ce3ffaae703..5d3d9ddda3b 100644 --- a/app/assets/javascripts/reports/store/utils.js +++ b/app/assets/javascripts/reports/store/utils.js @@ -11,9 +11,10 @@ const textBuilder = results => { const { failed, errored, resolved, total } = results; const failedOrErrored = (failed || 0) + (errored || 0); - const failedString = failedOrErrored - ? n__('%d failed/error test result', '%d failed/error test results', failedOrErrored) - : null; + const failedString = failed ? n__('%d failed', '%d failed', failed) : null; + const erroredString = errored ? n__('%d error', '%d errors', errored) : null; + const combinedString = + failed && errored ? `${failedString}, ${erroredString}` : failedString || erroredString; const resolvedString = resolved ? n__('%d fixed test result', '%d fixed test results', resolved) : null; @@ -23,12 +24,12 @@ const textBuilder = results => { if (failedOrErrored) { if (resolved) { - resultsString = sprintf(s__('Reports|%{failedString} and %{resolvedString}'), { - failedString, + resultsString = sprintf(s__('Reports|%{combinedString} and %{resolvedString}'), { + combinedString, resolvedString, }); } else { - resultsString = failedString; + resultsString = combinedString; } } else if (resolved) { resultsString = resolvedString; diff --git a/app/assets/javascripts/sidebar/queries/updateStatus.mutation.graphql b/app/assets/javascripts/sidebar/queries/updateStatus.mutation.graphql new file mode 100644 index 00000000000..27a5cff12c7 --- /dev/null +++ b/app/assets/javascripts/sidebar/queries/updateStatus.mutation.graphql @@ -0,0 +1,7 @@ +mutation ($projectPath: ID!, $iid: String!, $healthStatus: HealthStatus) { + updateIssue(input: { projectPath: $projectPath, iid: $iid, healthStatus: $healthStatus}) { + issue { + healthStatus + } + } +} diff --git a/app/assets/javascripts/sidebar/services/sidebar_service.js b/app/assets/javascripts/sidebar/services/sidebar_service.js index 59d4f6ed388..3b8903b4a4c 100644 --- a/app/assets/javascripts/sidebar/services/sidebar_service.js +++ b/app/assets/javascripts/sidebar/services/sidebar_service.js @@ -18,7 +18,7 @@ export default class SidebarService { this.moveIssueEndpoint = endpointMap.moveIssueEndpoint; this.projectsAutocompleteEndpoint = endpointMap.projectsAutocompleteEndpoint; this.fullPath = endpointMap.fullPath; - this.id = endpointMap.id; + this.iid = endpointMap.iid; SidebarService.singleton = this; } @@ -37,7 +37,7 @@ export default class SidebarService { : sidebarDetailsQuery, variables: { fullPath: this.fullPath, - iid: this.id.toString(), + iid: this.iid.toString(), }, }), ]); @@ -47,6 +47,17 @@ export default class SidebarService { return axios.put(this.endpoint, { [key]: data }); } + updateWithGraphQl(mutation, variables) { + return gqClient.mutate({ + mutation, + variables: { + ...variables, + projectPath: this.fullPath, + iid: this.iid.toString(), + }, + }); + } + getProjectsAutocomplete(searchTerm) { return axios.get(this.projectsAutocompleteEndpoint, { params: { diff --git a/app/assets/javascripts/sidebar/sidebar_mediator.js b/app/assets/javascripts/sidebar/sidebar_mediator.js index eb1cf977725..34621fc1036 100644 --- a/app/assets/javascripts/sidebar/sidebar_mediator.js +++ b/app/assets/javascripts/sidebar/sidebar_mediator.js @@ -20,7 +20,7 @@ export default class SidebarMediator { moveIssueEndpoint: options.moveIssueEndpoint, projectsAutocompleteEndpoint: options.projectsAutocompleteEndpoint, fullPath: options.fullPath, - id: options.id, + iid: options.iid, }); SidebarMediator.singleton = this; } diff --git a/app/assets/stylesheets/pages/settings.scss b/app/assets/stylesheets/pages/settings.scss index 7294816aa26..c90b92a5b49 100644 --- a/app/assets/stylesheets/pages/settings.scss +++ b/app/assets/stylesheets/pages/settings.scss @@ -376,8 +376,29 @@ } .ci-variable-table { - table tr th { - background-color: transparent; - border: 0; + table { + thead { + border-bottom: 1px solid $white-normal; + } + + tr { + td, + th { + padding-left: 0; + } + + th { + background-color: transparent; + font-weight: $gl-font-weight-bold; + border: 0; + } + } + } + + @media(max-width: map-get($grid-breakpoints, lg)-1) { + .truncated-container { + justify-content: flex-end; + } } } + diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index aa05076c43e..acfd972bb83 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -463,7 +463,7 @@ module IssuablesHelper currentUser: issuable[:current_user], rootPath: root_path, fullPath: issuable[:project_full_path], - id: issuable[:id], + iid: issuable[:iid], timeTrackingLimitToHours: Gitlab::CurrentSettings.time_tracking_limit_to_hours } end diff --git a/app/models/ci/bridge.rb b/app/models/ci/bridge.rb index 955b16ad50d..fa0619f35b0 100644 --- a/app/models/ci/bridge.rb +++ b/app/models/ci/bridge.rb @@ -7,7 +7,7 @@ module Ci include Ci::Metadatable include Importable include AfterCommitQueue - include HasRef + include Ci::HasRef InvalidBridgeTypeError = Class.new(StandardError) InvalidTransitionError = Class.new(StandardError) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index fd099107fe7..4762740ee2f 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -10,7 +10,7 @@ module Ci include ObjectStorage::BackgroundMove include Presentable include Importable - include HasRef + include Ci::HasRef include IgnorableColumns BuildArchivedError = Class.new(StandardError) diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index bc0528bdbe0..d841601cf4b 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -11,7 +11,7 @@ module Ci include Gitlab::Utils::StrongMemoize include AtomicInternalId include EnumWithNil - include HasRef + include Ci::HasRef include ShaAttribute include FromUnion include UpdatedAtFilterable diff --git a/app/models/concerns/ci/has_ref.rb b/app/models/concerns/ci/has_ref.rb new file mode 100644 index 00000000000..cf57ff47743 --- /dev/null +++ b/app/models/concerns/ci/has_ref.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +## +# We will disable `ref` and `sha` attributes in `Ci::Build` in the future +# and remove this module in favor of Ci::PipelineDelegator. +module Ci + module HasRef + extend ActiveSupport::Concern + + def branch? + !tag? && !merge_request? + end + + def git_ref + if branch? + Gitlab::Git::BRANCH_REF_PREFIX + ref.to_s + elsif tag? + Gitlab::Git::TAG_REF_PREFIX + ref.to_s + end + end + + # A slugified version of the build ref, suitable for inclusion in URLs and + # domain names. Rules: + # + # * Lowercased + # * Anything not matching [a-z0-9-] is replaced with a - + # * Maximum length is 63 bytes + # * First/Last Character is not a hyphen + def ref_slug + Gitlab::Utils.slugify(ref.to_s) + end + end +end diff --git a/app/models/concerns/has_ref.rb b/app/models/concerns/has_ref.rb deleted file mode 100644 index 22e5955984d..00000000000 --- a/app/models/concerns/has_ref.rb +++ /dev/null @@ -1,31 +0,0 @@ -# frozen_string_literal: true - -## -# We will disable `ref` and `sha` attributes in `Ci::Build` in the future -# and remove this module in favor of Ci::PipelineDelegator. -module HasRef - extend ActiveSupport::Concern - - def branch? - !tag? && !merge_request? - end - - def git_ref - if branch? - Gitlab::Git::BRANCH_REF_PREFIX + ref.to_s - elsif tag? - Gitlab::Git::TAG_REF_PREFIX + ref.to_s - end - end - - # A slugified version of the build ref, suitable for inclusion in URLs and - # domain names. Rules: - # - # * Lowercased - # * Anything not matching [a-z0-9-] is replaced with a - - # * Maximum length is 63 bytes - # * First/Last Character is not a hyphen - def ref_slug - Gitlab::Utils.slugify(ref.to_s) - end -end diff --git a/app/serializers/group_variable_entity.rb b/app/serializers/group_variable_entity.rb index 19c5fa26f34..622106458c3 100644 --- a/app/serializers/group_variable_entity.rb +++ b/app/serializers/group_variable_entity.rb @@ -4,6 +4,7 @@ class GroupVariableEntity < Grape::Entity expose :id expose :key expose :value + expose :variable_type expose :protected?, as: :protected expose :masked?, as: :masked diff --git a/app/views/admin/application_settings/_ci_cd.html.haml b/app/views/admin/application_settings/_ci_cd.html.haml index cb9f992bb1d..c7918881bdf 100644 --- a/app/views/admin/application_settings/_ci_cd.html.haml +++ b/app/views/admin/application_settings/_ci_cd.html.haml @@ -58,6 +58,6 @@ = f.text_field :default_ci_config_path, class: 'form-control', placeholder: '.gitlab-ci.yml' %p.form-text.text-muted = _("The default CI configuration path for new projects.").html_safe - = link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'custom-ci-configuration-path'), target: '_blank' + = link_to icon('question-circle'), help_page_path('ci/pipelines/settings', anchor: 'custom-ci-configuration-path'), target: '_blank' = f.submit _('Save changes'), class: "btn btn-success" diff --git a/app/views/projects/settings/ci_cd/_form.html.haml b/app/views/projects/settings/ci_cd/_form.html.haml index e6be364b48f..54e46501287 100644 --- a/app/views/projects/settings/ci_cd/_form.html.haml +++ b/app/views/projects/settings/ci_cd/_form.html.haml @@ -8,7 +8,7 @@ = _("Git strategy for pipelines") %p = _("Choose between <code>clone</code> or <code>fetch</code> to get the recent application code").html_safe - = link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'git-strategy'), target: '_blank' + = link_to icon('question-circle'), help_page_path('ci/pipelines/settings', anchor: 'git-strategy'), target: '_blank' .form-check = f.radio_button :build_allow_git_fetch, 'false', { class: 'form-check-input' } = f.label :build_allow_git_fetch_false, class: 'form-check-label' do @@ -38,7 +38,7 @@ = f.text_field :build_timeout_human_readable, class: 'form-control' %p.form-text.text-muted = _('If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like "1 hour". Values without specification represent seconds.') - = link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'timeout'), target: '_blank' + = link_to icon('question-circle'), help_page_path('ci/pipelines/settings', anchor: 'timeout'), target: '_blank' - if can?(current_user, :update_max_artifacts_size, @project) %hr @@ -55,7 +55,7 @@ = f.text_field :ci_config_path, class: 'form-control', placeholder: '.gitlab-ci.yml' %p.form-text.text-muted = _("The path to the CI configuration file. Defaults to <code>.gitlab-ci.yml</code>").html_safe - = link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'custom-ci-configuration-path'), target: '_blank' + = link_to icon('question-circle'), help_page_path('ci/pipelines/settings', anchor: 'custom-ci-configuration-path'), target: '_blank' %hr .form-group @@ -65,7 +65,7 @@ %strong= _("Public pipelines") .form-text.text-muted = _("Allow public access to pipelines and job details, including output logs and artifacts") - = link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'visibility-of-pipelines'), target: '_blank' + = link_to icon('question-circle'), help_page_path('ci/pipelines/settings', anchor: 'visibility-of-pipelines'), target: '_blank' .bs-callout.bs-callout-info %p #{_("If enabled")}: %ul @@ -86,7 +86,7 @@ %strong= _("Auto-cancel redundant, pending pipelines") .form-text.text-muted = _("New pipelines will cancel older, pending pipelines on the same branch") - = link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'auto-cancel-pending-pipelines'), target: '_blank' + = link_to icon('question-circle'), help_page_path('ci/pipelines/settings', anchor: 'auto-cancel-pending-pipelines'), target: '_blank' .form-group .form-check @@ -95,7 +95,7 @@ %strong= _("Skip older, pending deployment jobs") .form-text.text-muted = _("When a deployment job is successful, skip older deployment jobs that are still pending") - = link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'skip-older-pending-deployment-jobs'), target: '_blank' + = link_to icon('question-circle'), help_page_path('ci/pipelines/settings', anchor: 'skip-older-pending-deployment-jobs'), target: '_blank' %hr .form-group @@ -108,7 +108,7 @@ .input-group-text / %p.form-text.text-muted = _("A regular expression that will be used to find the test coverage output in the job log. Leave blank to disable") - = link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'test-coverage-parsing'), target: '_blank' + = link_to icon('question-circle'), help_page_path('ci/pipelines/settings', anchor: 'test-coverage-parsing'), target: '_blank' .bs-callout.bs-callout-info %p= _("Below are examples of regex for existing tools:") %ul |