Move all EE specific frontend assets behind an EE flag
Similar to https://gitlab.com/gitlab-org/gitlab-ee/issues/8710, we need to gate all EE specific changes made to all frontend assets out of the CE code behind an EE flag.
@filipa created a proposal of how this can be accomplished for each of the frontend assets:
CSS
- All our CSS will be under the MIT license. Since CSS is only applied when the elemnents exist, I'd say it's safe to keep the current structure we have.
- The frontend team is working on moving CSS from sprockets to webpack, which eventually will allow us to do code splitting and only load the CSS we need per page.
JavaScript
Vue Files
Vue files are composed for a script
tag, which is plain JS and a template
tag.
We will create a global mixin
with a function called renderIfEE
.
script
tag
-
For child components only imported in EE
- We will use
async components
to import components that should be imported for EE code only.
components: { 'ee-component': () => this.renderIfEE && import('./ee-component.vue') }
- We will use
-
For JS code that is EE only, like props, computed properties, methods, etc, we will keep the current approach
- Since we can't async load a mixin we will use the
ee_else_ce
alias we already have for webpack. - This means all the EE specific props, computed properties, methods, etc that are EE only should be in a mixin in the
ee/
folder and we need to create a CE counterpart of the mixin
import mixin from 'ee_else_ce/path/mixin'; { mixins: [mixin] }
- Since we can't async load a mixin we will use the
- Computed Properties/methods and getters only used in the child import still need a counterpart in CE
- For store modules, we will need a CE counterpart too.
template
tag
-
EE Child components
- Since we are using the async loading to check which component to load, we'd still use the component's name
<script> { components: { 'ee-component': () => this.renderIfEE ? import('./ee-component.vue') : import('vue_shared/compoments/empty_ce_vue_component.vue') } } </script> <template> <div> <ee-component> </div> </template>
-
EE extra HTML
- For the templates that have extra HTML in EE we will use the
renderIfEE
mixin with thev-if
directive. - You can either use the
template
tag as a wrapper or directly in the element, if there is only one element to be rendered in EE:
<template v-if="renderIfEE"> <p>Several</p> <p>non wrapper</p> <p>elements</p> <p>that are rendered</p> <p>in EE only</p> </template>
<ul v-if="renderIfEE"> <li>One wrapped</li> <li>element</li> <li>that is rendered</li> <li>in EE only</li> </template>
- For the templates that have extra HTML in EE we will use the
Non Vue Files
For regular JS files, the approach is similar.
- We will keep using the
ee_else_ce
helper, this means that EE only code should be inside theee/
folder. - An EE file should be created with the EE only code, and it should extend the CE counterpart.
- We will create an helper function
renderIfEE
in our utils files. For code inside functions that can't be extended, we will use anif
statement.
example:
import renderIfEE from '~/lib/utils/common_utils'
if (renderIfEE) {
$('.js-import-git-toggle-button').on('click', () => {
const $projectMirror = $('#project_mirror');
$projectMirror.attr('disabled', !$projectMirror.attr('disabled'));
});
}
Differences
app/assets/javascripts/sidebar/components/todo_toggle/todo.vue
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/sidebar/components/todo_toggle/todo.vue b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/sidebar/components/todo_toggle/todo.vue
index 706e6ca19c3..57125c78cf6 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/sidebar/components/todo_toggle/todo.vue
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/sidebar/components/todo_toggle/todo.vue
@@ -50,6 +50,9 @@ export default {
buttonLabel() {
return this.isTodo ? MARK_TEXT : TODO_TEXT;
},
+ buttonTooltip() {
+ return !this.collapsed ? undefined : this.buttonLabel;
+ },
collapsedButtonIconClasses() {
return this.isTodo ? 'todo-undone' : '';
},
@@ -69,7 +72,7 @@ export default {
<button
v-tooltip
:class="buttonClasses"
- :title="buttonLabel"
+ :title="buttonTooltip"
:aria-label="buttonLabel"
:data-issuable-id="issuableId"
:data-issuable-type="issuableType"
app/assets/javascripts/projects/project_new.js
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/projects/project_new.js b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/projects/project_new.js
index 998554d1be5..0105a4d919e 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/projects/project_new.js
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/projects/project_new.js
@@ -161,6 +161,12 @@ const bindEvents = () => {
$projectImportUrl.keyup(() => deriveProjectPathFromUrl($projectImportUrl));
+ $('.js-import-git-toggle-button').on('click', () => {
+ const $projectMirror = $('#project_mirror');
+
+ $projectMirror.attr('disabled', !$projectMirror.attr('disabled'));
+ });
+
$projectName.on('keyup change', () => {
onProjectNameChange($projectName, $projectPath);
hasUserDefinedProjectPath = $projectPath.val().trim().length > 0;
app/assets/javascripts/monitoring/components/dashboard.vue
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/monitoring/components/dashboard.vue b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/monitoring/components/dashboard.vue
index cea5c1a56ca..9e2b84d2d1d 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/monitoring/components/dashboard.vue
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/monitoring/components/dashboard.vue
@@ -1,4 +1,7 @@
<script>
+// ee-only
+import DashboardMixin from 'ee/monitoring/components/dashboard_mixin';
+
import _ from 'underscore';
import { s__ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
@@ -19,6 +22,10 @@ export default {
EmptyState,
Icon,
},
+
+ // ee-only
+ mixins: [DashboardMixin],
+
props: {
hasMetrics: {
type: Boolean,
@@ -93,6 +100,11 @@ export default {
type: String,
required: true,
},
+ showEnvironmentDropdown: {
+ type: Boolean,
+ required: false,
+ default: true,
+ },
},
data() {
return {
@@ -131,9 +143,29 @@ export default {
},
mounted() {
this.resizeThrottled = _.debounce(this.resize, 100);
+ this.servicePromises = [
+ this.service
+ .getGraphsData()
+ .then(data => this.store.storeMetrics(data))
+ .catch(() => Flash(s__('Metrics|There was an error while retrieving metrics'))),
+ this.service
+ .getDeploymentData()
+ .then(data => this.store.storeDeploymentData(data))
+ .catch(() => Flash(s__('Metrics|There was an error getting deployment information.'))),
+ ];
if (!this.hasMetrics) {
this.state = 'gettingStarted';
} else {
+ if (this.showEnvironmentDropdown) {
+ this.servicePromises.push(
+ this.service
+ .getEnvironmentsData()
+ .then(data => this.store.storeEnvironmentsData(data))
+ .catch(() =>
+ Flash(s__('Metrics|There was an error getting environments information.')),
+ ),
+ );
+ }
this.getGraphsData();
window.addEventListener('resize', this.resizeThrottled, false);
@@ -149,17 +181,7 @@ export default {
},
getGraphsData() {
this.state = 'loading';
- Promise.all([
- this.service.getGraphsData().then(data => this.store.storeMetrics(data)),
- this.service
- .getDeploymentData()
- .then(data => this.store.storeDeploymentData(data))
- .catch(() => Flash(s__('Metrics|There was an error getting deployment information.'))),
- this.service
- .getEnvironmentsData()
- .then(data => this.store.storeEnvironmentsData(data))
- .catch(() => Flash(s__('Metrics|There was an error getting environments information.'))),
- ])
+ Promise.all(this.servicePromises)
.then(() => {
if (this.store.groups.length < 1) {
this.state = 'noData';
@@ -185,7 +207,7 @@ export default {
<template>
<div v-if="!showEmptyState" :key="forceRedraw" class="prometheus-graphs prepend-top-default">
- <div class="environments d-flex align-items-center">
+ <div v-if="showEnvironmentDropdown" class="environments d-flex align-items-center">
{{ s__('Metrics|Environment') }}
<div class="dropdown prepend-left-10">
<button class="dropdown-menu-toggle" data-toggle="dropdown" type="button">
@@ -230,7 +252,24 @@ export default {
group-id="monitor-area-chart"
>
<!-- EE content -->
- {{ null }}
+ <template slot="additionalSvgContent" scope="{ graphDrawData }">
+ <threshold-lines
+ v-for="(alert, alertName) in alertData[graphData.id]"
+ :key="alertName"
+ :operator="alert.operator"
+ :threshold="alert.threshold"
+ :graph-draw-data="graphDrawData"
+ />
+ </template>
+ <alert-widget
+ v-if="alertsEndpoint && graphData.id"
+ :alerts-endpoint="alertsEndpoint"
+ :label="getGraphLabel(graphData)"
+ :current-alerts="getQueryAlerts(graphData)"
+ :custom-metric-id="graphData.id"
+ :alert-data="alertData[graphData.id]"
+ @setAlerts="setAlerts"
+ />
</component>
</graph-group>
</div>
app/assets/javascripts/notes/components/noteable_discussion.vue
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/notes/components/noteable_discussion.vue b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/notes/components/noteable_discussion.vue
index 695efe3602f..ce843a7bb4b 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/notes/components/noteable_discussion.vue
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/notes/components/noteable_discussion.vue
@@ -6,6 +6,8 @@ import { truncateSha } from '~/lib/utils/text_utility';
import { s__, __, sprintf } from '~/locale';
import systemNote from '~/vue_shared/components/notes/system_note.vue';
import icon from '~/vue_shared/components/icon.vue';
+import batchCommentsDiffLineNoteFormMixin from 'ee/batch_comments/mixins/diff_line_note_form';
+import DraftNote from 'ee/batch_comments/components/draft_note.vue';
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
import Flash from '../../flash';
import { SYSTEM_NOTE } from '../constants';
@@ -42,12 +44,19 @@ export default {
placeholderNote,
placeholderSystemNote,
systemNote,
+ DraftNote,
TimelineEntryItem,
},
directives: {
GlTooltip: GlTooltipDirective,
},
- mixins: [autosave, noteable, resolvable, discussionNavigation],
+ mixins: [
+ autosave,
+ noteable,
+ resolvable,
+ discussionNavigation,
+ batchCommentsDiffLineNoteFormMixin,
+ ],
props: {
discussion: {
type: Object,
@@ -85,6 +94,7 @@ export default {
return {
isReplying: false,
isResolving: false,
+ isUnresolving: false,
resolveAsThread: true,
isRepliesCollapsed: Boolean(!isDiffDiscussion && resolved),
};
@@ -440,8 +450,13 @@ Please check your network connection and try again.`;
</component>
</template>
</ul>
+ <draft-note
+ v-if="showDraft(discussion.reply_id)"
+ :key="`draft_${discussion.id}`"
+ :draft="draftForDiscussion(discussion.reply_id)"
+ />
<div
- v-if="!isRepliesCollapsed || !hasReplies"
+ v-else-if="!isRepliesCollapsed || !hasReplies"
:class="{ 'is-replying': isReplying }"
class="discussion-reply-holder"
>
@@ -490,6 +505,7 @@ Please check your network connection and try again.`;
:is-editing="false"
:line="diffLine"
save-button-title="Comment"
+ @handleFormUpdateAddToReview="addReplyToReview"
@handleFormUpdate="saveReply"
@cancelForm="cancelReplyForm"
/>
app/assets/javascripts/notes/components/note_actions.vue
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/notes/components/note_actions.vue b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/notes/components/note_actions.vue
index d99694b06e9..da78e4605d4 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/notes/components/note_actions.vue
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/notes/components/note_actions.vue
@@ -2,6 +2,7 @@
import { mapGetters } from 'vuex';
import Icon from '~/vue_shared/components/icon.vue';
import { GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui';
+import resolvedStatusMixin from 'ee/batch_comments/mixins/resolved_status';
export default {
name: 'NoteActions',
@@ -12,6 +13,7 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
+ mixins: [resolvedStatusMixin],
props: {
authorId: {
type: Number,
@@ -93,6 +95,8 @@ export default {
return this.getUserDataByProp('id');
},
resolveButtonTitle() {
+ if (this.discussionId) return this.resolvedStatusMessage;
+
let title = 'Mark as resolved';
if (this.resolvedBy) {
@@ -113,6 +117,7 @@ export default {
this.$emit('handleResolve');
},
},
+ showStaysResolved: true,
};
</script>
app/assets/javascripts/notes/components/note_body.vue
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/notes/components/note_body.vue b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/notes/components/note_body.vue
index bcf5d334da4..82b6cd35ec2 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/notes/components/note_body.vue
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/notes/components/note_body.vue
@@ -1,5 +1,5 @@
<script>
-import { mapActions } from 'vuex';
+import { mapGetters, mapActions } from 'vuex';
import $ from 'jquery';
import noteEditedText from './note_edited_text.vue';
import noteAwardsList from './note_awards_list.vue';
@@ -43,6 +43,7 @@ export default {
},
},
computed: {
+ ...mapGetters(['getDiscussion']),
noteBody() {
return this.note.note;
},
@@ -52,6 +53,11 @@ export default {
lineType() {
return this.line ? this.line.type : null;
},
+ discussion() {
+ if (!this.note.isDraft) return {};
+
+ return this.getDiscussion(this.note.discussion_id);
+ },
},
mounted() {
this.renderGFM();
@@ -76,8 +82,8 @@ export default {
renderGFM() {
$(this.$refs['note-body']).renderGFM();
},
- handleFormUpdate(note, parentElement, callback) {
- this.$emit('handleFormUpdate', note, parentElement, callback);
+ handleFormUpdate(note, parentElement, callback, resolveDiscussion) {
+ this.$emit('handleFormUpdate', note, parentElement, callback, resolveDiscussion);
},
formCancelHandler(shouldConfirm, isDirty) {
this.$emit('cancelForm', shouldConfirm, isDirty);
@@ -112,6 +118,8 @@ export default {
:note="note"
:help-page-path="helpPagePath"
:markdown-version="note.cached_markdown_version"
+ :discussion="discussion"
+ :resolve-discussion="note.resolve_discussion"
@handleFormUpdate="handleFormUpdate"
@cancelForm="formCancelHandler"
/>
app/assets/javascripts/notes/components/note_header.vue
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/notes/components/note_header.vue b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/notes/components/note_header.vue
index 7b39901024d..68b753a4abf 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/notes/components/note_header.vue
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/notes/components/note_header.vue
@@ -69,7 +69,7 @@ export default {
type="button"
@click="handleToggle"
>
- <i :class="toggleChevronClass" class="fa" aria-hidden="true"> </i>
+ <i :class="toggleChevronClass" class="fa" aria-hidden="true"></i>
{{ __('Toggle discussion') }}
</button>
</div>
@@ -81,19 +81,18 @@ export default {
:data-user-id="author.id"
:data-username="author.username"
>
+ <slot name="note-header-info"></slot>
<span class="note-header-author-name">{{ author.name }}</span>
<span v-if="author.status_tooltip_html" v-html="author.status_tooltip_html"></span>
- <span class="note-headline-light"> @{{ author.username }} </span>
+ <span class="note-headline-light">@{{ author.username }}</span>
</a>
- <span v-else> {{ __('A deleted user') }} </span>
+ <span v-else>{{ __('A deleted user') }}</span>
<span class="note-headline-light">
<span class="note-headline-meta">
<span class="system-note-message"> <slot></slot> </span>
<template v-if="createdAt">
<span class="system-note-separator">
- <template v-if="actionText">
- {{ actionText }}
- </template>
+ <template v-if="actionText">{{ actionText }}</template>
</span>
<a
:href="noteTimestampLink"
@@ -107,8 +106,7 @@ export default {
class="fa fa-spinner fa-spin editing-spinner"
aria-label="Comment is being updated"
aria-hidden="true"
- >
- </i>
+ ></i>
</span>
</span>
</div>
app/assets/javascripts/notes/components/note_form.vue
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/notes/components/note_form.vue b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/notes/components/note_form.vue
index 269b4a4b117..07cc7bf9c8b 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/notes/components/note_form.vue
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/notes/components/note_form.vue
@@ -7,6 +7,7 @@ import markdownField from '../../vue_shared/components/markdown/field.vue';
import issuableStateMixin from '../mixins/issuable_state';
import resolvable from '../mixins/resolvable';
import { __ } from '~/locale';
+import noteFormMixin from 'ee/batch_comments/mixins/note_form';
export default {
name: 'NoteForm',
@@ -14,7 +15,7 @@ export default {
issueWarning,
markdownField,
},
- mixins: [issuableStateMixin, resolvable],
+ mixins: [issuableStateMixin, resolvable, noteFormMixin],
props: {
noteBody: {
type: String,
@@ -151,19 +152,29 @@ export default {
return shouldResolve || shouldToggleState;
},
handleKeySubmit() {
- this.handleUpdate();
+ if (this.showBatchCommentsActions) {
+ this.handleAddToReview();
+ } else {
+ this.handleUpdate();
+ }
},
handleUpdate(shouldResolve) {
const beforeSubmitDiscussionState = this.discussionResolved;
this.isSubmitting = true;
- this.$emit('handleFormUpdate', this.updatedNoteBody, this.$refs.editNoteForm, () => {
- this.isSubmitting = false;
+ this.$emit(
+ 'handleFormUpdate',
+ this.updatedNoteBody,
+ this.$refs.editNoteForm,
+ () => {
+ this.isSubmitting = false;
- if (this.shouldToggleResolved(shouldResolve, beforeSubmitDiscussionState)) {
- this.resolveHandler(beforeSubmitDiscussionState);
- }
- });
+ if (this.shouldToggleResolved(shouldResolve, beforeSubmitDiscussionState)) {
+ this.resolveHandler(beforeSubmitDiscussionState);
+ }
+ },
+ this.discussionResolved ? !this.isUnresolving : this.isResolving,
+ );
},
editMyLastNote() {
if (this.updatedNoteBody === '') {
@@ -227,28 +238,72 @@ export default {
></textarea>
</markdown-field>
<div class="note-form-actions clearfix">
- <button
- :disabled="isDisabled"
- type="button"
- class="js-vue-issue-save btn btn-success js-comment-button qa-reply-comment-button"
- @click="handleUpdate()"
- >
- {{ saveButtonTitle }}
- </button>
- <button
- v-if="discussion.resolvable"
- class="btn btn-nr btn-default append-right-10 js-comment-resolve-button"
- @click.prevent="handleUpdate(true)"
- >
- {{ resolveButtonTitle }}
- </button>
- <button
- class="btn btn-cancel note-edit-cancel js-close-discussion-note-form"
- type="button"
- @click="cancelHandler()"
- >
- Cancel
- </button>
+ <p v-if="showResolveDiscussionToggle">
+ <label>
+ <template v-if="discussionResolved">
+ <input
+ v-model="isUnresolving"
+ type="checkbox"
+ class="qa-unresolve-review-discussion"
+ />
+ {{ __('Unresolve discussion') }}
+ </template>
+ <template v-else>
+ <input v-model="isResolving" type="checkbox" class="qa-resolve-review-discussion" />
+ {{ __('Resolve discussion') }}
+ </template>
+ </label>
+ </p>
+ <div v-if="showBatchCommentsActions">
+ <button
+ :disabled="isDisabled"
+ type="button"
+ class="btn btn-success qa-start-review"
+ @click="handleAddToReview"
+ >
+ <template v-if="hasDrafts">{{ __('Add to review') }}</template>
+ <template v-else>{{ __('Start a review') }}</template>
+ </button>
+ <button
+ :disabled="isDisabled"
+ type="button"
+ class="btn qa-comment-now"
+ @click="handleUpdate()"
+ >
+ {{ __('Add comment now') }}
+ </button>
+ <button
+ class="btn btn-cancel note-edit-cancel js-close-discussion-note-form"
+ type="button"
+ @click="cancelHandler()"
+ >
+ {{ __('Cancel') }}
+ </button>
+ </div>
+ <template v-else>
+ <button
+ :disabled="isDisabled"
+ type="button"
+ class="js-vue-issue-save btn btn-success js-comment-button qa-reply-comment-button"
+ @click="handleUpdate()"
+ >
+ {{ saveButtonTitle }}
+ </button>
+ <button
+ v-if="discussion.resolvable"
+ class="btn btn-nr btn-default append-right-10 js-comment-resolve-button"
+ @click.prevent="handleUpdate(true)"
+ >
+ {{ resolveButtonTitle }}
+ </button>
+ <button
+ class="btn btn-cancel note-edit-cancel js-close-discussion-note-form"
+ type="button"
+ @click="cancelHandler()"
+ >
+ Cancel
+ </button>
+ </template>
</div>
</form>
</div>
app/assets/javascripts/notes/components/noteable_note.vue
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/notes/components/noteable_note.vue b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/notes/components/noteable_note.vue
index 3c48d81ed05..8077d63f663 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/notes/components/noteable_note.vue
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/notes/components/noteable_note.vue
@@ -145,12 +145,16 @@ export default {
this.$refs.noteBody.resetAutoSave();
this.$emit('updateSuccess');
},
- formUpdateHandler(noteText, parentElement, callback) {
+ formUpdateHandler(noteText, parentElement, callback, resolveDiscussion) {
this.$emit('handleUpdateNote', {
note: this.note,
noteText,
+ resolveDiscussion,
callback: () => this.updateSuccess(),
});
+
+ if (this.note.isDraft) return;
+
const data = {
endpoint: this.note.path,
note: {
@@ -223,6 +227,7 @@ export default {
<div class="timeline-content">
<div class="note-header">
<note-header v-once :author="author" :created-at="note.created_at" :note-id="note.id">
+ <slot slot="note-header-info" name="note-header-info"></slot>
<span v-if="commit" v-html="actionText"></span>
<span v-else class="d-none d-sm-inline">·</span>
</note-header>
@@ -235,12 +240,16 @@ export default {
:can-award-emoji="note.current_user.can_award_emoji"
:can-delete="note.current_user.can_edit"
:can-report-as-abuse="canReportAsAbuse"
- :can-resolve="note.current_user.can_resolve"
+ :can-resolve="
+ note.current_user.can_resolve || (note.isDraft && note.discussion_id !== null)
+ "
:report-abuse-path="note.report_abuse_path"
- :resolvable="note.resolvable"
- :is-resolved="note.resolved"
+ :resolvable="note.resolvable || note.isDraft"
+ :is-resolved="note.resolved || note.resolve_discussion"
:is-resolving="isResolving"
:resolved-by="note.resolved_by"
+ :discussion-id="note.isDraft && note.discussion_id"
+ :resolve-discussion="note.isDraft && note.resolve_discussion"
@handleEdit="editHandler"
@handleDelete="deleteHandler"
@handleResolve="resolveHandler"
app/assets/javascripts/notes/stores/getters.js
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/notes/stores/getters.js b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/notes/stores/getters.js
index 0ffc0cb2593..52ce0ee5e5e 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/notes/stores/getters.js
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/notes/stores/getters.js
@@ -189,6 +189,9 @@ export const firstUnresolvedDiscussionId = (state, getters) => diffOrder => {
return getters.unresolvedDiscussionsIdsByDate[0];
};
+export const getDiscussion = state => discussionId =>
+ state.discussions.find(discussion => discussion.id === discussionId);
+
export const commentsDisabled = state => state.commentsDisabled;
// prevent babel-plugin-rewire from generating an invalid default during karma tests
app/assets/javascripts/notes/mixins/resolvable.js
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/notes/mixins/resolvable.js b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/notes/mixins/resolvable.js
index 8edf3d088bb..2329727bca2 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/notes/mixins/resolvable.js
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/notes/mixins/resolvable.js
@@ -31,6 +31,10 @@ export default {
},
methods: {
resolveHandler(resolvedState = false) {
+ if (this.note && this.note.isDraft) {
+ return this.$emit('toggleResolveStatus');
+ }
+
this.isResolving = true;
const isResolved = this.discussionResolved || resolvedState;
const discussion = this.resolveAsThread;
app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
index 57ec6603d80..de10bbc6401 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
@@ -1,5 +1,6 @@
import _ from 'underscore';
import DropLab from '~/droplab/drop_lab';
+import DropdownWeight from 'ee/filtered_search/dropdown_weight';
import FilteredSearchContainer from './container';
import FilteredSearchTokenKeys from './filtered_search_token_keys';
import DropdownUtils from './dropdown_utils';
@@ -106,6 +107,14 @@ export default class FilteredSearchDropdownManager {
gl: NullDropdown,
element: this.container.querySelector('#js-dropdown-admin-runner-type'),
},
+
+ // EE-only start
+ weight: {
+ reference: null,
+ gl: DropdownWeight,
+ element: this.container.querySelector('#js-dropdown-weight'),
+ },
+ // EE-only end
};
supportedTokens.forEach(type => {
@@ -118,7 +127,12 @@ export default class FilteredSearchDropdownManager {
}
getMilestoneEndpoint() {
- const endpoint = `${this.baseEndpoint}/milestones.json`;
+ let endpoint = `${this.baseEndpoint}/milestones.json`;
+
+ // EE-only
+ if (this.groupsOnly) {
+ endpoint = `${endpoint}?only_group_milestones=true`;
+ }
return endpoint;
}
app/assets/javascripts/filtered_search/dropdown_user.js
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/filtered_search/dropdown_user.js b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/filtered_search/dropdown_user.js
index d5027590bb7..a372e88a2e1 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/filtered_search/dropdown_user.js
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/filtered_search/dropdown_user.js
@@ -18,6 +18,7 @@ export default class DropdownUser extends FilteredSearchDropdown {
group_id: this.getGroupId(),
project_id: this.getProjectId(),
current_user: true,
+ ...this.projectOrGroupId(),
},
searchValueFunction: this.getSearchInput.bind(this),
loadingTemplate: this.loadingTemplate,
@@ -57,6 +58,19 @@ export default class DropdownUser extends FilteredSearchDropdown {
return this.input.getAttribute('data-project-id');
}
+ projectOrGroupId() {
+ const projectId = this.getProjectId();
+ const groupId = this.getGroupId();
+ if (groupId) {
+ return {
+ group_id: groupId,
+ };
+ }
+ return {
+ project_id: projectId,
+ };
+ }
+
getSearchInput() {
const query = DropdownUtils.getSearchInput(this.input);
const { lastToken } = FilteredSearchTokenizer.processTokens(query, this.tokenKeys.get());
app/assets/javascripts/filtered_search/filtered_search_manager.js
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/filtered_search/filtered_search_manager.js b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/filtered_search/filtered_search_manager.js
index 33c82778c79..a54b88b78df 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/filtered_search/filtered_search_manager.js
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/filtered_search/filtered_search_manager.js
@@ -41,6 +41,9 @@ export default class FilteredSearchManager {
merge_requests: 'merge-request-recent-searches',
};
+ // EE specific setup
+ this.initEE();
+
this.recentSearchesStore = new RecentSearchesStore({
isLocalStorageAvailable: RecentSearchesService.isAvailable(),
allowedKeys: this.filteredSearchTokenKeys.getKeys(),
@@ -55,6 +58,26 @@ export default class FilteredSearchManager {
this.recentSearchesService = new RecentSearchesService(recentSearchesKey);
}
+ /**
+ * Do EE specific initializations
+ */
+ initEE() {
+ // Setup token keys for multiple-assignees support
+ if (typeof this.filteredSearchTokenKeys.init === 'function') {
+ this.filteredSearchTokenKeys.init({
+ multipleAssignees: this.filteredSearchInput.dataset.multipleAssignees,
+ });
+ }
+
+ // Add localStorage key name for Epics recent searches
+ this.recentsStorageKeyNames.epics = 'epics-recent-searches';
+
+ // Update `isGroup` from DOM info
+ if (this.filteredSearchInput) {
+ this.isGroup = !!this.filteredSearchInput.getAttribute('data-group-id');
+ }
+ }
+
setup() {
// Fetch recent searches from localStorage
this.fetchingRecentSearchesPromise = this.recentSearchesService
app/assets/javascripts/gl_dropdown.js
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/gl_dropdown.js b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/gl_dropdown.js
index a8ac2f510a4..a4aa383b9fb 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/gl_dropdown.js
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/gl_dropdown.js
@@ -719,6 +719,10 @@ GitLabDropdown = (function() {
}
html = document.createElement('li');
+ if (rowHidden) {
+ html.style.display = 'none';
+ }
+
if (data === 'divider' || data === 'separator') {
html.className = data;
return html;
app/assets/javascripts/diffs/components/diff_line_note_form.vue
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/diffs/components/diff_line_note_form.vue b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/diffs/components/diff_line_note_form.vue
index 18edbe286ba..95fde0e83b9 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/diffs/components/diff_line_note_form.vue
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/diffs/components/diff_line_note_form.vue
@@ -1,6 +1,7 @@
<script>
import { mapState, mapGetters, mapActions } from 'vuex';
import { s__ } from '~/locale';
+import batchCommentsDiffLineNoteFormMixin from 'ee/batch_comments/mixins/diff_line_note_form';
import noteForm from '../../notes/components/note_form.vue';
import autosave from '../../notes/mixins/autosave';
import { DIFF_NOTE_TYPE } from '../constants';
@@ -9,7 +10,7 @@ export default {
components: {
noteForm,
},
- mixins: [autosave],
+ mixins: [autosave, batchCommentsDiffLineNoteFormMixin],
props: {
diffFileHash: {
type: String,
@@ -103,6 +104,7 @@ export default {
:help-page-path="helpPagePath"
save-button-title="Comment"
class="diff-comment-form"
+ @handleFormUpdateAddToReview="addToReview"
@cancelForm="handleCancelCommentForm"
@handleFormUpdate="handleSaveNote"
/>
app/assets/javascripts/diffs/components/inline_diff_view.vue
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/diffs/components/inline_diff_view.vue b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/diffs/components/inline_diff_view.vue
index e781397214d..af9746ffe87 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/diffs/components/inline_diff_view.vue
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/diffs/components/inline_diff_view.vue
@@ -3,10 +3,14 @@ import { mapGetters } from 'vuex';
import inlineDiffTableRow from './inline_diff_table_row.vue';
import inlineDiffCommentRow from './inline_diff_comment_row.vue';
+// eslint-disable-next-line import/order
+import InlineDraftCommentRow from 'ee/batch_comments/components/inline_draft_comment_row.vue';
+
export default {
components: {
inlineDiffCommentRow,
inlineDiffTableRow,
+ InlineDraftCommentRow,
},
props: {
diffFile: {
@@ -25,6 +29,7 @@ export default {
},
computed: {
...mapGetters('diffs', ['commitId']),
+ ...mapGetters('batchComments', ['shouldRenderDraftRow', 'draftForLine']),
diffLinesLength() {
return this.diffLines.length;
},
@@ -54,6 +59,11 @@ export default {
:line="line"
:help-page-path="helpPagePath"
/>
+ <inline-draft-comment-row
+ v-if="shouldRenderDraftRow(diffFile.file_hash, line)"
+ :key="`draft_${index}`"
+ :draft="draftForLine(diffFile.file_hash, line)"
+ />
</template>
</tbody>
</table>
app/assets/javascripts/diffs/components/parallel_diff_view.vue
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/diffs/components/parallel_diff_view.vue b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/diffs/components/parallel_diff_view.vue
index 1bf693380db..354e12746a8 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/diffs/components/parallel_diff_view.vue
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/diffs/components/parallel_diff_view.vue
@@ -3,10 +3,14 @@ import { mapGetters } from 'vuex';
import parallelDiffTableRow from './parallel_diff_table_row.vue';
import parallelDiffCommentRow from './parallel_diff_comment_row.vue';
+// eslint-disable-next-line import/order
+import ParallelDraftCommentRow from 'ee/batch_comments/components/parallel_draft_comment_row.vue';
+
export default {
components: {
parallelDiffTableRow,
parallelDiffCommentRow,
+ ParallelDraftCommentRow,
},
props: {
diffFile: {
@@ -25,6 +29,7 @@ export default {
},
computed: {
...mapGetters('diffs', ['commitId']),
+ ...mapGetters('batchComments', ['shouldRenderParallelDraftRow', 'draftForLine']),
diffLinesLength() {
return this.diffLines.length;
},
@@ -56,6 +61,12 @@ export default {
:line-index="index"
:help-page-path="helpPagePath"
/>
+ <parallel-draft-comment-row
+ v-if="shouldRenderParallelDraftRow(diffFile.file_hash, line)"
+ :key="`drafts-${index}`"
+ :line="line"
+ :diff-file-content-sha="diffFile.file_hash"
+ />
</template>
</tbody>
</table>
app/assets/javascripts/mr_notes/stores/index.js
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/mr_notes/stores/index.js b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/mr_notes/stores/index.js
index c4225c8ec08..c52ad4ceb65 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/mr_notes/stores/index.js
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/mr_notes/stores/index.js
@@ -2,6 +2,7 @@ import Vue from 'vue';
import Vuex from 'vuex';
import notesModule from '~/notes/stores/modules';
import diffsModule from '~/diffs/store/modules';
+import batchCommentsModule from 'ee/batch_comments/stores/modules/batch_comments';
import mrPageModule from './modules';
Vue.use(Vuex);
@@ -12,6 +13,7 @@ export const createStore = () =>
page: mrPageModule,
notes: notesModule(),
diffs: diffsModule(),
+ batchComments: batchCommentsModule(),
},
});
app/assets/javascripts/boards/index.js
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/boards/index.js b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/boards/index.js
index f88e9b55988..1a328689d03 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/boards/index.js
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/boards/index.js
@@ -10,26 +10,33 @@ import '~/vue_shared/models/assignee';
import FilteredSearchBoards from './filtered_search_boards';
import eventHub from './eventhub';
import sidebarEventHub from '~/sidebar/event_hub';
-import './models/issue';
-import './models/list';
import './models/milestone';
import './models/project';
import boardsStore from './stores/boards_store';
import ModalStore from './stores/modal_store';
-import BoardService from './services/board_service';
import modalMixin from './mixins/modal_mixins';
import './filters/due_date_filters';
-import Board from './components/board';
-import BoardSidebar from './components/board_sidebar';
+import Board from 'ee/boards/components/board';
+import BoardSidebar from 'ee/boards/components/board_sidebar';
import initNewListDropdown from './components/new_list_dropdown';
-import BoardAddIssuesModal from './components/modal/index.vue';
+import BoardAddIssuesModal from 'ee/boards/components/modal/index';
import '~/vue_shared/vue_resource_interceptor';
import { NavigationType, parseBoolean } from '~/lib/utils/common_utils';
+import 'ee/boards/models/list';
+import 'ee/boards/models/issue';
+import 'ee/boards/models/project';
+import BoardService from 'ee/boards/services/board_service';
+import BoardsSelector from 'ee/boards/components/boards_selector.vue';
+import collapseIcon from 'ee/boards/icons/fullscreen_collapse.svg';
+import expandIcon from 'ee/boards/icons/fullscreen_expand.svg';
+import tooltip from '~/vue_shared/directives/tooltip';
+
let issueBoardsApp;
export default () => {
const $boardApp = document.getElementById('board-app');
+ const issueBoardsContent = document.querySelector('.content-wrapper > .js-focus-mode-board');
// check for browser back and trigger a hard reload to circumvent browser caching.
window.addEventListener('pageshow', event => {
@@ -114,6 +121,7 @@ export default () => {
this.state.lists = _.sortBy(this.state.lists, 'position');
boardsStore.addBlankState();
+ boardsStore.addPromotionState();
this.loading = false;
})
.catch(() => {
@@ -128,16 +136,23 @@ export default () => {
const { sidebarInfoEndpoint } = newIssue;
if (sidebarInfoEndpoint && newIssue.subscribed === undefined) {
newIssue.setFetchingState('subscriptions', true);
+ newIssue.setFetchingState('weight', true);
+ newIssue.setFetchingState('epic', true);
BoardService.getIssueInfo(sidebarInfoEndpoint)
.then(res => res.data)
.then(data => {
newIssue.setFetchingState('subscriptions', false);
+ newIssue.setFetchingState('weight', false);
+ newIssue.setFetchingState('epic', false);
newIssue.updateData({
subscribed: data.subscribed,
+ weight: data.weight,
+ epic: data.epic,
});
})
.catch(() => {
newIssue.setFetchingState('subscriptions', false);
+ newIssue.setFetchingState('weight', false);
Flash(__('An error occurred while fetching sidebar data'));
});
}
@@ -172,12 +187,56 @@ export default () => {
el: document.getElementById('js-add-list'),
data: {
filters: boardsStore.state.filters,
+ milestoneTitle: $boardApp.dataset.boardMilestoneTitle,
},
mounted() {
initNewListDropdown();
},
});
+ const configEl = document.querySelector('.js-board-config');
+
+ if (configEl) {
+ gl.boardConfigToggle = new Vue({
+ el: configEl,
+ directives: {
+ tooltip,
+ },
+ data() {
+ return {
+ canAdminList: this.$options.el.hasAttribute('data-can-admin-list'),
+ hasScope: this.$options.el.hasAttribute('data-has-scope'),
+ state: boardsStore.state,
+ };
+ },
+ computed: {
+ buttonText() {
+ return this.canAdminList ? 'Edit board' : 'View scope';
+ },
+ tooltipTitle() {
+ return this.hasScope ? __("This board's scope is reduced") : '';
+ },
+ },
+ methods: {
+ showPage: page => boardsStore.showPage(page),
+ },
+ template: `
+ <div class="prepend-left-10">
+ <button
+ v-tooltip
+ :title="tooltipTitle"
+ class="btn btn-inverted"
+ :class="{ 'dot-highlight': hasScope }"
+ type="button"
+ @click.prevent="showPage('edit')"
+ >
+ {{ buttonText }}
+ </button>
+ </div>
+ `,
+ });
+ }
+
const issueBoardsModal = document.getElementById('js-add-issues-btn');
if (issueBoardsModal) {
@@ -189,6 +248,8 @@ export default () => {
return {
modal: ModalStore.store,
store: boardsStore.state,
+ isFullscreen: false,
+ focusModeAvailable: $boardApp.hasAttribute('data-focus-mode-available'),
canAdminList: this.$options.el.hasAttribute('data-can-admin-list'),
};
},
@@ -251,4 +312,78 @@ export default () => {
`,
});
}
+
+ // eslint-disable-next-line no-new
+ new Vue({
+ el: document.getElementById('js-toggle-focus-btn'),
+ data: {
+ modal: ModalStore.store,
+ store: boardsStore.state,
+ isFullscreen: false,
+ focusModeAvailable: $boardApp.hasAttribute('data-focus-mode-available'),
+ },
+ methods: {
+ toggleFocusMode() {
+ if (!this.focusModeAvailable) {
+ return;
+ }
+
+ $(this.$refs.toggleFocusModeButton).tooltip('hide');
+ issueBoardsContent.classList.toggle('is-focused');
+
+ this.isFullscreen = !this.isFullscreen;
+ },
+ },
+ template: `
+ <div class="board-extra-actions">
+ <a
+ href="#"
+ class="btn btn-default has-tooltip prepend-left-10 js-focus-mode-btn"
+ role="button"
+ aria-label="Toggle focus mode"
+ title="Toggle focus mode"
+ ref="toggleFocusModeButton"
+ v-if="focusModeAvailable"
+ @click="toggleFocusMode">
+ <span v-show="isFullscreen">
+ ${collapseIcon}
+ </span>
+ <span v-show="!isFullscreen">
+ ${expandIcon}
+ </span>
+ </a>
+ </div>
+ `,
+ });
+
+ const boardsSwitcherElement = document.getElementById('js-multiple-boards-switcher');
+ // eslint-disable-next-line no-new
+ new Vue({
+ el: boardsSwitcherElement,
+ components: {
+ BoardsSelector,
+ },
+ data() {
+ const { dataset } = boardsSwitcherElement;
+
+ const boardsSelectorProps = {
+ ...dataset,
+ currentBoard: JSON.parse(dataset.currentBoard),
+ hasMissingBoards: parseBoolean(dataset.hasMissingBoards),
+ canAdminBoard: parseBoolean(dataset.canAdminBoard),
+ multipleIssueBoardsAvailable: parseBoolean(dataset.multipleIssueBoardsAvailable),
+ projectId: Number(dataset.projectId),
+ groupId: Number(dataset.groupId),
+ scopedIssueBoardFeatureEnabled: parseBoolean(dataset.scopedIssueBoardFeatureEnabled),
+ weights: JSON.parse(dataset.weights),
+ };
+
+ return { boardsSelectorProps };
+ },
+ render(createElement) {
+ return createElement(BoardsSelector, {
+ props: this.boardsSelectorProps,
+ });
+ },
+ });
};
app/assets/javascripts/boards/components/board_sidebar.js
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/boards/components/board_sidebar.js b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/boards/components/board_sidebar.js
index e637e1f1223..f02de68df7d 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/boards/components/board_sidebar.js
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/boards/components/board_sidebar.js
@@ -2,6 +2,7 @@
import $ from 'jquery';
import Vue from 'vue';
+import Weight from 'ee/sidebar/components/weight/weight.vue';
import Flash from '../../flash';
import { sprintf, __ } from '../../locale';
import Sidebar from '../../right_sidebar';
@@ -22,6 +23,7 @@ export default Vue.extend({
Assignees,
RemoveBtn,
Subscriptions,
+ Weight,
},
props: {
currentUser: {
app/assets/javascripts/boards/components/board_new_issue.vue
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/boards/components/board_new_issue.vue b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/boards/components/board_new_issue.vue
index 28d96dab605..67d2d287423 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/boards/components/board_new_issue.vue
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/boards/components/board_new_issue.vue
@@ -51,11 +51,14 @@ export default {
const labels = this.list.label ? [this.list.label] : [];
const assignees = this.list.assignee ? [this.list.assignee] : [];
+ const milestone = this.list.milestone ? this.list.milestone : null;
+
const issue = new ListIssue({
title: this.title,
labels,
subscribed: true,
assignees,
+ milestone,
project_id: this.selectedProject.id,
});
app/assets/javascripts/boards/components/issue_card_inner.vue
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/boards/components/issue_card_inner.vue b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/boards/components/issue_card_inner.vue
index 90ab3a76342..3820156e7ee 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/boards/components/issue_card_inner.vue
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/boards/components/issue_card_inner.vue
@@ -3,6 +3,7 @@ import { GlTooltipDirective } from '@gitlab/ui';
import { sprintf, __ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
+import IssueCardWeight from 'ee/boards/components/issue_card_weight.vue';
import UserAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import eventHub from '../eventhub';
import IssueDueDate from './issue_due_date.vue';
@@ -15,6 +16,7 @@ export default {
UserAvatarLink,
TooltipOnTruncate,
IssueDueDate,
+ IssueCardWeight,
IssueTimeEstimate,
},
directives: {
@@ -209,6 +211,10 @@ export default {
<issue-due-date v-if="issue.dueDate" :date="issue.dueDate" /><issue-time-estimate
v-if="issue.timeEstimate"
:estimate="issue.timeEstimate"
+ /><issue-card-weight
+ v-if="issue.weight"
+ :weight="issue.weight"
+ @click="filterByWeight(issue.weight)"
/>
</span>
</div>
app/assets/javascripts/boards/stores/boards_store.js
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/boards/stores/boards_store.js b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/boards/stores/boards_store.js
index 802796208c2..af721ba8238 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/boards/stores/boards_store.js
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/boards/stores/boards_store.js
@@ -5,6 +5,7 @@ import $ from 'jquery';
import _ from 'underscore';
import Vue from 'vue';
import Cookies from 'js-cookie';
+import BoardsStoreEE from 'ee/boards/stores/boards_store_ee';
import { getUrlParamsArray, parseBoolean } from '~/lib/utils/common_utils';
const boardsStore = {
@@ -12,7 +13,13 @@ const boardsStore = {
filter: {
path: '',
},
- state: {},
+ state: {
+ currentBoard: {
+ labels: [],
+ },
+ currentPage: '',
+ reload: false,
+ },
detail: {
issue: {},
},
@@ -27,6 +34,17 @@ const boardsStore = {
issue: {},
};
},
+ createNewListDropdownData() {
+ this.state.currentBoard = {
+ labels: [],
+ };
+ this.state.currentPage = '';
+ this.state.reload = false;
+ },
+ showPage(page) {
+ this.state.reload = false;
+ this.state.currentPage = page;
+ },
addList(listObj, defaultAvatar) {
const list = new List(listObj, defaultAvatar);
this.state.lists.push(list);
@@ -174,6 +192,8 @@ const boardsStore = {
},
};
+BoardsStoreEE.initEESpecific(boardsStore);
+
// hacks added in order to allow milestone_select to function properly
// TODO: remove these
app/assets/javascripts/boards/models/milestone.js
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/boards/models/milestone.js b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/boards/models/milestone.js
index 17d15278a74..5fc9f31eb89 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/boards/models/milestone.js
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/boards/models/milestone.js
@@ -1,7 +1,11 @@
-class ListMilestone {
+export default class ListMilestone {
constructor(obj) {
this.id = obj.id;
this.title = obj.title;
+ this.path = obj.path;
+ this.state = obj.state;
+ this.webUrl = obj.web_url || obj.webUrl;
+ this.description = obj.description;
}
}
app/assets/javascripts/boards/models/issue.js
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/boards/models/issue.js b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/boards/models/issue.js
index dd92d3c8552..56766b2a679 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/boards/models/issue.js
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/boards/models/issue.js
@@ -28,7 +28,6 @@ class ListIssue {
this.referencePath = obj.reference_path;
this.path = obj.real_path;
this.toggleSubscriptionEndpoint = obj.toggle_subscription_endpoint;
- this.milestone_id = obj.milestone_id;
this.project_id = obj.project_id;
this.timeEstimate = obj.time_estimate;
this.assignableLabelsEndpoint = obj.assignable_labels_endpoint;
@@ -39,6 +38,7 @@ class ListIssue {
if (obj.milestone) {
this.milestone = new ListMilestone(obj.milestone);
+ this.milestone_id = obj.milestone.id;
}
obj.labels.forEach(label => {
@@ -88,6 +88,19 @@ class ListIssue {
this.assignees = [];
}
+ addMilestone(milestone) {
+ const miletoneId = this.milestone ? this.milestone.id : null;
+ if (milestone.id !== miletoneId) {
+ this.milestone = new ListMilestone(milestone);
+ }
+ }
+
+ removeMilestone(removeMilestone) {
+ if (removeMilestone && removeMilestone.id === this.milestone.id) {
+ this.milestone = {};
+ }
+ }
+
getLists() {
return boardsStore.state.lists.filter(list => list.findIssue(this.id));
}
app/assets/javascripts/boards/models/list.js
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/boards/models/list.js b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/boards/models/list.js
index 9f6d9a853da..227d242e4c3 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/boards/models/list.js
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/boards/models/list.js
@@ -6,6 +6,7 @@ import ListLabel from '~/vue_shared/models/label';
import ListAssignee from '~/vue_shared/models/assignee';
import { urlParamsToObject } from '~/lib/utils/common_utils';
import boardsStore from '../stores/boards_store';
+import ListMilestone from './milestone';
const PER_PAGE = 20;
@@ -51,6 +52,9 @@ class List {
} else if (obj.user) {
this.assignee = new ListAssignee(obj.user);
this.title = this.assignee.name;
+ } else if (obj.milestone) {
+ this.milestone = new ListMilestone(obj.milestone);
+ this.title = this.milestone.title;
}
if (!typeInfo.isBlank && this.id) {
@@ -69,12 +73,14 @@ class List {
}
save() {
- const entity = this.label || this.assignee;
+ const entity = this.label || this.assignee || this.milestone;
let entityType = '';
if (this.label) {
entityType = 'label_id';
- } else {
+ } else if (this.assignee) {
entityType = 'assignee_id';
+ } else if (this.milestone) {
+ entityType = 'milestone_id';
}
return gl.boardService
@@ -192,6 +198,13 @@ class List {
issue.addAssignee(this.assignee);
}
+ if (this.milestone) {
+ if (listFrom && listFrom.type === 'milestone') {
+ issue.removeMilestone(listFrom.milestone);
+ }
+ issue.addMilestone(this.milestone);
+ }
+
if (listFrom) {
this.issuesSize += 1;
app/assets/javascripts/boards/filtered_search_boards.js
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/boards/filtered_search_boards.js b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/boards/filtered_search_boards.js
index c14d69c5d18..aa9a23a3a7a 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/boards/filtered_search_boards.js
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/boards/filtered_search_boards.js
@@ -1,3 +1,4 @@
+import IssuesFilteredSearchTokenKeysEE from 'ee/filtered_search/issues_filtered_search_token_keys';
import FilteredSearchContainer from '../filtered_search/container';
import FilteredSearchManager from '../filtered_search/filtered_search_manager';
import boardsStore from './stores/boards_store';
@@ -8,6 +9,8 @@ export default class FilteredSearchBoards extends FilteredSearchManager {
page: 'boards',
isGroupDecendent: true,
stateFiltersSelector: '.issues-state-filters',
+ isGroup: true,
+ filteredSearchTokenKeys: IssuesFilteredSearchTokenKeysEE,
});
this.store = store;
app/assets/javascripts/members.js
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/members.js b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/members.js
index bd263c75a3d..59af9b7873f 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/members.js
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/members.js
@@ -1,4 +1,7 @@
import $ from 'jquery';
+import Flash from './flash';
+import { __ } from './locale';
+import axios from './lib/utils/axios_utils';
export default class Members {
constructor() {
@@ -7,6 +10,15 @@ export default class Members {
}
addListeners() {
+ $('.js-ldap-permissions')
+ .off('click')
+ .on('click', this.showLDAPPermissionsWarning.bind(this));
+ $('.js-ldap-override')
+ .off('click')
+ .on('click', this.toggleMemberAccessToggle.bind(this));
+ $('.project_member, .group_member')
+ .off('ajax:success')
+ .on('ajax:success', this.removeRow);
$('.js-member-update-control')
.off('change')
.on('change', this.formSubmit.bind(this));
@@ -23,6 +35,10 @@ export default class Members {
$btn.glDropdown({
selectable: true,
isSelectable(selected, $el) {
+ if ($el.data('revert')) {
+ return false;
+ }
+
return !$el.hasClass('is-active');
},
fieldName: $btn.data('fieldName'),
@@ -30,10 +46,28 @@ export default class Members {
return $el.data('id');
},
toggleLabel(selected, $el) {
+ if ($el.data('revert')) {
+ return $btn.text();
+ }
+
return $el.text();
},
clicked: options => {
- this.formSubmit(null, options.$el);
+ const $link = options.$el;
+
+ if (!$link.data('revert')) {
+ this.formSubmit(null, $link);
+ } else {
+ const { $memberListItem, $toggle, $dateInput } = this.getMemberListItems($link);
+
+ $toggle.disable();
+ $dateInput.disable();
+
+ this.overrideLdap($memberListItem, $link.data('endpoint'), false).catch(() => {
+ $toggle.enable();
+ $dateInput.enable();
+ });
+ }
},
});
});
@@ -55,6 +89,15 @@ export default class Members {
$toggle.enable();
$dateInput.enable();
}
+
+ showLDAPPermissionsWarning(e) {
+ const $btn = $(e.currentTarget);
+ const { $memberListItem } = this.getMemberListItems($btn);
+ const $ldapPermissionsElement = $memberListItem.next();
+
+ $ldapPermissionsElement.toggle();
+ }
+
// eslint-disable-next-line class-methods-use-this
getMemberListItems($el) {
const $memberListItem = $el.is('.member') ? $el : $(`#${$el.data('elId')}`);
@@ -65,4 +108,44 @@ export default class Members {
$dateInput: $memberListItem.find('.js-access-expiration-date'),
};
}
+
+ toggleMemberAccessToggle(e) {
+ const $btn = $(e.currentTarget);
+ const { $memberListItem, $toggle, $dateInput } = this.getMemberListItems($btn);
+
+ $btn.disable();
+ this.overrideLdap($memberListItem, $btn.data('endpoint'), true)
+ .then(() => {
+ this.showLDAPPermissionsWarning(e);
+
+ $toggle.enable();
+ $dateInput.enable();
+ })
+ .catch(xhr => {
+ $btn.enable();
+
+ if (xhr.status === 403) {
+ Flash(
+ __(
+ 'You do not have the correct permissions to override the settings from the LDAP group sync.',
+ ),
+ );
+ } else {
+ Flash(__('An error occurred while saving LDAP override status. Please try again.'));
+ }
+ });
+ }
+
+ // eslint-disable-next-line class-methods-use-this
+ overrideLdap($memberListitem, endpoint, override) {
+ return axios
+ .patch(endpoint, {
+ group_member: {
+ override,
+ },
+ })
+ .then(() => {
+ $memberListitem.toggleClass('is-overriden', override);
+ });
+ }
}
app/assets/javascripts/issuable_form.js
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/issuable_form.js b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/issuable_form.js
index c81a2230310..70dbb3e33f7 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/issuable_form.js
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/issuable_form.js
@@ -6,6 +6,7 @@ import GfmAutoComplete from './gfm_auto_complete';
import ZenMode from './zen_mode';
import AutoWidthDropdownSelect from './issuable/auto_width_dropdown_select';
import { parsePikadayDate, pikadayToString } from './lib/utils/datetime_utility';
+import groupsSelect from './groups_select';
export default class IssuableForm {
constructor(form) {
@@ -20,6 +21,7 @@ export default class IssuableForm {
gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources,
).setup();
this.usersSelect = new UsersSelect();
+ groupsSelect();
this.zenMode = new ZenMode();
this.titleField = this.form.find('input[name*="[title]"]');
app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue
index f11cf21b0ca..d6cb1dd3c13 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue
@@ -5,6 +5,7 @@ import PipelineStage from '~/pipelines/components/stage.vue';
import CiIcon from '~/vue_shared/components/ci_icon.vue';
import Icon from '~/vue_shared/components/icon.vue';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
+import LinkedPipelinesMiniList from 'ee/vue_shared/components/linked_pipelines_mini_list.vue';
export default {
name: 'MRWidgetPipeline',
@@ -13,6 +14,7 @@ export default {
CiIcon,
Icon,
TooltipOnTruncate,
+ LinkedPipelinesMiniList,
},
props: {
pipeline: {
@@ -74,10 +76,20 @@ export default {
false,
);
},
+ /* We typically set defaults ([]) in the store or prop declarations, but because triggered
+ * and triggeredBy are appended to `pipeline`, we can't set defaults in the store, and we
+ * need to check their length here to prevent initializing linked-pipeline-mini-lists
+ * unneccessarily. */
+ triggered() {
+ return this.pipeline.triggered || [];
+ },
+ triggeredBy() {
+ const response = this.pipeline.triggered_by;
+ return response ? [response] : [];
+ },
},
};
</script>
-
<template>
<div v-if="hasPipeline || hasCIError" class="ci-widget media">
<template v-if="hasCIError">
@@ -126,15 +138,24 @@ export default {
</div>
<div>
<span class="mr-widget-pipeline-graph">
- <span v-if="hasStages" class="stage-cell">
- <div
- v-for="(stage, i) in pipeline.details.stages"
- :key="i"
- class="stage-container dropdown js-mini-pipeline-graph mr-widget-pipeline-stages"
- >
- <pipeline-stage :stage="stage" />
- </div>
+ <span class="stage-cell">
+ <linked-pipelines-mini-list v-if="triggeredBy.length" :triggered-by="triggeredBy" />
+ <template v-if="hasStages">
+ <div
+ v-for="(stage, i) in pipeline.details.stages"
+ :key="i"
+ :class="{
+ 'has-downstream': i === pipeline.details.stages.length - 1 && triggered.length,
+ }"
+ class="stage-container dropdown js-mini-pipeline-graph
+ mr-widget-pipeline-stages"
+ >
+ <pipeline-stage :stage="stage" />
+ </div>
+ </template>
</span>
+
+ <linked-pipelines-mini-list v-if="triggered.length" :triggered="triggered" />
</span>
</div>
</div>
app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
index b8f29649eb5..399c3bad4e7 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
@@ -100,6 +100,7 @@ export default {
!commitMessage.length ||
!this.shouldShowMergeControls() ||
this.isMakingRequest ||
+ this.isApprovalNeeded ||
this.mr.preventMerge,
);
},
@@ -110,6 +111,9 @@ export default {
const { commitsCount, enableSquashBeforeMerge } = this.mr;
return enableSquashBeforeMerge && commitsCount > 1;
},
+ isApprovalNeeded() {
+ return this.mr.approvalsRequired ? !this.mr.isApproved : false;
+ },
},
methods: {
shouldShowMergeControls() {
app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
index 5a9d86594b1..c18d6a6f9be 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
@@ -153,6 +153,7 @@ export default {
statusPath: store.statusPath,
mergeActionsContentPath: store.mergeActionsContentPath,
rebasePath: store.rebasePath,
+ approvalsPath: store.approvalsPath,
};
return new MRWidgetService(endpoints);
},
app/assets/javascripts/api.js
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/api.js b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/api.js
index d1396b6c4bc..81d273a40f5 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/api.js
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/api.js
@@ -17,6 +17,7 @@ const Api = {
projectRunnersPath: '/api/:version/projects/:id/runners',
mergeRequestsPath: '/api/:version/merge_requests',
groupLabelsPath: '/groups/:namespace_path/-/labels',
+ ldapGroupsPath: '/api/:version/ldap/:provider/groups.json',
issuableTemplatePath: '/:namespace_path/:project_path/templates/:type/:key',
projectTemplatePath: '/api/:version/projects/:id/templates/:type/:key',
projectTemplatesPath: '/api/:version/projects/:id/templates/:type',
@@ -29,6 +30,8 @@ const Api = {
commitPipelinesPath: '/:project_id/commit/:sha/pipelines',
branchSinglePath: '/api/:version/projects/:id/repository/branches/:branch',
createBranchPath: '/api/:version/projects/:id/repository/branches',
+ geoNodesPath: '/api/:version/geo_nodes',
+ subscriptionPath: '/api/:version/namespaces/:id/gitlab_subscription',
releasesPath: '/api/:version/projects/:id/releases',
group(groupId, callback) {
@@ -308,6 +311,48 @@ const Api = {
});
},
+ approverUsers(search, options, callback = $.noop) {
+ const url = Api.buildUrl('/autocomplete/users.json');
+ return axios
+ .get(url, {
+ params: Object.assign(
+ {
+ search,
+ per_page: 20,
+ },
+ options,
+ ),
+ })
+ .then(({ data }) => {
+ callback(data);
+
+ return data;
+ });
+ },
+
+ ldap_groups(query, provider, callback) {
+ const url = Api.buildUrl(this.ldapGroupsPath).replace(':provider', provider);
+ return axios
+ .get(url, {
+ params: {
+ search: query,
+ per_page: 20,
+ active: true,
+ },
+ })
+ .then(({ data }) => {
+ callback(data);
+
+ return data;
+ });
+ },
+
+ userSubscription(namespaceId) {
+ const url = Api.buildUrl(this.subscriptionPath).replace(':id', encodeURIComponent(namespaceId));
+
+ return axios.get(url);
+ },
+
releases(id) {
const url = Api.buildUrl(this.releasesPath).replace(':id', encodeURIComponent(id));
app/assets/javascripts/environments/folder/environments_folder_view.vue
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/environments/folder/environments_folder_view.vue b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/environments/folder/environments_folder_view.vue
index d6f0b6115a6..eb8a0fc1ded 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/environments/folder/environments_folder_view.vue
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/environments/folder/environments_folder_view.vue
@@ -31,6 +31,28 @@ export default {
type: Boolean,
required: true,
},
+ // ee-only start
+ canaryDeploymentFeatureId: {
+ type: String,
+ required: true,
+ },
+ showCanaryDeploymentCallout: {
+ type: Boolean,
+ required: true,
+ },
+ userCalloutsPath: {
+ type: String,
+ required: true,
+ },
+ lockPromotionSvgPath: {
+ type: String,
+ required: true,
+ },
+ helpCanaryDeploymentsPath: {
+ type: String,
+ required: true,
+ },
+ // ee-only end
},
methods: {
successCallback(resp) {
@@ -57,6 +79,11 @@ export default {
:pagination="state.paginationInformation"
:can-create-deployment="canCreateDeployment"
:can-read-environment="canReadEnvironment"
+ :canary-deployment-feature-id="canaryDeploymentFeatureId"
+ :show-canary-deployment-callout="showCanaryDeploymentCallout"
+ :user-callouts-path="userCalloutsPath"
+ :lock-promotion-svg-path="lockPromotionSvgPath"
+ :help-canary-deployments-path="helpCanaryDeploymentsPath"
@onChangePage="onChangePage"
/>
</div>
app/assets/javascripts/environments/folder/environments_folder_bundle.js
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/environments/folder/environments_folder_bundle.js b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/environments/folder/environments_folder_bundle.js
index 982e550e73c..4013cc43f5f 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/environments/folder/environments_folder_bundle.js
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/environments/folder/environments_folder_bundle.js
@@ -3,6 +3,10 @@ import environmentsFolderApp from './environments_folder_view.vue';
import { parseBoolean } from '../../lib/utils/common_utils';
import Translate from '../../vue_shared/translate';
+// ee-only start
+import CanaryCalloutMixin from 'ee/environments/mixins/canary_callout_mixin'; // eslint-disable-line import/order
+// ee-only end
+
Vue.use(Translate);
export default () =>
@@ -11,6 +15,9 @@ export default () =>
components: {
environmentsFolderApp,
},
+ // ee-only start
+ mixins: [CanaryCalloutMixin],
+ // ee-only end
data() {
const environmentsData = document.querySelector(this.$options.el).dataset;
@@ -30,6 +37,13 @@ export default () =>
cssContainerClass: this.cssContainerClass,
canCreateDeployment: this.canCreateDeployment,
canReadEnvironment: this.canReadEnvironment,
+ // ee-only start
+ canaryDeploymentFeatureId: this.canaryDeploymentFeatureId,
+ showCanaryDeploymentCallout: this.showCanaryDeploymentCallout,
+ userCalloutsPath: this.userCalloutsPath,
+ lockPromotionSvgPath: this.lockPromotionSvgPath,
+ helpCanaryDeploymentsPath: this.helpCanaryDeploymentsPath,
+ // ee-only end
},
});
},
app/assets/javascripts/environments/index.js
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/environments/index.js b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/environments/index.js
index d366e7550b7..ee153cf26dd 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/environments/index.js
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/environments/index.js
@@ -3,6 +3,10 @@ import environmentsComponent from './components/environments_app.vue';
import { parseBoolean } from '../lib/utils/common_utils';
import Translate from '../vue_shared/translate';
+// ee-only start
+import CanaryCalloutMixin from 'ee/environments/mixins/canary_callout_mixin'; // eslint-disable-line import/order
+// ee-only end
+
Vue.use(Translate);
export default () =>
@@ -11,6 +15,9 @@ export default () =>
components: {
environmentsComponent,
},
+ // ee-only start
+ mixins: [CanaryCalloutMixin],
+ // ee-only end
data() {
const environmentsData = document.querySelector(this.$options.el).dataset;
@@ -34,6 +41,13 @@ export default () =>
canCreateEnvironment: this.canCreateEnvironment,
canCreateDeployment: this.canCreateDeployment,
canReadEnvironment: this.canReadEnvironment,
+ // ee-only start
+ canaryDeploymentFeatureId: this.canaryDeploymentFeatureId,
+ showCanaryDeploymentCallout: this.showCanaryDeploymentCallout,
+ userCalloutsPath: this.userCalloutsPath,
+ lockPromotionSvgPath: this.lockPromotionSvgPath,
+ helpCanaryDeploymentsPath: this.helpCanaryDeploymentsPath,
+ // ee-only end
},
});
},
app/assets/javascripts/environments/components/environment_item.vue
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/environments/components/environment_item.vue b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/environments/components/environment_item.vue
index f44806d82a6..346278ed5a3 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/environments/components/environment_item.vue
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/environments/components/environment_item.vue
@@ -447,12 +447,19 @@ export default {
folderIconName() {
return this.model.isOpen ? 'chevron-down' : 'chevron-right';
},
+
+ deployIconName() {
+ return this.model.isDeployBoardVisible ? 'chevron-down' : 'chevron-right';
+ },
},
methods: {
onClickFolder() {
eventHub.$emit('toggleFolder', this.model);
},
+ toggleDeployBoard() {
+ eventHub.$emit('toggleDeployBoard', this.model);
+ },
},
};
</script>
@@ -474,8 +481,16 @@ export default {
<div v-if="!model.isFolder" class="table-mobile-header" role="rowheader">
{{ s__('Environments|Environment') }}
</div>
+
+ <span v-if="model.hasDeployBoard" class="deploy-board-icon" @click="toggleDeployBoard">
+ <icon :name="deployIconName" />
+ </span>
+
<span v-if="!model.isFolder" class="environment-name table-mobile-content">
<a class="qa-environment-link" :href="environmentPath"> {{ model.name }} </a>
+ <span v-if="isProtected" class="badge badge-success">
+ {{ s__('Environments|protected') }}
+ </span>
</span>
<span v-else class="folder-name" role="button" @click="onClickFolder">
<icon :name="folderIconName" class="folder-icon" />
app/assets/javascripts/environments/components/environments_app.vue
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/environments/components/environments_app.vue b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/environments/components/environments_app.vue
index 87c1d44dd40..30908182bf8 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/environments/components/environments_app.vue
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/environments/components/environments_app.vue
@@ -44,17 +44,49 @@ export default {
type: String,
required: true,
},
+ // ee-only start
+ canaryDeploymentFeatureId: {
+ type: String,
+ required: true,
+ },
+ showCanaryDeploymentCallout: {
+ type: Boolean,
+ required: true,
+ },
+ userCalloutsPath: {
+ type: String,
+ required: true,
+ },
+ lockPromotionSvgPath: {
+ type: String,
+ required: true,
+ },
+ helpCanaryDeploymentsPath: {
+ type: String,
+ required: true,
+ },
+ // ee-only end
},
created() {
eventHub.$on('toggleFolder', this.toggleFolder);
+ eventHub.$on('toggleDeployBoard', this.toggleDeployBoard);
},
beforeDestroy() {
eventHub.$off('toggleFolder');
+ eventHub.$off('toggleDeployBoard');
},
methods: {
+ /**
+ * Toggles the visibility of the deploy boards of the clicked environment.
+ * @param {Object} model
+ */
+ toggleDeployBoard(model) {
+ this.store.toggleDeployBoard(model.id);
+ },
+
toggleFolder(folder) {
this.store.toggleFolder(folder);
@@ -108,6 +140,11 @@ export default {
:pagination="state.paginationInformation"
:can-create-deployment="canCreateDeployment"
:can-read-environment="canReadEnvironment"
+ :canary-deployment-feature-id="canaryDeploymentFeatureId"
+ :show-canary-deployment-callout="showCanaryDeploymentCallout"
+ :user-callouts-path="userCalloutsPath"
+ :lock-promotion-svg-path="lockPromotionSvgPath"
+ :help-canary-deployments-path="helpCanaryDeploymentsPath"
@onChangePage="onChangePage"
>
<empty-state
app/assets/javascripts/environments/components/container.vue
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/environments/components/container.vue b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/environments/components/container.vue
index bd402c0eea5..0bffa3a0cf1 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/environments/components/container.vue
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/environments/components/container.vue
@@ -30,6 +30,28 @@ export default {
type: Boolean,
required: true,
},
+ // ee-only start
+ canaryDeploymentFeatureId: {
+ type: String,
+ required: true,
+ },
+ showCanaryDeploymentCallout: {
+ type: Boolean,
+ required: true,
+ },
+ userCalloutsPath: {
+ type: String,
+ required: true,
+ },
+ lockPromotionSvgPath: {
+ type: String,
+ required: true,
+ },
+ helpCanaryDeploymentsPath: {
+ type: String,
+ required: true,
+ },
+ // ee-only end
},
methods: {
onChangePage(page) {
@@ -55,6 +77,11 @@ export default {
:environments="environments"
:can-create-deployment="canCreateDeployment"
:can-read-environment="canReadEnvironment"
+ :canary-deployment-feature-id="canaryDeploymentFeatureId"
+ :show-canary-deployment-callout="showCanaryDeploymentCallout"
+ :user-callouts-path="userCalloutsPath"
+ :lock-promotion-svg-path="lockPromotionSvgPath"
+ :help-canary-deployments-path="helpCanaryDeploymentsPath"
/>
<table-pagination
app/assets/javascripts/environments/components/environments_table.vue
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/environments/components/environments_table.vue b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/environments/components/environments_table.vue
index 75bdf87137f..08c3f5e377d 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/environments/components/environments_table.vue
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/environments/components/environments_table.vue
@@ -3,12 +3,21 @@
* Render environments table.
*/
import { GlLoadingIcon } from '@gitlab/ui';
-import environmentItem from './environment_item.vue';
+import environmentItem from './environment_item.vue'; // eslint-disable-line import/order
+
+// ee-only start
+import deployBoard from 'ee/environments/components/deploy_board_component.vue';
+import CanaryDeploymentCallout from 'ee/environments/components/canary_deployment_callout.vue';
+// ee-only end
export default {
components: {
environmentItem,
+ deployBoard,
GlLoadingIcon,
+ // ee-only start
+ CanaryDeploymentCallout,
+ // ee-only end
},
props: {
@@ -29,6 +38,33 @@ export default {
required: false,
default: false,
},
+
+ // ee-only start
+ canaryDeploymentFeatureId: {
+ type: String,
+ required: true,
+ },
+
+ showCanaryDeploymentCallout: {
+ type: Boolean,
+ required: true,
+ },
+
+ userCalloutsPath: {
+ type: String,
+ required: true,
+ },
+
+ lockPromotionSvgPath: {
+ type: String,
+ required: true,
+ },
+
+ helpCanaryDeploymentsPath: {
+ type: String,
+ required: true,
+ },
+ // ee-only end
},
methods: {
folderUrl(model) {
@@ -37,6 +73,11 @@ export default {
shouldRenderFolderContent(env) {
return env.isFolder && env.isOpen && env.children && env.children.length > 0;
},
+ // ee-only start
+ shouldShowCanaryCallout(env) {
+ return env.showCanaryCallout && this.showCanaryDeploymentCallout;
+ },
+ // ee-only end
},
};
</script>
@@ -68,6 +109,21 @@ export default {
:can-read-environment="canReadEnvironment"
/>
+ <div
+ v-if="model.hasDeployBoard && model.isDeployBoardVisible"
+ :key="`deploy-board-row-${i}`"
+ class="js-deploy-board-row"
+ >
+ <div class="deploy-board-container">
+ <deploy-board
+ :deploy-board-data="model.deployBoardData"
+ :is-loading="model.isLoadingDeployBoard"
+ :is-empty="model.isEmptyDeployBoard"
+ :logs-path="model.logs_path"
+ />
+ </div>
+ </div>
+
<template v-if="shouldRenderFolderContent(model)">
<div v-if="model.isLoadingFolderContent" :key="`loading-item-${i}`">
<gl-loading-icon :size="2" class="prepend-top-16" />
@@ -92,6 +148,17 @@ export default {
</div>
</template>
</template>
+
+ <template v-if="shouldShowCanaryCallout(model)">
+ <canary-deployment-callout
+ :key="`canary-promo-${i}`"
+ :canary-deployment-feature-id="canaryDeploymentFeatureId"
+ :user-callouts-path="userCalloutsPath"
+ :lock-promotion-svg-path="lockPromotionSvgPath"
+ :help-canary-deployments-path="helpCanaryDeploymentsPath"
+ :data-js-canary-promo-key="i"
+ />
+ </template>
</template>
</div>
</template>
app/assets/javascripts/environments/stores/environments_store.js
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/environments/stores/environments_store.js b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/environments/stores/environments_store.js
index 5808a2d4afa..4a076d44c3f 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/environments/stores/environments_store.js
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/environments/stores/environments_store.js
@@ -1,4 +1,7 @@
import { parseIntPagination, normalizeHeaders } from '~/lib/utils/common_utils';
+
+// ee-only
+import { CLUSTER_TYPE } from '~/clusters/constants';
/**
* Environments Store.
*
@@ -30,6 +33,14 @@ export default class EnvironmentsStore {
* If the `size` is bigger than 1, it means it should be rendered as a folder.
* In those cases we add `isFolder` key in order to render it properly.
*
+ * Top level environments - when the size is 1 - with `rollout_status`
+ * can render a deploy board. We add `isDeployBoardVisible` and `deployBoardData`
+ * keys to those environments.
+ * The first key will let's us know if we should or not render the deploy board.
+ * It will be toggled when the user clicks to seee the deploy board.
+ *
+ * The second key will allow us to update the environment with the received deploy board data.
+ *
* @param {Array} environments
* @returns {Array}
*/
@@ -62,14 +73,58 @@ export default class EnvironmentsStore {
filtered = Object.assign(filtered, env);
}
+ if (
+ filtered.size === 1 &&
+ filtered.rollout_status &&
+ filtered.cluster_type !== CLUSTER_TYPE.GROUP
+ ) {
+ filtered = Object.assign({}, filtered, {
+ hasDeployBoard: true,
+ isDeployBoardVisible:
+ oldEnvironmentState.isDeployBoardVisible === false
+ ? oldEnvironmentState.isDeployBoardVisible
+ : true,
+ deployBoardData:
+ filtered.rollout_status.status === 'found' ? filtered.rollout_status : {},
+ isLoadingDeployBoard: filtered.rollout_status.status === 'loading',
+ isEmptyDeployBoard: filtered.rollout_status.status === 'not_found',
+ });
+ }
return filtered;
});
this.state.environments = filteredEnvironments;
+ // ee-only start
+ /**
+ * Add the canary callout banner underneath the second environment listed.
+ *
+ * If there is only one environment, then add to it underneath the first.
+ */
+ if (this.state.environments.length >= 2) {
+ this.state.environments[1].showCanaryCallout = true;
+ } else if (this.state.environments.length === 1) {
+ this.state.environments[0].showCanaryCallout = true;
+ }
+ // ee-only end
+
return filteredEnvironments;
}
+ /**
+ * Stores the pagination information needed to render the pagination for the
+ * table.
+ *
+ * Normalizes the headers to uppercase since they can be provided either
+ * in uppercase or lowercase.
+ *
+ * Parses to an integer the normalized ones needed for the pagination component.
+ *
+ * Stores the normalized and parsed information.
+ *
+ * @param {Object} pagination = {}
+ * @return {Object}
+ */
setPagination(pagination = {}) {
const normalizedHeaders = normalizeHeaders(pagination);
const paginationInformation = parseIntPagination(normalizedHeaders);
@@ -165,4 +220,27 @@ export default class EnvironmentsStore {
return environments.filter(env => env.isFolder && env.isOpen);
}
+
+ /**
+ * Toggles deploy board visibility for the provided environment ID.
+ *
+ * @param {Object} environment
+ * @return {Array}
+ */
+ toggleDeployBoard(environmentID) {
+ const environments = this.state.environments.slice();
+
+ this.state.environments = environments.map(env => {
+ let updated = Object.assign({}, env);
+
+ if (env.id === environmentID) {
+ updated = Object.assign({}, updated, {
+ isDeployBoardVisible: !env.isDeployBoardVisible,
+ });
+ }
+ return updated;
+ });
+
+ return this.state.environments;
+ }
}
app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue
index 373794fb1f2..05446903286 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue
@@ -14,10 +14,12 @@ export default {
},
computed: {
labelsList() {
- const labelsString = this.labels
- .slice(0, 5)
- .map(label => label.title)
- .join(', ');
+ const labelsString = this.labels.length
+ ? this.labels
+ .slice(0, 5)
+ .map(label => label.title)
+ .join(', ')
+ : s__('LabelSelect|Labels');
if (this.labels.length > 5) {
return sprintf(s__('LabelSelect|%{labelsString}, and %{remainingLabelCount} more'), {
app/assets/javascripts/vue_shared/components/ci_icon.vue
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/vue_shared/components/ci_icon.vue b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/vue_shared/components/ci_icon.vue
index b8eb555106f..a1087b1d0a3 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/vue_shared/components/ci_icon.vue
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/vue_shared/components/ci_icon.vue
@@ -21,6 +21,7 @@ import Icon from '../../vue_shared/components/icon.vue';
* - Jobs table
* - Jobs show view header
* - Jobs show view sidebar
+ * - Linked pipelines
*/
const validSizes = [8, 12, 16, 18, 24, 32, 48, 72];
app/assets/javascripts/vue_shared/components/svg_gradient.vue
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/vue_shared/components/svg_gradient.vue b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/vue_shared/components/svg_gradient.vue
index cca90af275e..5ce45d492f9 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/vue_shared/components/svg_gradient.vue
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/vue_shared/components/svg_gradient.vue
@@ -4,10 +4,16 @@ export default {
colors: {
type: Array,
required: true,
+ validator(value) {
+ return value.length === 2;
+ },
},
opacity: {
type: Array,
required: true,
+ validator(value) {
+ return value.length === 2;
+ },
},
identifierName: {
type: String,
app/assets/javascripts/pages/groups/issues/index.js
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/pages/groups/issues/index.js b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/pages/groups/issues/index.js
index 736c6a62610..1fa163bb2d1 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/pages/groups/issues/index.js
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/pages/groups/issues/index.js
@@ -1,13 +1,13 @@
import projectSelect from '~/project_select';
import initFilteredSearch from '~/pages/search/init_filtered_search';
import { FILTERED_SEARCH } from '~/pages/constants';
-import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys';
+import IssuesFilteredSearchTokenKeysEE from 'ee/filtered_search/issues_filtered_search_token_keys';
document.addEventListener('DOMContentLoaded', () => {
initFilteredSearch({
page: FILTERED_SEARCH.ISSUES,
isGroupDecendent: true,
- filteredSearchTokenKeys: IssuableFilteredSearchTokenKeys,
+ filteredSearchTokenKeys: IssuesFilteredSearchTokenKeysEE,
});
projectSelect();
});
app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
index 19d9903c988..0c1ad372449 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
@@ -37,6 +37,11 @@ export default {
required: false,
default: false,
},
+ packagesAvailable: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
visibilityHelpPath: {
type: String,
required: false,
@@ -67,6 +72,11 @@ export default {
required: false,
default: '',
},
+ packagesHelpPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
data() {
@@ -82,6 +92,7 @@ export default {
pagesAccessLevel: 20,
containerRegistryEnabled: true,
lfsEnabled: true,
+ packagesEnabled: true,
requestAccessEnabled: true,
highlightChangesClass: false,
};
@@ -157,12 +168,14 @@ export default {
if (value === 0) {
this.containerRegistryEnabled = false;
this.lfsEnabled = false;
+ this.packagesEnabled = false;
}
} else if (oldValue === 0) {
this.mergeRequestsAccessLevel = value;
this.buildsAccessLevel = value;
this.containerRegistryEnabled = true;
this.lfsEnabled = true;
+ this.packagesEnabled = true;
}
},
@@ -304,6 +317,18 @@ export default {
name="project[lfs_enabled]"
/>
</project-setting-row>
+ <project-setting-row
+ v-if="packagesAvailable"
+ :help-path="packagesHelpPath"
+ label="Packages"
+ help-text="Every project can have its own space to store its packages"
+ >
+ <project-feature-toggle
+ v-model="packagesEnabled"
+ :disabled-input="!repositoryEnabled"
+ name="project[packages_enabled]"
+ />
+ </project-setting-row>
</div>
<project-setting-row label="Wiki" help-text="Pages for project documentation">
<project-feature-setting
app/assets/javascripts/pages/projects/project.js
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/pages/projects/project.js b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/pages/projects/project.js
index b288989b252..f0d529758d5 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/pages/projects/project.js
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/pages/projects/project.js
@@ -39,6 +39,11 @@ export default class Project {
$label.text(activeText);
});
+ $('#modal-geo-info').data({
+ cloneUrlSecondary: $this.attr('href'),
+ cloneUrlPrimary: $this.data('primaryUrl') || '',
+ });
+
if (mobileCloneField) {
mobileCloneField.dataset.clipboardText = url;
} else {
@@ -67,6 +72,13 @@ export default class Project {
.remove();
return e.preventDefault();
});
+ $('.hide-shared-runner-limit-message').on('click', function(e) {
+ var $alert = $(this).parents('.shared-runner-quota-message');
+ var scope = $alert.data('scope');
+ Cookies.set('hide_shared_runner_quota_message', 'false', { path: scope });
+ $alert.remove();
+ e.preventDefault();
+ });
$('.hide-auto-devops-implicitly-enabled-banner').on('click', function(e) {
const projectId = $(this).data('project-id');
const cookieKey = `hide_auto_devops_implicitly_enabled_banner_${projectId}`;
app/assets/javascripts/pages/projects/issues/form.js
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/pages/projects/issues/form.js b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/pages/projects/issues/form.js
index f99023ad8e7..a2d8ae6d526 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/pages/projects/issues/form.js
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/pages/projects/issues/form.js
@@ -8,6 +8,7 @@ import MilestoneSelect from '~/milestone_select';
import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
import IssuableTemplateSelectors from '~/templates/issuable_template_selectors';
import initSuggestions from '~/issuable_suggestions';
+import WeightSelect from 'ee/weight_select';
export default () => {
new ShortcutsNavigation();
@@ -20,4 +21,6 @@ export default () => {
if (gon.features.graphql) {
initSuggestions();
}
+
+ new WeightSelect();
};
app/assets/javascripts/pages/projects/issues/index/index.js
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/pages/projects/issues/index/index.js b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/pages/projects/issues/index/index.js
index a56c0bb6be8..1e36f098c88 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/pages/projects/issues/index/index.js
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/pages/projects/issues/index/index.js
@@ -4,14 +4,14 @@ import IssuableIndex from '~/issuable_index';
import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
import UsersSelect from '~/users_select';
import initFilteredSearch from '~/pages/search/init_filtered_search';
-import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys';
import { FILTERED_SEARCH } from '~/pages/constants';
import { ISSUABLE_INDEX } from '~/pages/projects/constants';
+import IssuesFilteredSearchTokenKeysEE from 'ee/filtered_search/issues_filtered_search_token_keys';
document.addEventListener('DOMContentLoaded', () => {
initFilteredSearch({
page: FILTERED_SEARCH.ISSUES,
- filteredSearchTokenKeys: IssuableFilteredSearchTokenKeys,
+ filteredSearchTokenKeys: IssuesFilteredSearchTokenKeysEE,
});
new IssuableIndex(ISSUABLE_INDEX.ISSUE);
app/assets/javascripts/pages/milestones/shared/components/promote_milestone_modal.vue
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/pages/milestones/shared/components/promote_milestone_modal.vue b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/pages/milestones/shared/components/promote_milestone_modal.vue
index a79ef07f1c5..42124c63885 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/pages/milestones/shared/components/promote_milestone_modal.vue
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/pages/milestones/shared/components/promote_milestone_modal.vue
@@ -31,11 +31,26 @@ export default {
});
},
text() {
+ const milestonePromotion = sprintf(
+ s__(`Milestones|Promoting %{milestone} will make it available for all projects inside %{groupName}.
+ Existing project milestones with the same name will be merged. `),
+ {
+ milestone: this.milestoneTitle,
+ groupName: this.groupName,
+ },
+ );
+ const finalWarning = s__('Milestones|This action cannot be reversed.');
+
return sprintf(
- s__(`Milestones|Promoting %{milestoneTitle} will make it available for all projects inside %{groupName}.
- Existing project milestones with the same title will be merged.
- This action cannot be reversed.`),
- { milestoneTitle: this.milestoneTitle, groupName: this.groupName },
+ s__(
+ `Milestones|<p>%{milestonePromotion}</p>
+ %{finalWarning}`,
+ ),
+ {
+ milestonePromotion,
+ finalWarning,
+ },
+ false,
);
},
},
@@ -72,6 +87,6 @@ export default {
<template slot="title">
{{ title }}
</template>
- {{ text }}
+ <div v-html="text"></div>
</gl-modal>
</template>
app/assets/javascripts/pipelines/components/graph/stage_column_component.vue
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue
index 09a50d25020..556d26b33c9 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue
@@ -27,13 +27,18 @@ export default {
required: false,
default: '',
},
+ hasTriggeredBy: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
methods: {
groupId(group) {
return `ci-badge-${_.escape(group.name)}`;
},
buildConnnectorClass(index) {
- return index === 0 && !this.isFirstColumn ? 'left-connector' : '';
+ return index === 0 && (!this.isFirstColumn || this.hasTriggeredBy) ? 'left-connector' : '';
},
pipelineActionRequestComplete() {
this.$emit('refreshPipelineGraph');
app/assets/javascripts/pipelines/pipeline_details_bundle.js
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/pipelines/pipeline_details_bundle.js b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/pipelines/pipeline_details_bundle.js
index dc9befe6349..313348386ae 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/pipelines/pipeline_details_bundle.js
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/pipelines/pipeline_details_bundle.js
@@ -2,10 +2,11 @@ import Vue from 'vue';
import Flash from '~/flash';
import Translate from '~/vue_shared/translate';
import { __ } from '~/locale';
-import PipelinesMediator from './pipeline_details_mediator';
-import pipelineGraph from './components/graph/graph_component.vue';
+import PipelinesMediator from 'ee/pipelines/pipeline_details_mediator';
+import pipelineGraph from 'ee/pipelines/components/graph/graph_component.vue';
import pipelineHeader from './components/header_component.vue';
import eventHub from './event_hub';
+import GraphEEMixin from 'ee/pipelines/mixins/graph_pipeline_bundle_mixin'; // eslint-disable-line import/order
Vue.use(Translate);
@@ -22,6 +23,7 @@ export default () => {
components: {
pipelineGraph,
},
+ mixins: [GraphEEMixin],
data() {
return {
mediator,
@@ -41,9 +43,21 @@ export default () => {
props: {
isLoading: this.mediator.state.isLoading,
pipeline: this.mediator.store.state.pipeline,
+ // EE-only start
+ triggeredPipelines: this.mediator.store.state.triggeredPipelines,
+ triggered: this.mediator.store.state.triggered,
+ triggeredByPipelines: this.mediator.store.state.triggeredByPipelines,
+ triggeredBy: this.mediator.store.state.triggeredBy,
+ // EE-only end
},
on: {
refreshPipelineGraph: this.requestRefreshPipelineGraph,
+ // EE-only start
+ refreshTriggeredPipelineGraph: this.mediator.refreshTriggeredByPipelineGraph,
+ refreshTriggeredByPipelineGraph: this.mediator.refreshTriggeredByPipelineGraph,
+ onClickTriggeredBy: pipeline => this.clickTriggeredBy(pipeline),
+ onClickTriggered: pipeline => this.clickTriggered(pipeline),
+ // EE-only end
},
});
},
app/assets/javascripts/jobs/components/job_app.vue
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/jobs/components/job_app.vue b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/jobs/components/job_app.vue
index d473d6a482d..c1842d5b475 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/jobs/components/job_app.vue
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/jobs/components/job_app.vue
@@ -7,6 +7,9 @@ import { polyfillSticky } from '~/lib/utils/sticky';
import bp from '~/breakpoints';
import CiHeader from '~/vue_shared/components/header_ci_component.vue';
import Callout from '~/vue_shared/components/callout.vue';
+// ee-only start
+import SharedRunner from 'ee/jobs/components/shared_runner_limit_block.vue';
+// ee-only end
import Icon from '~/vue_shared/components/icon.vue';
import createStore from '../store';
import EmptyState from './empty_state.vue';
@@ -28,12 +31,13 @@ export default {
EmptyState,
EnvironmentsBlock,
ErasedBlock,
+ GlLoadingIcon,
Icon,
Log,
LogTopBar,
StuckBlock,
+ SharedRunner,
Sidebar,
- GlLoadingIcon,
},
mixins: [delayedJobMixin],
props: {
@@ -84,6 +88,7 @@ export default {
'shouldRenderCalloutMessage',
'shouldRenderTriggeredLabel',
'hasEnvironment',
+ 'shouldRenderSharedRunnerLimitWarning',
'hasTrace',
'emptyStateIllustration',
'isScrollingDown',
@@ -221,6 +226,14 @@ export default {
:runners-path="runnerSettingsUrl"
/>
+ <shared-runner
+ v-if="shouldRenderSharedRunnerLimitWarning"
+ class="js-shared-runner-limit"
+ :quota-used="job.runners.quota.used"
+ :quota-limit="job.runners.quota.limit"
+ :runners-path="runnerHelpUrl"
+ />
+
<environments-block
v-if="hasEnvironment"
class="js-job-environment"
app/assets/javascripts/jobs/store/getters.js
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/jobs/store/getters.js b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/jobs/store/getters.js
index 98911717381..734c1cf0cb3 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/jobs/store/getters.js
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/jobs/store/getters.js
@@ -28,6 +28,19 @@ export const emptyStateIllustration = state =>
export const emptyStateAction = state =>
(state.job && state.job.status && state.job.status.action) || null;
+// ee-only start
+/**
+ * Shared runners limit is only rendered when
+ * used quota is bigger or equal than the limit
+ *
+ * @returns {Boolean}
+ */
+export const shouldRenderSharedRunnerLimitWarning = state =>
+ !_.isEmpty(state.job.runners) &&
+ !_.isEmpty(state.job.runners.quota) &&
+ state.job.runners.quota.used >= state.job.runners.quota.limit;
+// ee-only end
+
export const isScrollingDown = state => isScrolledToBottom() && !state.isTraceComplete;
export const hasRunnersForProject = state =>
app/assets/javascripts/gfm_auto_complete.js
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/gfm_auto_complete.js b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/gfm_auto_complete.js
index 570d3b712e0..82fc44ef65f 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/gfm_auto_complete.js
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/gfm_auto_complete.js
@@ -1,5 +1,9 @@
import $ from 'jquery';
import _ from 'underscore';
+
+// EE-specific
+import setupAutoCompleteEpics from 'ee/gfm_auto_complete_ee';
+
import glRegexp from './lib/utils/regexp';
import AjaxCache from './lib/utils/ajax_cache';
@@ -53,6 +57,9 @@ class GfmAutoComplete {
if (this.enableMap.labels) this.setupLabels($input);
if (this.enableMap.snippets) this.setupSnippets($input);
+ // EE-specific
+ if (this.enableMap.epics) setupAutoCompleteEpics($input, this.getDefaultCallbacks());
+
// We don't instantiate the quick actions autocomplete for note and issue/MR edit forms
$input.filter('[data-supports-quick-actions="true"]').atwho({
at: '/',
app/assets/javascripts/lib/utils/number_utils.js
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/lib/utils/number_utils.js b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/lib/utils/number_utils.js
index 2ccc51c35f7..19c4de6083d 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/assets/javascripts/lib/utils/number_utils.js
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/assets/javascripts/lib/utils/number_utils.js
@@ -80,3 +80,22 @@ export function numberToHumanSize(size) {
}
return `${bytesToGiB(size).toFixed(2)} GiB`;
}
+
+/**
+ * A simple method that returns the value of a + b
+ * It seems unessesary, but when combined with a reducer it
+ * adds up all the values in an array.
+ *
+ * e.g. `[1, 2, 3, 4, 5].reduce(sum) // => 15`
+ *
+ * @param {Float} a
+ * @param {Float} b
+ * @example
+ * // return 15
+ * [1, 2, 3, 4, 5].reduce(sum);
+ *
+ * // returns 6
+ * Object.values([{a: 1, b: 2, c: 3].reduce(sum);
+ * @returns {Float} The summed value
+ */
+export const sum = (a = 0, b = 0) => a + b;
Edited by Filipa Lacerda