Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-03-13 18:09:21 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-03-13 18:09:21 +0300
commitc36152ff8c41fad2f413f253eb7ac5c927e47c56 (patch)
treebbf300da207de3e8bbf272d44111ceedb18f5833 /app
parent286fe61013674fe2d245ffc8d2233baf09923e70 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/boards/mixins/sortable_default_options.js1
-rw-r--r--app/assets/javascripts/ci_variable_list/components/ci_variable_modal.vue118
-rw-r--r--app/assets/javascripts/ci_variable_list/components/ci_variable_popover.vue55
-rw-r--r--app/assets/javascripts/ci_variable_list/components/ci_variable_table.vue104
-rw-r--r--app/assets/javascripts/ci_variable_list/constants.js16
-rw-r--r--app/assets/javascripts/ci_variable_list/store/mutations.js8
-rw-r--r--app/assets/javascripts/ci_variable_list/store/state.js8
-rw-r--r--app/assets/javascripts/ci_variable_list/store/utils.js24
-rw-r--r--app/assets/javascripts/contributors/components/contributors.vue10
-rw-r--r--app/assets/javascripts/contributors/stores/getters.js10
-rw-r--r--app/assets/javascripts/reports/store/utils.js13
-rw-r--r--app/assets/javascripts/sidebar/queries/updateStatus.mutation.graphql7
-rw-r--r--app/assets/javascripts/sidebar/services/sidebar_service.js15
-rw-r--r--app/assets/javascripts/sidebar/sidebar_mediator.js2
-rw-r--r--app/assets/stylesheets/pages/settings.scss27
-rw-r--r--app/helpers/issuables_helper.rb2
-rw-r--r--app/models/ci/bridge.rb2
-rw-r--r--app/models/ci/build.rb2
-rw-r--r--app/models/ci/pipeline.rb2
-rw-r--r--app/models/concerns/ci/has_ref.rb33
-rw-r--r--app/models/concerns/has_ref.rb31
-rw-r--r--app/serializers/group_variable_entity.rb1
-rw-r--r--app/views/admin/application_settings/_ci_cd.html.haml2
-rw-r--r--app/views/projects/settings/ci_cd/_form.html.haml14
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