Skip to content
Snippets Groups Projects
Commit c36152ff authored by GitLab Bot's avatar GitLab Bot
Browse files

Add latest changes from gitlab-org/gitlab@master

parent 286fe610
No related branches found
No related tags found
No related merge requests found
Showing
with 349 additions and 110 deletions
Loading
Loading
@@ -26,7 +26,6 @@ export function getBoardSortableDefaultOptions(obj) {
scrollSpeed: 20,
onStart: sortableStart,
onEnd: sortableEnd,
fallbackTolerance: 1,
});
 
Object.keys(obj).forEach(key => {
Loading
Loading
Loading
Loading
@@ -7,6 +7,7 @@ import {
GlFormSelect,
GlFormGroup,
GlFormInput,
GlFormTextarea,
GlFormCheckbox,
GlLink,
GlIcon,
Loading
Loading
@@ -19,6 +20,7 @@ export default {
GlFormSelect,
GlFormGroup,
GlFormInput,
GlFormTextarea,
GlFormCheckbox,
GlLink,
GlIcon,
Loading
Loading
@@ -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 {
Loading
Loading
@@ -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([
Loading
Loading
@@ -65,6 +91,7 @@ export default {
'resetEditing',
'displayInputValue',
'clearModal',
'deleteVariable',
]),
updateOrAddVariable() {
if (this.variableBeingEdited) {
Loading
Loading
@@ -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>
Loading
Loading
<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>
Loading
Loading
@@ -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,
Loading
Loading
@@ -64,7 +78,7 @@ export default {
this.fetchVariables();
},
methods: {
...mapActions(['fetchVariables', 'deleteVariable', 'toggleValues', 'editVariable']),
...mapActions(['fetchVariables', 'toggleValues', 'editVariable']),
},
};
</script>
Loading
Loading
@@ -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"
Loading
Loading
// 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: '*',
};
import * as types from './mutation_types';
import { __ } from '~/locale';
import { displayText } from '../constants';
 
export default {
[types.REQUEST_VARIABLES](state) {
Loading
Loading
@@ -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) {
Loading
Loading
@@ -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,
};
},
 
Loading
Loading
import { __ } from '~/locale';
import { displayText } from '../constants';
 
export default () => ({
endpoint: null,
Loading
Loading
@@ -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,
});
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);
});
Loading
Loading
@@ -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) {
Loading
Loading
Loading
Loading
@@ -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: [
{
Loading
Loading
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,
Loading
Loading
@@ -25,7 +25,7 @@ export const parsedData = state => {
 
return {
total,
byAuthor,
byAuthorEmail,
};
};
 
Loading
Loading
Loading
Loading
@@ -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;
Loading
Loading
@@ -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;
Loading
Loading
mutation ($projectPath: ID!, $iid: String!, $healthStatus: HealthStatus) {
updateIssue(input: { projectPath: $projectPath, iid: $iid, healthStatus: $healthStatus}) {
issue {
healthStatus
}
}
}
Loading
Loading
@@ -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;
}
Loading
Loading
@@ -37,7 +37,7 @@ export default class SidebarService {
: sidebarDetailsQuery,
variables: {
fullPath: this.fullPath,
iid: this.id.toString(),
iid: this.iid.toString(),
},
}),
]);
Loading
Loading
@@ -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: {
Loading
Loading
Loading
Loading
@@ -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;
}
Loading
Loading
Loading
Loading
@@ -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;
}
}
}
Loading
Loading
@@ -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
Loading
Loading
Loading
Loading
@@ -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)
Loading
Loading
Loading
Loading
@@ -10,7 +10,7 @@ module Ci
include ObjectStorage::BackgroundMove
include Presentable
include Importable
include HasRef
include Ci::HasRef
include IgnorableColumns
 
BuildArchivedError = Class.new(StandardError)
Loading
Loading
Loading
Loading
@@ -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
Loading
Loading
# 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
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment