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

Add latest changes from gitlab-org/gitlab@master

parent 02211168
No related branches found
No related tags found
No related merge requests found
Showing
with 279 additions and 314 deletions
<script>
import { initEditorLite } from '~/blob/utils';
export default {
props: {
value: {
type: String,
required: true,
},
fileName: {
type: String,
required: false,
default: '',
},
},
data() {
return {
content: this.value,
editor: null,
};
},
watch: {
fileName(newVal) {
this.editor.updateModelLanguage(newVal);
},
},
mounted() {
this.editor = initEditorLite({
el: this.$refs.editor,
blobPath: this.fileName,
blobContent: this.content,
});
},
methods: {
triggerFileChange() {
const val = this.editor.getValue();
this.content = val;
this.$emit('input', val);
},
},
};
</script>
<template>
<div class="file-content code">
<pre id="editor" ref="editor" data-editor-loading @focusout="triggerFileChange">{{
content
}}</pre>
</div>
</template>
/* global ace */
import Editor from '~/editor/editor_lite';
export function initEditorLite({ el, blobPath, blobContent }) {
if (!el) {
throw new Error(`"el" parameter is required to initialize Editor`);
}
let editor;
if (window?.gon?.features?.monacoSnippets) {
editor = new Editor();
editor.createInstance({
el,
blobPath,
blobContent,
});
} else {
editor = ace.edit(el);
}
return editor;
}
export default () => ({});
<script>
import _ from 'underscore';
import { mapState } from 'vuex';
import { GlTooltipDirective } from '@gitlab/ui';
import { GlLabel, GlTooltipDirective } from '@gitlab/ui';
import issueCardInner from 'ee_else_ce/boards/mixins/issue_card_inner';
import { sprintf, __ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
Loading
Loading
@@ -10,18 +10,17 @@ import UserAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_
import IssueDueDate from './issue_due_date.vue';
import IssueTimeEstimate from './issue_time_estimate.vue';
import boardsStore from '../stores/boards_store';
import IssueCardInnerScopedLabel from './issue_card_inner_scoped_label.vue';
import { isScopedLabel } from '~/lib/utils/common_utils';
 
export default {
components: {
GlLabel,
Icon,
UserAvatarLink,
TooltipOnTruncate,
IssueDueDate,
IssueTimeEstimate,
IssueCardWeight: () => import('ee_component/boards/components/issue_card_weight.vue'),
IssueCardInnerScopedLabel,
},
directives: {
GlTooltip: GlTooltipDirective,
Loading
Loading
@@ -145,12 +144,6 @@ export default {
 
boardsStore.toggleFilter(filter);
},
labelStyle(label) {
return {
backgroundColor: label.color,
color: label.textColor,
};
},
showScopedLabel(label) {
return boardsStore.scopedLabels.enabled && isScopedLabel(label);
},
Loading
Loading
@@ -184,27 +177,16 @@ export default {
</div>
<div v-if="showLabelFooter" class="board-card-labels prepend-top-4 d-flex flex-wrap">
<template v-for="label in orderedLabels">
<issue-card-inner-scoped-label
v-if="showScopedLabel(label)"
<gl-label
:key="label.id"
:label="label"
:label-style="labelStyle(label)"
:background-color="label.color"
:title="label.title"
:description="label.description"
size="sm"
:scoped="showScopedLabel(label)"
:scoped-labels-documentation-link="helpLink"
@scoped-label-click="filterByLabel($event)"
/>
<button
v-else
:key="label.id"
v-gl-tooltip
:style="labelStyle(label)"
:title="label.description"
class="badge color-label append-right-4 prepend-top-4"
type="button"
@click="filterByLabel(label)"
>
{{ label.title }}
</button>
/>
</template>
</div>
<div class="board-card-footer d-flex justify-content-between align-items-end">
Loading
Loading
<script>
import { GlLink, GlTooltip } from '@gitlab/ui';
export default {
components: {
GlTooltip,
GlLink,
},
props: {
label: {
type: Object,
required: true,
},
labelStyle: {
type: Object,
required: true,
},
scopedLabelsDocumentationLink: {
type: String,
required: true,
},
},
};
</script>
<template>
<span
class="d-inline-block position-relative scoped-label-wrapper append-right-4 prepend-top-4 board-label"
>
<a @click="$emit('scoped-label-click', label)">
<span :ref="'labelTitleRef'" :style="labelStyle" class="badge label color-label">
{{ label.title }}
</span>
<gl-tooltip :target="() => $refs.labelTitleRef" placement="top" boundary="viewport">
<span class="font-weight-bold scoped-label-tooltip-title">{{ __('Scoped label') }}</span
><br />
{{ label.description }}
</gl-tooltip>
</a>
<gl-link :href="scopedLabelsDocumentationLink" target="_blank" class="label scoped-label"
><i class="fa fa-question-circle" :style="labelStyle"></i
></gl-link>
</span>
</template>
<script>
import { mapState, mapActions } from 'vuex';
import { GlTable, GlLoadingIcon, GlBadge } from '@gitlab/ui';
import { CLUSTER_TYPES } from '../constants';
import { __ } from '~/locale';
import tooltip from '~/vue_shared/directives/tooltip';
import { CLUSTER_TYPES, STATUSES } from '../constants';
import { __, sprintf } from '~/locale';
 
export default {
components: {
Loading
Loading
@@ -10,6 +11,9 @@ export default {
GlLoadingIcon,
GlBadge,
},
directives: {
tooltip,
},
fields: [
{
key: 'name',
Loading
Loading
@@ -38,6 +42,13 @@ export default {
},
methods: {
...mapActions(['fetchClusters']),
statusClass(status) {
return STATUSES[status].className;
},
statusTitle(status) {
const { title } = STATUSES[status];
return sprintf(__('Status: %{title}'), { title }, false);
},
},
};
</script>
Loading
Loading
@@ -52,6 +63,25 @@ export default {
variant="light"
class="qa-clusters-table"
>
<template #cell(name)="{ item }">
<div class="d-flex flex-row-reverse flex-md-row js-status">
{{ item.name }}
<gl-loading-icon
v-if="item.status === 'deleting'"
v-tooltip
:title="statusTitle(item.status)"
size="sm"
class="mr-2 ml-md-2"
/>
<div
v-else
v-tooltip
class="cluster-status-indicator rounded-circle align-self-center gl-w-8 gl-h-8 mr-2 ml-md-2"
:class="statusClass(item.status)"
:title="statusTitle(item.status)"
></div>
</div>
</template>
<template #cell(clusterType)="{value}">
<gl-badge variant="light">
{{ value }}
Loading
Loading
Loading
Loading
@@ -6,6 +6,10 @@ export const CLUSTER_TYPES = {
instance_type: __('Instance'),
};
 
export default {
CLUSTER_TYPES,
export const STATUSES = {
disabled: { className: 'disabled', title: __('Disabled') },
connected: { className: 'bg-success', title: __('Connected') },
unreachable: { className: 'bg-danger', title: __('Unreachable') },
authentication_failure: { className: 'bg-warning', title: __('Authentication Failure') },
deleting: { title: __('Deleting') },
};
export default (initialState = {}) => ({
endpoint: initialState.endpoint,
loading: false, // TODO - set this to true once integrated with BE
clusters: [
// TODO - remove mock data once integrated with BE
// {
// name: 'My Cluster',
// environmentScope: '*',
// size: '3',
// clusterType: 'group_type',
// },
// {
// name: 'My other cluster',
// environmentScope: 'production',
// size: '12',
// clusterType: 'project_type',
// },
],
clusters: [],
});
/* global ace */
import Editor from '~/editor/editor_lite';
import { initEditorLite } from '~/blob/utils';
import setupCollapsibleInputs from './collapsible_input';
 
let editor;
 
const initAce = () => {
editor = ace.edit('editor');
const editorEl = document.getElementById('editor');
const form = document.querySelector('.snippet-form-holder form');
const content = document.querySelector('.snippet-file-content');
editor = initEditorLite({ el: editorEl });
form.addEventListener('submit', () => {
content.value = editor.getValue();
});
Loading
Loading
@@ -20,8 +21,7 @@ const initMonaco = () => {
const fileNameEl = document.querySelector('.js-snippet-file-name');
const form = document.querySelector('.snippet-form-holder form');
 
editor = new Editor();
editor.createInstance({
editor = initEditorLite({
el: editorEl,
blobPath: fileNameEl.value,
blobContent: contentEl.value,
Loading
Loading
<script>
import BlobHeaderEdit from '~/blob/components/blob_edit_header.vue';
import BlobContentEdit from '~/blob/components/blob_edit_content.vue';
export default {
components: {
BlobHeaderEdit,
BlobContentEdit,
},
props: {
content: {
type: String,
required: true,
},
fileName: {
type: String,
required: true,
},
},
data() {
return {
name: this.fileName,
blobContent: this.content,
};
},
};
</script>
<template>
<div class="form-group file-editor">
<label>{{ s__('Snippets|File') }}</label>
<div class="file-holder snippet">
<blob-header-edit v-model="name" />
<blob-content-edit v-model="blobContent" :file-name="name" />
</div>
</div>
</template>
Loading
Loading
@@ -111,4 +111,6 @@ export function initUserTracking() {
if (opts.linkClickTracking) window.snowplow('enableLinkClickTracking');
 
Tracking.bindDocument();
document.dispatchEvent(new Event('SnowplowInitialized'));
}
<script>
import DropdownValueScopedLabel from './dropdown_value_scoped_label.vue';
import DropdownValueRegularLabel from './dropdown_value_regular_label.vue';
import { GlLabel } from '@gitlab/ui';
import { isScopedLabel } from '~/lib/utils/common_utils';
 
export default {
components: {
DropdownValueScopedLabel,
DropdownValueRegularLabel,
GlLabel,
},
props: {
labels: {
Loading
Loading
@@ -37,12 +35,6 @@ export default {
labelFilterUrl(label) {
return `${this.labelFilterBasePath}?label_name[]=${encodeURIComponent(label.title)}`;
},
labelStyle(label) {
return {
color: label.textColor,
backgroundColor: label.color,
};
},
scopedLabelsDescription({ description = '' }) {
return `<span class="font-weight-bold scoped-label-tooltip-title">Scoped label</span><br />${description}`;
},
Loading
Loading
@@ -65,22 +57,15 @@ export default {
</span>
 
<template v-for="label in labels" v-else>
<dropdown-value-scoped-label
v-if="showScopedLabels(label)"
<gl-label
:key="label.id"
:label="label"
:label-filter-url="labelFilterUrl(label)"
:label-style="labelStyle(label)"
:target="labelFilterUrl(label)"
:background-color="label.color"
:title="label.title"
:description="label.description"
:scoped="showScopedLabels(label)"
:scoped-labels-documentation-link="scopedLabelsDocumentationLink"
/>
<dropdown-value-regular-label
v-else
:key="label.id"
:label="label"
:label-filter-url="labelFilterUrl(label)"
:label-style="labelStyle(label)"
/>
</template>
</div>
</template>
<script>
import { GlTooltip } from '@gitlab/ui';
export default {
components: {
GlTooltip,
},
props: {
label: {
type: Object,
required: true,
},
labelStyle: {
type: Object,
required: true,
},
labelFilterUrl: {
type: String,
required: true,
},
},
};
</script>
<template>
<a ref="regularLabelRef" :href="labelFilterUrl">
<span :style="labelStyle" class="badge color-label">
{{ label.title }}
</span>
<gl-tooltip
v-if="label.description"
:target="() => $refs.regularLabelRef"
placement="top"
boundary="viewport"
>
{{ label.description }}
</gl-tooltip>
</a>
</template>
<script>
import { GlLink, GlTooltip } from '@gitlab/ui';
export default {
components: {
GlTooltip,
GlLink,
},
props: {
label: {
type: Object,
required: true,
},
labelStyle: {
type: Object,
required: true,
},
scopedLabelsDocumentationLink: {
type: String,
required: true,
},
labelFilterUrl: {
type: String,
required: true,
},
},
};
</script>
<template>
<span class="d-inline-block position-relative scoped-label-wrapper">
<a :href="labelFilterUrl">
<span :ref="`labelTitleRef`" :style="labelStyle" class="badge color-label label">
{{ label.title }}
</span>
<gl-tooltip
v-if="label.description"
:target="() => $refs.labelTitleRef"
placement="top"
boundary="viewport"
>
<span class="font-weight-bold scoped-label-tooltip-title">{{ __('Scoped label') }}</span
><br />
{{ label.description }}
</gl-tooltip>
</a>
<gl-link :href="scopedLabelsDocumentationLink" target="_blank" class="label scoped-label"
><i class="fa fa-question-circle" :style="labelStyle"></i
></gl-link>
</span>
</template>
Loading
Loading
@@ -266,20 +266,9 @@
background-color: $blue-50;
}
 
.badge {
border: 0;
outline: 0;
&:hover {
text-decoration: underline;
}
@include media-breakpoint-down(lg) {
font-size: $gl-font-size-xs;
padding-left: $gl-padding-4;
padding-right: $gl-padding-4;
font-weight: $gl-font-weight-bold;
}
.gl-label {
margin-top: 4px;
margin-right: 4px;
}
 
.confidential-icon {
Loading
Loading
Loading
Loading
@@ -163,3 +163,9 @@
color: $black;
font-weight: $gl-font-weight-bold;
}
.cluster-status-indicator {
&.disabled {
background-color: $gray-600;
}
}
Loading
Loading
@@ -158,6 +158,10 @@
a:not(.btn) {
color: inherit;
 
.gl-label-text:hover {
color: inherit;
}
&:hover {
color: $blue-800;
 
Loading
Loading
Loading
Loading
@@ -54,8 +54,10 @@
.mh-50vh { max-height: 50vh; }
 
.font-size-inherit { font-size: inherit; }
.gl-w-8 { width: px-to-rem($grid-size); }
.gl-w-16 { width: px-to-rem($grid-size * 2); }
.gl-w-64 { width: px-to-rem($grid-size * 8); }
.gl-h-8 { height: px-to-rem($grid-size); }
.gl-h-32 { height: px-to-rem($grid-size * 4); }
.gl-h-64 { height: px-to-rem($grid-size * 8); }
 
Loading
Loading
# frozen_string_literal: true
module Ci
# A state object to centralize logic related to merge request pipelines
class PipelinesForMergeRequestFinder
include Gitlab::Utils::StrongMemoize
EVENT = 'merge_request_event'
def initialize(merge_request)
@merge_request = merge_request
end
attr_reader :merge_request
delegate :commit_shas, :source_project, :source_branch, to: :merge_request
def all
strong_memoize(:all_pipelines) do
next Ci::Pipeline.none unless source_project
pipelines =
if merge_request.persisted?
pipelines_using_cte
else
triggered_for_branch.for_sha(commit_shas)
end
sort(pipelines)
end
end
private
def pipelines_using_cte
cte = Gitlab::SQL::CTE.new(:shas, merge_request.all_commits.select(:sha))
source_pipelines_join = cte.table[:sha].eq(Ci::Pipeline.arel_table[:source_sha])
source_pipelines = filter_by(triggered_by_merge_request, cte, source_pipelines_join)
detached_pipelines = filter_by_sha(triggered_by_merge_request, cte)
pipelines_for_branch = filter_by_sha(triggered_for_branch, cte)
Ci::Pipeline.with(cte.to_arel) # rubocop: disable CodeReuse/ActiveRecord
.from_union([source_pipelines, detached_pipelines, pipelines_for_branch])
end
def filter_by_sha(pipelines, cte)
hex = Arel::Nodes::SqlLiteral.new("'hex'")
string_sha = Arel::Nodes::NamedFunction.new('encode', [cte.table[:sha], hex])
join_condition = string_sha.eq(Ci::Pipeline.arel_table[:sha])
filter_by(pipelines, cte, join_condition)
end
def filter_by(pipelines, cte, join_condition)
shas_table =
Ci::Pipeline.arel_table
.join(cte.table, Arel::Nodes::InnerJoin)
.on(join_condition)
.join_sources
pipelines.joins(shas_table) # rubocop: disable CodeReuse/ActiveRecord
end
# NOTE: this method returns only parent merge request pipelines.
# Child merge request pipelines have a different source.
def triggered_by_merge_request
source_project.ci_pipelines
.where(source: :merge_request_event, merge_request: merge_request) # rubocop: disable CodeReuse/ActiveRecord
end
def triggered_for_branch
source_project.ci_pipelines
.where(source: branch_pipeline_sources, ref: source_branch, tag: false) # rubocop: disable CodeReuse/ActiveRecord
end
def branch_pipeline_sources
strong_memoize(:branch_pipeline_sources) do
Ci::Pipeline.sources.reject { |source| source == EVENT }.values
end
end
def sort(pipelines)
sql = 'CASE ci_pipelines.source WHEN (?) THEN 0 ELSE 1 END, ci_pipelines.id DESC'
query = ApplicationRecord.send(:sanitize_sql_array, [sql, Ci::Pipeline.sources[:merge_request_event]]) # rubocop:disable GitlabSecurity/PublicSend
pipelines.order(Arel.sql(query)) # rubocop: disable CodeReuse/ActiveRecord
end
end
end
Loading
Loading
@@ -1251,7 +1251,7 @@ class MergeRequest < ApplicationRecord
 
def all_pipelines
strong_memoize(:all_pipelines) do
MergeRequest::Pipelines.new(self).all
Ci::PipelinesForMergeRequestFinder.new(self).all
end
end
 
Loading
Loading
# frozen_string_literal: true
# A state object to centralize logic related to merge request pipelines
class MergeRequest::Pipelines
include Gitlab::Utils::StrongMemoize
EVENT = 'merge_request_event'
def initialize(merge_request)
@merge_request = merge_request
end
attr_reader :merge_request
delegate :commit_shas, :source_project, :source_branch, to: :merge_request
def all
strong_memoize(:all_pipelines) do
next Ci::Pipeline.none unless source_project
pipelines =
if merge_request.persisted?
pipelines_using_cte
else
triggered_for_branch.for_sha(commit_shas)
end
sort(pipelines)
end
end
private
def pipelines_using_cte
cte = Gitlab::SQL::CTE.new(:shas, merge_request.all_commits.select(:sha))
source_pipelines_join = cte.table[:sha].eq(Ci::Pipeline.arel_table[:source_sha])
source_pipelines = filter_by(triggered_by_merge_request, cte, source_pipelines_join)
detached_pipelines = filter_by_sha(triggered_by_merge_request, cte)
pipelines_for_branch = filter_by_sha(triggered_for_branch, cte)
Ci::Pipeline.with(cte.to_arel)
.from_union([source_pipelines, detached_pipelines, pipelines_for_branch])
end
def filter_by_sha(pipelines, cte)
hex = Arel::Nodes::SqlLiteral.new("'hex'")
string_sha = Arel::Nodes::NamedFunction.new('encode', [cte.table[:sha], hex])
join_condition = string_sha.eq(Ci::Pipeline.arel_table[:sha])
filter_by(pipelines, cte, join_condition)
end
def filter_by(pipelines, cte, join_condition)
shas_table =
Ci::Pipeline.arel_table
.join(cte.table, Arel::Nodes::InnerJoin)
.on(join_condition)
.join_sources
pipelines.joins(shas_table)
end
# NOTE: this method returns only parent merge request pipelines.
# Child merge request pipelines have a different source.
def triggered_by_merge_request
source_project.ci_pipelines
.where(source: :merge_request_event, merge_request: merge_request)
end
def triggered_for_branch
source_project.ci_pipelines
.where(source: branch_pipeline_sources, ref: source_branch, tag: false)
end
def branch_pipeline_sources
strong_memoize(:branch_pipeline_sources) do
Ci::Pipeline.sources.reject { |source| source == EVENT }.values
end
end
def sort(pipelines)
sql = 'CASE ci_pipelines.source WHEN (?) THEN 0 ELSE 1 END, ci_pipelines.id DESC'
query = ApplicationRecord.send(:sanitize_sql_array, [sql, Ci::Pipeline.sources[:merge_request_event]]) # rubocop:disable GitlabSecurity/PublicSend
pipelines.order(Arel.sql(query))
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