Commit f35a3c5b authored by Julius Kvedaras's avatar Julius Kvedaras

Merge branch 'ce-to-ee-2018-08-08' into 'master'

CE upstream - 2018-08-08 07:57 UTC

See merge request gitlab-org/gitlab-ee!6833
parents 6d01a65a 33b71fa5
......@@ -53,6 +53,16 @@ gl.issueBoards.BoardSidebar = Vue.extend({
canRemove() {
return !this.list.preset;
},
hasLabels() {
return this.issue.labels && this.issue.labels.length;
},
labelDropdownTitle() {
return this.hasLabels ?
`${this.issue.labels[0].title} ${this.issue.labels.length - 1}+ more` : 'Label';
},
selectedLabels() {
return this.hasLabels ? this.issue.labels.map(l => l.title).join(',') : '';
}
},
watch: {
detail: {
......
......@@ -6,7 +6,7 @@ import Poll from '../lib/utils/poll';
import initSettingsPanels from '../settings_panels';
import eventHub from './event_hub';
import {
APPLICATION_INSTALLED,
APPLICATION_STATUS,
REQUEST_LOADING,
REQUEST_SUCCESS,
REQUEST_FAILURE,
......@@ -177,8 +177,8 @@ export default class Clusters {
checkForNewInstalls(prevApplicationMap, newApplicationMap) {
const appTitles = Object.keys(newApplicationMap)
.filter(appId => newApplicationMap[appId].status === APPLICATION_INSTALLED &&
prevApplicationMap[appId].status !== APPLICATION_INSTALLED &&
.filter(appId => newApplicationMap[appId].status === APPLICATION_STATUS.INSTALLED &&
prevApplicationMap[appId].status !== APPLICATION_STATUS.INSTALLED &&
prevApplicationMap[appId].status !== null)
.map(appId => newApplicationMap[appId].title);
......
......@@ -4,12 +4,7 @@
import eventHub from '../event_hub';
import loadingButton from '../../vue_shared/components/loading_button.vue';
import {
APPLICATION_NOT_INSTALLABLE,
APPLICATION_SCHEDULED,
APPLICATION_INSTALLABLE,
APPLICATION_INSTALLING,
APPLICATION_INSTALLED,
APPLICATION_ERROR,
APPLICATION_STATUS,
REQUEST_LOADING,
REQUEST_SUCCESS,
REQUEST_FAILURE,
......@@ -59,49 +54,57 @@
},
},
computed: {
isUnknownStatus() {
return !this.isKnownStatus && this.status !== null;
},
isKnownStatus() {
return Object.values(APPLICATION_STATUS).includes(this.status);
},
rowJsClass() {
return `js-cluster-application-row-${this.id}`;
},
installButtonLoading() {
return !this.status ||
this.status === APPLICATION_SCHEDULED ||
this.status === APPLICATION_INSTALLING ||
this.status === APPLICATION_STATUS.SCHEDULED ||
this.status === APPLICATION_STATUS.INSTALLING ||
this.requestStatus === REQUEST_LOADING;
},
installButtonDisabled() {
// Avoid the potential for the real-time data to say APPLICATION_INSTALLABLE but
// Avoid the potential for the real-time data to say APPLICATION_STATUS.INSTALLABLE but
// we already made a request to install and are just waiting for the real-time
// to sync up.
return (this.status !== APPLICATION_INSTALLABLE
&& this.status !== APPLICATION_ERROR) ||
return ((this.status !== APPLICATION_STATUS.INSTALLABLE
&& this.status !== APPLICATION_STATUS.ERROR) ||
this.requestStatus === REQUEST_LOADING ||
this.requestStatus === REQUEST_SUCCESS;
this.requestStatus === REQUEST_SUCCESS) && this.isKnownStatus;
},
installButtonLabel() {
let label;
if (
this.status === APPLICATION_NOT_INSTALLABLE ||
this.status === APPLICATION_INSTALLABLE ||
this.status === APPLICATION_ERROR
this.status === APPLICATION_STATUS.NOT_INSTALLABLE ||
this.status === APPLICATION_STATUS.INSTALLABLE ||
this.status === APPLICATION_STATUS.ERROR ||
this.isUnknownStatus
) {
label = s__('ClusterIntegration|Install');
} else if (this.status === APPLICATION_SCHEDULED ||
this.status === APPLICATION_INSTALLING) {
} else if (this.status === APPLICATION_STATUS.SCHEDULED ||
this.status === APPLICATION_STATUS.INSTALLING) {
label = s__('ClusterIntegration|Installing');
} else if (this.status === APPLICATION_INSTALLED) {
} else if (this.status === APPLICATION_STATUS.INSTALLED ||
this.status === APPLICATION_STATUS.UPDATED) {
label = s__('ClusterIntegration|Installed');
}
return label;
},
showManageButton() {
return this.manageLink && this.status === APPLICATION_INSTALLED;
return this.manageLink && this.status === APPLICATION_STATUS.INSTALLED;
},
manageButtonLabel() {
return s__('ClusterIntegration|Manage');
},
hasError() {
return this.status === APPLICATION_ERROR ||
return this.status === APPLICATION_STATUS.ERROR ||
this.requestStatus === REQUEST_FAILURE;
},
generalErrorDescription() {
......@@ -182,7 +185,7 @@
</div>
</div>
<div
v-if="hasError"
v-if="hasError || isUnknownStatus"
class="gl-responsive-table-row-layout"
role="row"
>
......
......@@ -3,7 +3,7 @@ import _ from 'underscore';
import { s__, sprintf } from '../../locale';
import applicationRow from './application_row.vue';
import clipboardButton from '../../vue_shared/components/clipboard_button.vue';
import { APPLICATION_INSTALLED, INGRESS } from '../constants';
import { APPLICATION_STATUS, INGRESS } from '../constants';
export default {
components: {
......@@ -58,7 +58,7 @@ export default {
return INGRESS;
},
ingressInstalled() {
return this.applications.ingress.status === APPLICATION_INSTALLED;
return this.applications.ingress.status === APPLICATION_STATUS.INSTALLED;
},
ingressExternalIp() {
return this.applications.ingress.externalIp;
......@@ -122,7 +122,7 @@ export default {
);
},
jupyterInstalled() {
return this.applications.jupyter.status === APPLICATION_INSTALLED;
return this.applications.jupyter.status === APPLICATION_STATUS.INSTALLED;
},
jupyterHostname() {
return this.applications.jupyter.hostname;
......
// These need to match what is returned from the server
export const APPLICATION_NOT_INSTALLABLE = 'not_installable';
export const APPLICATION_INSTALLABLE = 'installable';
export const APPLICATION_SCHEDULED = 'scheduled';
export const APPLICATION_INSTALLING = 'installing';
export const APPLICATION_INSTALLED = 'installed';
export const APPLICATION_ERROR = 'errored';
export const APPLICATION_STATUS = {
NOT_INSTALLABLE: 'not_installable',
INSTALLABLE: 'installable',
SCHEDULED: 'scheduled',
INSTALLING: 'installing',
INSTALLED: 'installed',
UPDATED: 'updated',
ERROR: 'errored',
};
// These are only used client-side
export const REQUEST_LOADING = 'request-loading';
......
......@@ -19,3 +19,4 @@ import './polyfills/custom_event';
import './polyfills/element';
import './polyfills/event';
import './polyfills/nodelist';
import './polyfills/request_idle_callback';
window.requestIdleCallback =
window.requestIdleCallback ||
function requestShim(cb) {
const start = Date.now();
return setTimeout(() => {
cb({
didTimeout: false,
timeRemaining: () => Math.max(0, 50 - (Date.now() - start)),
});
}, 1);
};
window.cancelIdleCallback =
window.cancelIdleCallback ||
function cancelShim(id) {
clearTimeout(id);
};
......@@ -71,13 +71,23 @@ export default {
required: false,
default: false,
},
isHover: {
type: Boolean,
required: false,
default: false,
},
discussions: {
type: Array,
required: false,
default: () => [],
},
},
computed: {
...mapState({
diffViewType: state => state.diffs.diffViewType,
diffFiles: state => state.diffs.diffFiles,
}),
...mapGetters(['isLoggedIn', 'discussionsByLineCode']),
...mapGetters(['isLoggedIn']),
lineHref() {
return this.lineCode ? `#${this.lineCode}` : '#';
},
......@@ -85,26 +95,22 @@ export default {
return (
this.isLoggedIn &&
this.showCommentButton &&
this.isHover &&
!this.isMatchLine &&
!this.isContextLine &&
!this.hasDiscussions &&
!this.isMetaLine
!this.isMetaLine &&
!this.hasDiscussions
);
},
discussions() {
return this.discussionsByLineCode[this.lineCode] || [];
},
hasDiscussions() {
return this.discussions.length > 0;
},
shouldShowAvatarsOnGutter() {
let render = this.hasDiscussions && this.showCommentButton;
if (!this.lineType && this.linePosition === LINE_POSITION_RIGHT) {
render = false;
return false;
}
return render;
return this.showCommentButton && this.hasDiscussions;
},
},
methods: {
......@@ -176,7 +182,7 @@ export default {
v-else
>
<button
v-show="shouldShowCommentButton"
v-if="shouldShowCommentButton"
type="button"
class="add-diff-note js-add-diff-note-button"
title="Add a comment to this line"
......
......@@ -67,6 +67,11 @@ export default {
required: false,
default: false,
},
discussions: {
type: Array,
required: false,
default: () => [],
},
},
computed: {
...mapGetters(['isLoggedIn']),
......@@ -132,10 +137,12 @@ export default {
:line-number="lineNumber"
:meta-data="normalizedLine.metaData"
:show-comment-button="showCommentButton"
:is-hover="isHover"
:is-bottom="isBottom"
:is-match-line="isMatchLine"
:is-context-line="isContentLine"
:is-meta-line="isMetaLine"
:discussions="discussions"
/>
</td>
</template>
<script>
import { mapState, mapGetters } from 'vuex';
import { mapState } from 'vuex';
import diffDiscussions from './diff_discussions.vue';
import diffLineNoteForm from './diff_line_note_form.vue';
......@@ -21,15 +21,16 @@ export default {
type: Number,
required: true,
},
discussions: {
type: Array,
required: false,
default: () => [],
},
},
computed: {
...mapState({
diffLineCommentForms: state => state.diffs.diffLineCommentForms,
}),
...mapGetters(['discussionsByLineCode']),
discussions() {
return this.discussionsByLineCode[this.line.lineCode] || [];
},
className() {
return this.discussions.length ? '' : 'js-temp-notes-holder';
},
......
......@@ -33,6 +33,11 @@ export default {
required: false,
default: false,
},
discussions: {
type: Array,
required: false,
default: () => [],
},
},
data() {
return {
......@@ -89,6 +94,7 @@ export default {
:is-bottom="isBottom"
:is-hover="isHover"
:show-comment-button="true"
:discussions="discussions"
class="diff-line-num old_line"
/>
<diff-table-cell
......@@ -98,6 +104,7 @@ export default {
:line-type="newLineType"
:is-bottom="isBottom"
:is-hover="isHover"
:discussions="discussions"
class="diff-line-num new_line"
/>
<td
......
......@@ -20,8 +20,11 @@ export default {
},
},
computed: {
...mapGetters('diffs', ['commitId']),
...mapGetters(['discussionsByLineCode']),
...mapGetters('diffs', [
'commitId',
'shouldRenderInlineCommentRow',
'singleDiscussionByLineCode',
]),
...mapState({
diffLineCommentForms: state => state.diffs.diffLineCommentForms,
}),
......@@ -36,15 +39,8 @@ export default {
},
},
methods: {
shouldRenderCommentRow(line) {
if (this.diffLineCommentForms[line.lineCode]) return true;
const lineDiscussions = this.discussionsByLineCode[line.lineCode];
if (lineDiscussions === undefined) {
return false;
}
return lineDiscussions.every(discussion => discussion.expanded);
discussionsList(line) {
return line.lineCode !== undefined ? this.singleDiscussionByLineCode(line.lineCode) : [];
},
},
};
......@@ -65,13 +61,15 @@ export default {
:line="line"
:is-bottom="index + 1 === diffLinesLength"
:key="line.lineCode"
:discussions="discussionsList(line)"
/>
<inline-diff-comment-row
v-if="shouldRenderCommentRow(line)"
v-if="shouldRenderInlineCommentRow(line)"
:diff-file-hash="diffFile.fileHash"
:line="line"
:line-index="index"
:key="index"
:discussions="discussionsList(line)"
/>
</template>
</tbody>
......
<script>
import { mapState, mapGetters } from 'vuex';
import { mapState } from 'vuex';
import diffDiscussions from './diff_discussions.vue';
import diffLineNoteForm from './diff_line_note_form.vue';
......@@ -21,30 +21,34 @@ export default {
type: Number,
required: true,
},
leftDiscussions: {
type: Array,
required: false,
default: () => [],
},
rightDiscussions: {
type: Array,
required: false,
default: () => [],
},
},
computed: {
...mapState({
diffLineCommentForms: state => state.diffs.diffLineCommentForms,
}),
...mapGetters(['discussionsByLineCode']),
leftLineCode() {
return this.line.left.lineCode;
},
rightLineCode() {
return this.line.right.lineCode;
},
hasDiscussion() {
const discussions = this.discussionsByLineCode;
return discussions[this.leftLineCode] || discussions[this.rightLineCode];
},
hasExpandedDiscussionOnLeft() {
const discussions = this.discussionsByLineCode[this.leftLineCode];
const discussions = this.leftDiscussions;
return discussions ? discussions.every(discussion => discussion.expanded) : false;
},
hasExpandedDiscussionOnRight() {
const discussions = this.discussionsByLineCode[this.rightLineCode];
const discussions = this.rightDiscussions;
return discussions ? discussions.every(discussion => discussion.expanded) : false;
},
......@@ -52,17 +56,18 @@ export default {
return this.hasExpandedDiscussionOnLeft || this.hasExpandedDiscussionOnRight;
},
shouldRenderDiscussionsOnLeft() {
return this.discussionsByLineCode[this.leftLineCode] && this.hasExpandedDiscussionOnLeft;
return this.leftDiscussions && this.hasExpandedDiscussionOnLeft;
},
shouldRenderDiscussionsOnRight() {
return (
this.discussionsByLineCode[this.rightLineCode] &&
this.hasExpandedDiscussionOnRight &&
this.line.right.type
);
return this.rightDiscussions && this.hasExpandedDiscussionOnRight && this.line.right.type;
},
showRightSideCommentForm() {
return this.line.right.type && this.diffLineCommentForms[this.rightLineCode];
},
className() {
return this.hasDiscussion ? '' : 'js-temp-notes-holder';
return this.leftDiscussions.length > 0 || this.rightDiscussions.length > 0
? ''
: 'js-temp-notes-holder';
},
},
};
......@@ -80,13 +85,12 @@ export default {
class="content"
>
<diff-discussions
v-if="discussionsByLineCode[leftLineCode].length"
:discussions="discussionsByLineCode[leftLineCode]"
v-if="leftDiscussions.length"
:discussions="leftDiscussions"
/>
</div>
<diff-line-note-form
v-if="diffLineCommentForms[leftLineCode] &&
diffLineCommentForms[leftLineCode]"
v-if="diffLineCommentForms[leftLineCode]"
:diff-file-hash="diffFileHash"
:line="line.left"
:note-target-line="line.left"
......@@ -100,13 +104,12 @@ export default {
class="content"
>
<diff-discussions
v-if="discussionsByLineCode[rightLineCode].length"
:discussions="discussionsByLineCode[rightLineCode]"
v-if="rightDiscussions.length"
:discussions="rightDiscussions"
/>
</div>
<diff-line-note-form
v-if="diffLineCommentForms[rightLineCode] &&
diffLineCommentForms[rightLineCode] && line.right.type"
v-if="showRightSideCommentForm"
:diff-file-hash="diffFileHash"
:line="line.right"
:note-target-line="line.right"
......
......@@ -36,6 +36,16 @@ export default {
required: false,
default: false,
},
leftDiscussions: {
type: Array,
required: false,
default: () => [],
},
rightDiscussions: {
type: Array,
required: false,
default: () => [],
},
},
data() {
return {
......@@ -116,6 +126,7 @@ export default {
:is-hover="isLeftHover"
:show-comment-button="true"
:diff-view-type="parallelDiffViewType"
:discussions="leftDiscussions"
class="diff-line-num old_line"
/>
<td
......@@ -136,6 +147,7 @@ export default {
:is-hover="isRightHover"
:show-comment-button="true"
:diff-view-type="parallelDiffViewType"
:discussions="rightDiscussions"
class="diff-line-num new_line"
/>
<td
......
......@@ -21,8 +21,11 @@ export default {
},
},
computed: {
...mapGetters('diffs', ['commitId']),
...mapGetters(['discussionsByLineCode']),
...mapGetters('diffs', [
'commitId',
'singleDiscussionByLineCode',
'shouldRenderParallelCommentRow',
]),
...mapState({
diffLineCommentForms: state => state.diffs.diffLineCommentForms,
}),
......@@ -53,29 +56,9 @@ export default {
},
},
methods: {
shouldRenderCommentRow(line) {
const leftLineCode = line.left.lineCode;
const rightLineCode = line.right.lineCode;
const discussions = this.discussionsByLineCode;
const leftDiscussions = discussions[leftLineCode];
const rightDiscussions = discussions[rightLineCode];
const hasDiscussion = leftDiscussions || rightDiscussions;
const hasExpandedDiscussionOnLeft = leftDiscussions
? leftDiscussions.every(discussion => discussion.expanded)
: false;
const hasExpandedDiscussionOnRight = rightDiscussions
? rightDiscussions.every(discussion => discussion.expanded)
: false;
if (hasDiscussion && (hasExpandedDiscussionOnLeft || hasExpandedDiscussionOnRight)) {
return true;
}
const hasCommentFormOnLeft = this.diffLineCommentForms[leftLineCode];
const hasCommentFormOnRight = this.diffLineCommentForms[rightLineCode];
return hasCommentFormOnLeft || hasCommentFormOnRight;
discussionsByLine(line, leftOrRight) {
return line[leftOrRight] && line[leftOrRight].lineCode !== undefined ?
this.singleDiscussionByLineCode(line[leftOrRight].lineCode) : [];
},
},
};
......@@ -98,13 +81,17 @@ export default {
:line="line"
:is-bottom="index + 1 === diffLinesLength"
:key="index"
:left-discussions="discussionsByLine(line, 'left')"
:right-discussions="discussionsByLine(line, 'right')"
/>
<parallel-diff-comment-row
v-if="shouldRenderCommentRow(line)"
v-if="shouldRenderParallelCommentRow(line)"
:key="`dcr-${index}`"
:line="line"
:diff-file-hash="diffFile.fileHash"
:line-index="index"
:left-discussions="discussionsByLine(line, 'left')"
:right-discussions="discussionsByLine(line, 'right')"
/>
</template>
</tbody>
......
......@@ -64,6 +64,47 @@ export const getDiffFileDiscussions = (state, getters, rootState, rootGetters) =
discussion.diff_discussion && _.isEqual(discussion.diff_file.file_hash, diff.fileHash),
) || [];
export const singleDiscussionByLineCode = (state, getters, rootState, rootGetters) => lineCode => {
if (!lineCode || lineCode === undefined) return [];
const discussions = rootGetters.discussionsByLineCode;
return discussions[lineCode] || [];
};
export const shouldRenderParallelCommentRow = (state, getters) => line => {
const leftLineCode = line.left.lineCode;
const rightLineCode = line.right.lineCode;
const leftDiscussions = getters.singleDiscussionByLineCode(leftLineCode);
const rightDiscussions = getters.singleDiscussionByLineCode(rightLineCode);
const hasDiscussion = leftDiscussions.length || rightDiscussions.length;
const hasExpandedDiscussionOnLeft = leftDiscussions.length
? leftDiscussions.every(discussion => discussion.expanded)
: false;
const hasExpandedDiscussionOnRight = rightDiscussions.length
? rightDiscussions.every(discussion => discussion.expanded)
: false;
if (hasDiscussion && (hasExpandedDiscussionOnLeft || hasExpandedDiscussionOnRight)) {
return true;
}
const hasCommentFormOnLeft = state.diffLineCommentForms[leftLineCode];
const hasCommentFormOnRight = state.diffLineCommentForms[rightLineCode];
return hasCommentFormOnLeft || hasCommentFormOnRight;
};
export const shouldRenderInlineCommentRow = (state, getters) => line => {
if (state.diffLineCommentForms[line.lineCode]) return true;
const lineDiscussions = getters.singleDiscussionByLineCode(line.lineCode);
if (lineDiscussions.length === 0) {
return false;
}
return lineDiscussions.every(discussion => discussion.expanded);
};
// prevent babel-plugin-rewire from generating an invalid default during karma∂ tests
export const getDiffFileByHash = state => fileHash =>
state.diffFiles.find(file => file.fileHash === fileHash);
......
......@@ -173,3 +173,24 @@ export function trimFirstCharOfLineContent(line = {}) {
return parsedLine;
}
export function getDiffRefsByLineCode(diffFiles) {
return diffFiles.reduce((acc, diffFile) => {
const { baseSha, headSha, startSha } = diffFile.diffRefs;
const { newPath, oldPath } = diffFile;
// We can only use highlightedDiffLines to create the map of diff lines because
// highlightedDiffLines will also include every parallel diff line in it.
if (diffFile.highlightedDiffLines) {
diffFile.highlightedDiffLines.forEach(line => {
const { lineCode, oldLine, newLine } = line;
if (lineCode) {
acc[lineCode] = { baseSha, headSha, startSha, newPath, oldPath, oldLine, newLine };
}
});
}
return acc;
}, {});
}
......@@ -616,7 +616,11 @@ GitLabDropdown = (function() {
}
if (this.options.opened) {
this.options.opened.call(this, e);
if (this.options.preserveContext) {
this.options.opened(e);
} else {
this.options.opened.call(this, e);
}
}
return this.dropdown.trigger('shown.gl.dropdown');
......
......@@ -78,17 +78,10 @@ export default {
>
<div
:class="{ 'project-row-contents': !isGroup }"
class="group-row-contents">
<item-actions
v-if="isGroup"
:group="group"
:parent-group="parentGroup"
/>
<item-stats
:item="group"
/>
class="group-row-contents d-flex justify-content-end align-items-center"
>
<div
class="folder-toggle-wrap"
class="folder-toggle-wrap append-right-4 d-flex align-items-center"
>
<item-caret
:is-group-open="group.isOpen"
......@@ -100,7 +93,7 @@ export default {
</div>
<div
:class="{ 'content-loading': group.isChildrenLoading }"
class="avatar-container prepend-top-8 prepend-left-5 s24 d-none d-sm-block"
class="avatar-container s24 d-none d-sm-block"
>
<a
:href="group.relativePath"
......@@ -120,32 +113,46 @@ export default {
</a>
</div>
<div
class="title namespace-title"
class="group-text flex-grow"
>
<a
v-tooltip
:href="group.relativePath"
:title="group.fullName"
class="no-expand"
data-placement="bottom"
>{{
// ending bracket must be by closing tag to prevent
// link hover text-decoration from over-extending
group.name
}}</a>
<span
v-if="group.permission"
class="user-access-role"
<div
class="title namespace-title append-right-8"
>
{{ group.permission }}
</span>
</div>
<div
v-if="group.description"
class="description">
<span v-html="group.description">
</span>
<a
v-tooltip
:href="group.relativePath"
:title="group.fullName"
class="no-expand"
data-placement="bottom"
>{{
// ending bracket must be by closing tag to prevent
// link hover text-decoration from over-extending
group.name
}}</a>
<span
v-if="group.permission"
class="user-access-role"
>
{{ group.permission }}
</span>
</div>
<div
v-if="group.description"
class="description"
>
<span v-html="group.description">
</span>
</div>
</div>
<item-stats
:item="group"
class="group-stats prepend-top-2"
/>
<item-actions
v-if="isGroup"
:group="group"
:parent-group="parentGroup"
/>
</div>
<group-folder
v-if="group.isOpen && hasChildren"
......
......@@ -39,7 +39,7 @@ export default class LabelsSelect {
showNo = $dropdown.data('showNo');
showAny = $dropdown.data('showAny');
showMenuAbove = $dropdown.data('showMenuAbove');
defaultLabel = $dropdown.data('defaultLabel');
defaultLabel = $dropdown.data('defaultLabel') || 'Label';
abilityName = $dropdown.data('abilityName');
$selectbox = $dropdown.closest('.selectbox');
$block = $selectbox.closest('.block');
......@@ -244,21 +244,21 @@ export default class LabelsSelect {
var $dropdownInputField = $dropdownParent.find('.dropdown-input-field');
var isSelected = el !== null ? el.hasClass('is-active') : false;
var { title } = selected;
var title = selected ? selected.title : null;
var selectedLabels = this.selected;
if ($dropdownInputField.length && $dropdownInputField.val().length) {
$dropdownParent.find('.dropdown-input-clear').trigger('click');
}
if (selected.id === 0) {
if (selected && selected.id === 0) {
this.selected = [];
return 'No Label';
}
else if (isSelected) {
this.selected.push(title);
}
else {
else if (!isSelected && title) {
var index = this.selected.indexOf(title);
this.selected.splice(index, 1);
}
......@@ -409,6 +409,14 @@ export default class LabelsSelect {
}
}
},
opened: function(e) {
if ($dropdown.hasClass('js-issue-board-sidebar')) {
const previousSelection = $dropdown.attr('data-selected');
this.selected = previousSelection ? previousSelection.split(',') : [];
$dropdown.data('glDropdown').updateLabel();
}
},
preserveContext: true,
});
// Set dropdown data
......
......@@ -19,11 +19,17 @@ export default class LazyLoader {
scrollContainer.addEventListener('load', () => this.loadCheck());
}
searchLazyImages() {
this.lazyImages = [].slice.call(document.querySelectorAll('.lazy'));
const that = this;
requestIdleCallback(
() => {
that.lazyImages = [].slice.call(document.querySelectorAll('.lazy'));
if (this.lazyImages.length) {
this.checkElementsInView();
}
if (that.lazyImages.length) {
that.checkElementsInView();
}
},
{ timeout: 500 },
);
}
startContentObserver() {
const contentNode = document.querySelector(this.observerNode) || document.querySelector('body');
......@@ -56,7 +62,9 @@ export default class LazyLoader {
const imgBound = imgTop + imgBoundRect.height;
if (scrollTop < imgBound && visHeight > imgTop) {
LazyLoader.loadImage(selectedImage);
requestAnimationFrame(() => {
LazyLoader.loadImage(selectedImage);
});
return false;
}
......
......@@ -42,6 +42,9 @@ export default {
},
methods: {
onImgLoad() {
requestIdleCallback(this.calculateImgSize, { timeout: 1000 });
},
calculateImgSize() {
const { contentImg } = this.$refs;
if (contentImg) {
......
......@@ -459,6 +459,7 @@ img.emoji {
.prepend-left-15 { margin-left: 15px; }
.prepend-left-default { margin-left: $gl-padding; }
.prepend-left-20 { margin-left: 20px; }
.append-right-4 { margin-right: 4px; }
.append-right-5 { margin-right: 5px; }
.append-right-8 { margin-right: 8px; }
.append-right-10 { margin-right: 10px; }
......@@ -478,3 +479,5 @@ img.emoji {
.center { text-align: center; }
.vertical-align-middle { vertical-align: middle; }
.flex-align-self-center { align-self: center; }
.flex-grow { flex-grow: 1; }
.flex-no-shrink { flex-shrink: 0; }
......@@ -339,9 +339,8 @@ table.pipeline-project-metrics tr td {
}
.folder-toggle-wrap {
float: left;
line-height: $list-text-height;
font-size: 0;
flex-shrink: 0;
span {
font-size: $gl-font-size;
......@@ -357,7 +356,7 @@ table.pipeline-project-metrics tr td {
width: 15px;
svg {
margin-bottom: 2px;
margin-bottom: 1px;
}
}
......@@ -440,9 +439,17 @@ table.pipeline-project-metrics tr td {
cursor: pointer;
}
.avatar-container > a {
width: 100%;
text-decoration: none;
.group-text {
min-width: 0; // allows for truncated text within flex children
}
.avatar-container {
flex-shrink: 0;
> a {
width: 100%;
text-decoration: none;
}
}
&.has-more-items {
......@@ -450,9 +457,18 @@ table.pipeline-project-metrics tr td {
padding: 20px 10px;
}
.description {
p {
@include str-truncated;
max-width: none;
}
}
.stats {
position: relative;
line-height: 46px;
line-height: normal;
flex-shrink: 0;
> span {
display: inline-flex;
......@@ -471,14 +487,20 @@ table.pipeline-project-metrics tr td {
}
.controls {
margin-left: 5px;
flex-shrink: 0;
> .btn {
margin-right: $btn-margin-5;
margin: 0 0 0 $btn-margin-5;
}
}
}
@include media-breakpoint-down(xs) {
.group-stats {
display: none;
}
}
.project-row-contents .stats {
line-height: inherit;
......@@ -500,18 +522,6 @@ table.pipeline-project-metrics tr td {
}
}
ul.group-list-tree {
li.group-row {
> .group-row-contents .title {
line-height: $list-text-height;
}
&.has-description > .group-row-contents .title {
line-height: inherit;
}
}
}
.js-groups-list-holder {
.groups-list-loading {
font-size: 34px;
......
......@@ -20,13 +20,13 @@ class ApplicationController < ActionController::Base
before_action :ldap_security_check
before_action :sentry_context
before_action :default_headers
before_action :add_gon_variables, unless: :peek_request?
before_action :add_gon_variables, unless: [:peek_request?, :json_request?]
before_action :configure_permitted_parameters, if: :devise_controller?
before_action :require_email, unless: :devise_controller?
around_action :set_locale
after_action :set_page_title_header, if: -> { request.format == :json }
after_action :set_page_title_header, if: :json_request?
protect_from_forgery with: :exception, prepend: true
......@@ -432,6 +432,10 @@ class ApplicationController < ActionController::Base
request.path.start_with?('/-/peek')
end
def json_request?
request.format.json?
end
def should_enforce_terms?
return false unless Gitlab::CurrentSettings.current_application_settings.enforce_terms
......
......@@ -5,7 +5,7 @@ class Projects::RunnersController < Projects::ApplicationController
layout 'project_settings'
def index
redirect_to project_settings_ci_cd_path(@project)
redirect_to project_settings_ci_cd_path(@project, anchor: 'js-runners-settings')
end
def edit
......@@ -50,13 +50,13 @@ class Projects::RunnersController < Projects::ApplicationController
def toggle_shared_runners
project.toggle!(:shared_runners_enabled)
redirect_to project_settings_ci_cd_path(@project)
redirect_to project_settings_ci_cd_path(@project, anchor: 'js-runners-settings')
end
def toggle_group_runners
project.toggle_ci_cd_settings!(:group_runners_enabled)
redirect_to project_settings_ci_cd_path(@project)
redirect_to project_settings_ci_cd_path(@project, anchor: 'js-runners-settings')
end
protected
......
......@@ -7,7 +7,7 @@ class Projects::TriggersController < Projects::ApplicationController
layout 'project_settings'
def index
redirect_to project_settings_ci_cd_path(@project)
redirect_to project_settings_ci_cd_path(@project, anchor: 'js-pipeline-triggers')
end
def create
......@@ -19,7 +19,7 @@ class Projects::TriggersController < Projects::ApplicationController
flash[:alert] = 'You could not create a new trigger.'
end
redirect_to project_settings_ci_cd_path(@project)
redirect_to project_settings_ci_cd_path(@project, anchor: 'js-pipeline-triggers')
end
def take_ownership
......@@ -29,7 +29,7 @@ class Projects::TriggersController < Projects::ApplicationController
flash[:alert] = 'You could not take ownership of trigger.'
end
redirect_to project_settings_ci_cd_path(@project)
redirect_to project_settings_ci_cd_path(@project, anchor: 'js-pipeline-triggers')
end
def edit
......@@ -37,7 +37,7 @@ class Projects::TriggersController < Projects::ApplicationController
def update
if trigger.update(trigger_params)
redirect_to project_settings_ci_cd_path(@project), notice: 'Trigger was successfully updated.'
redirect_to project_settings_ci_cd_path(@project, anchor: 'js-pipeline-triggers'), notice: 'Trigger was successfully updated.'
else
render action: "edit"
end
......@@ -50,7 +50,7 @@ class Projects::TriggersController < Projects::ApplicationController
flash[:alert] = "Could not remove the trigger."
end
redirect_to project_settings_ci_cd_path(@project), status: :found
redirect_to project_settings_ci_cd_path(@project, anchor: 'js-pipeline-triggers'), status: :found
end
private
......
......@@ -153,7 +153,7 @@ module Issuable
end
# Break ties with the ID column for pagination
sorted.order(id: :desc)
sorted.with_order_id_desc
end
def order_due_date_and_labels_priority(excluded_labels: [])
......
......@@ -6,6 +6,7 @@ module Sortable
extend ActiveSupport::Concern
included do
scope :with_order_id_desc, -> { order(id: :desc) }
scope :order_id_desc, -> { reorder(id: :desc) }
scope :order_id_asc, -> { reorder(id: :asc) }
scope :order_created_desc, -> { reorder(created_at: :desc) }
......
......@@ -150,22 +150,25 @@ class Milestone < ActiveRecord::Base
end
def self.sort_by_attribute(method)
case method.to_s
when 'due_date_asc'
reorder(Gitlab::Database.nulls_last_order('due_date', 'ASC'))
when 'due_date_desc'
reorder(Gitlab::Database.nulls_last_order('due_date', 'DESC'))
when 'name_asc'
reorder(Arel::Nodes::Ascending.new(arel_table[:title].lower))
when 'name_desc'
reorder(Arel::Nodes::Descending.new(arel_table[:title].lower))
when 'start_date_asc'
reorder(Gitlab::Database.nulls_last_order('start_date', 'ASC'))
when 'start_date_desc'
reorder(Gitlab::Database.nulls_last_order('start_date', 'DESC'))
else
order_by(method)
end
sorted =
case method.to_s
when 'due_date_asc'
reorder(Gitlab::Database.nulls_last_order('due_date', 'ASC'))
when 'due_date_desc'
reorder(Gitlab::Database.nulls_last_order('due_date', 'DESC'))
when 'name_asc'
reorder(Arel::Nodes::Ascending.new(arel_table[:title].lower))
when 'name_desc'
reorder(Arel::Nodes::Descending.new(arel_table[:title].lower))
when 'start_date_asc'
reorder(Gitlab::Database.nulls_last_order('start_date', 'ASC'))
when 'start_date_desc'
reorder(Gitlab::Database.nulls_last_order('start_date', 'DESC'))
else
order_by(method)
end
sorted.with_order_id_desc
end
##
......
.row
.col-lg-12
= form_for @project, url: project_settings_ci_cd_path(@project) do |f|
= form_for @project, url: project_settings_ci_cd_path(@project, anchor: 'autodevops-settings') do |f|
= form_errors(@project)
%fieldset.builds-feature.js-auto-devops-settings
.form-group
......
.row.prepend-top-default
.col-lg-12
= form_for @project, url: project_settings_ci_cd_path(@project) do |f|
= form_for @project, url: project_settings_ci_cd_path(@project, anchor: 'js-general-pipeline-settings') do |f|
= form_errors(@project)
%fieldset.builds-feature
.form-group.append-bottom-default.js-secret-runner-token
......
......@@ -19,6 +19,7 @@
":value" => "label.id" }
.dropdown
%button.dropdown-menu-toggle.js-label-select.js-multiselect.js-issue-board-sidebar{ type: "button",
"v-bind:data-selected" => "selectedLabels",
data: { toggle: "dropdown",
field_name: "issue[label_names][]",
show_no: "true",
......@@ -28,7 +29,7 @@
namespace_path: @namespace_path,
project_path: @project.try(:path) } }
%span.dropdown-toggle-text
= _("Label")
{{ labelDropdownTitle }}
= icon('chevron-down')
.dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable
= render partial: "shared/issuable/label_page_default"
......
---
title: Board label edit dropdown shows incorrect selected labels summary
merge_request: 20673
author:
type: fixed
---
title: Solves group dashboard line height is too tall for group names.
merge_request: 21033
author:
type: fixed
---
title: Fix UI error whereby prometheus application status is updated
merge_request: 21029
author:
type: fixed
---
title: Fix missing and duplicates on project milestone listing page
merge_request: 21058
author:
type: fixed
---
title: Don't set gon variables in JSON requests
merge_request: 21016
author: Peter Leitzen
type: performance
---
title: Improve performance and memory footprint of Changes tab of Merge Requests
merge_request: 21028
author:
type: performance
# frozen_string_literal: true
# This is backport of https://github.com/rails/rails/pull/26815/files
# Enabled by default for every non-production environment
module ActiveRecord
class LogSubscriber
module VerboseQueryLogs
def debug(progname = nil, &block)
return unless super
log_query_source
end
def log_query_source
source_line, line_number = extract_callstack(caller_locations)
if source_line
if defined?(::Rails.root)
app_root = "#{::Rails.root}/".freeze
source_line = source_line.sub(app_root, "")
end
logger.debug(" ↳ #{source_line}:#{line_number}")
end
end
def extract_callstack(callstack)
line = callstack.find do |frame|
frame.absolute_path && !ignored_callstack(frame.absolute_path)
end
offending_line = line || callstack.first
[
offending_line.path,
offending_line.lineno,
offending_line.label
]
end
LOG_SUBSCRIBER_FILE = ActiveRecord::LogSubscriber.method(:logger).source_location.first
RAILS_GEM_ROOT = File.expand_path("../../../..", LOG_SUBSCRIBER_FILE) + "/"
APP_CONFIG_ROOT = File.expand_path("..", __dir__) + "/"
def ignored_callstack(path)
path.start_with?(APP_CONFIG_ROOT, RAILS_GEM_ROOT, RbConfig::CONFIG["rubylibdir"])
end
end
unless Gitlab.rails5?
prepend(VerboseQueryLogs) unless Rails.env.production?
end
end
end
......@@ -56,6 +56,57 @@ describe ApplicationController do
end
end
describe '#add_gon_variables' do
before do
Gon.clear
sign_in user
end
let(:json_response) { JSON.parse(response.body) }
controller(described_class) do
def index
render json: Gon.all_variables
end
end
shared_examples 'setting gon variables' do
it 'sets gon variables' do
get :index, format: format
expect(json_response.size).not_to be_zero
end
end
shared_examples 'not setting gon variables' do
it 'does not set gon variables' do
get :index, format: format
expect(json_response.size).to be_zero
end
end
context 'with html format' do
let(:format) { :html }
it_behaves_like 'setting gon variables'
context 'for peek requests' do
before do
request.path = '/-/peek'
end
it_behaves_like 'not setting gon variables'
end
end
context 'with json format' do
let(:format) { :json }
it_behaves_like 'not setting gon variables'
end
end
describe "#authenticate_user_from_personal_access_token!" do
before do
stub_authentication_activity_metrics(debug: false)
......
......@@ -42,16 +42,45 @@ describe Projects::MilestonesController do
describe "#index" do
context "as html" do
before do
get :index, namespace_id: project.namespace.id, project_id: project.id
def render_index(project:, page:)
get :index, namespace_id: project.namespace.id,
project_id: project.id,
page: page
end
it "queries only projects milestones" do
render_index project: project, page: 1
milestones = assigns(:milestones)
expect(milestones.count).to eq(1)
expect(milestones.where(project_id: nil)).to be_empty
end
it 'renders paginated milestones without missing or duplicates' do
allow(Milestone).to receive(:default_per_page).and_return(2)
create_list(:milestone, 5, project: project)
render_index project: project, page: 1
page_1_milestones = assigns(:milestones)
expect(page_1_milestones.size).to eq(2)
render_index project: project, page: 2
page_2_milestones = assigns(:milestones)
expect(page_2_milestones.size).to eq(2)
render_index project: project, page: 3
page_3_milestones = assigns(:milestones)
expect(page_3_milestones.size).to eq(2)
rendered_milestone_ids =
page_1_milestones.pluck(:id) +
page_2_milestones.pluck(:id) +
page_3_milestones.pluck(:id)
expect(rendered_milestone_ids)
.to match_array(project.milestones.pluck(:id))
end
end
context "as json" do
......
import Clusters from '~/clusters/clusters_bundle';
import {
APPLICATION_INSTALLABLE,
APPLICATION_INSTALLING,
APPLICATION_INSTALLED,
REQUEST_LOADING,
REQUEST_SUCCESS,
REQUEST_FAILURE,
APPLICATION_STATUS,
} from '~/clusters/constants';
import getSetTimeoutPromise from 'spec/helpers/set_timeout_promise_helper';
......@@ -84,7 +82,7 @@ describe('Clusters', () => {
it('does not show alert when things transition from initial null state to something', () => {
cluster.checkForNewInstalls(INITIAL_APP_MAP, {
...INITIAL_APP_MAP,
helm: { status: APPLICATION_INSTALLABLE, title: 'Helm Tiller' },
helm: { status: APPLICATION_STATUS.INSTALLABLE, title: 'Helm Tiller' },
});
const flashMessage = document.querySelector('.js-cluster-application-notice .flash-text');
......@@ -94,10 +92,10 @@ describe('Clusters', () => {
it('shows an alert when something gets newly installed', () => {
cluster.checkForNewInstalls({
...INITIAL_APP_MAP,
helm: { status: APPLICATION_INSTALLING, title: 'Helm Tiller' },
helm: { status: APPLICATION_STATUS.INSTALLING, title: 'Helm Tiller' },
}, {
...INITIAL_APP_MAP,
helm: { status: APPLICATION_INSTALLED, title: 'Helm Tiller' },
helm: { status: APPLICATION_STATUS.INSTALLED, title: 'Helm Tiller' },
});
const flashMessage = document.querySelector('.js-cluster-application-notice .flash-text');
......@@ -108,12 +106,12 @@ describe('Clusters', () => {
it('shows an alert when multiple things gets newly installed', () => {
cluster.checkForNewInstalls({
...INITIAL_APP_MAP,
helm: { status: APPLICATION_INSTALLING, title: 'Helm Tiller' },
ingress: { status: APPLICATION_INSTALLABLE, title: 'Ingress' },
helm: { status: APPLICATION_STATUS.INSTALLING, title: 'Helm Tiller' },
ingress: { status: APPLICATION_STATUS.INSTALLABLE, title: 'Ingress' },
}, {
...INITIAL_APP_MAP,
helm: { status: APPLICATION_INSTALLED, title: 'Helm Tiller' },
ingress: { status: APPLICATION_INSTALLED, title: 'Ingress' },
helm: { status: APPLICATION_STATUS.INSTALLED, title: 'Helm Tiller' },
ingress: { status: APPLICATION_STATUS.INSTALLED, title: 'Ingress' },
});
const flashMessage = document.querySelector('.js-cluster-application-notice .flash-text');
......
import Vue from 'vue';
import eventHub from '~/clusters/event_hub';
import {
APPLICATION_NOT_INSTALLABLE,
APPLICATION_SCHEDULED,
APPLICATION_INSTALLABLE,
APPLICATION_INSTALLING,
APPLICATION_INSTALLED,
APPLICATION_ERROR,
APPLICATION_STATUS,
REQUEST_LOADING,
REQUEST_SUCCESS,
REQUEST_FAILURE,
......@@ -62,10 +57,10 @@ describe('Application Row', () => {
expect(vm.installButtonLabel).toBeUndefined();
});
it('has disabled "Install" when APPLICATION_NOT_INSTALLABLE', () => {
it('has disabled "Install" when APPLICATION_STATUS.NOT_INSTALLABLE', () => {
vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE,
status: APPLICATION_NOT_INSTALLABLE,
status: APPLICATION_STATUS.NOT_INSTALLABLE,
});
expect(vm.installButtonLabel).toEqual('Install');
......@@ -73,10 +68,10 @@ describe('Application Row', () => {
expect(vm.installButtonDisabled).toEqual(true);
});
it('has enabled "Install" when APPLICATION_INSTALLABLE', () => {
it('has enabled "Install" when APPLICATION_STATUS.INSTALLABLE', () => {
vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE,
status: APPLICATION_INSTALLABLE,
status: APPLICATION_STATUS.INSTALLABLE,
});
expect(vm.installButtonLabel).toEqual('Install');
......@@ -84,10 +79,10 @@ describe('Application Row', () => {
expect(vm.installButtonDisabled).toEqual(false);
});
it('has loading "Installing" when APPLICATION_SCHEDULED', () => {
it('has loading "Installing" when APPLICATION_STATUS.SCHEDULED', () => {
vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE,
status: APPLICATION_SCHEDULED,
status: APPLICATION_STATUS.SCHEDULED,
});
expect(vm.installButtonLabel).toEqual('Installing');
......@@ -95,10 +90,10 @@ describe('Application Row', () => {
expect(vm.installButtonDisabled).toEqual(true);
});
it('has loading "Installing" when APPLICATION_INSTALLING', () => {
it('has loading "Installing" when APPLICATION_STATUS.INSTALLING', () => {
vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE,
status: APPLICATION_INSTALLING,
status: APPLICATION_STATUS.INSTALLING,
});
expect(vm.installButtonLabel).toEqual('Installing');
......@@ -106,10 +101,10 @@ describe('Application Row', () => {
expect(vm.installButtonDisabled).toEqual(true);
});
it('has disabled "Installed" when APPLICATION_INSTALLED', () => {
it('has disabled "Installed" when APPLICATION_STATUS.INSTALLED', () => {
vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE,
status: APPLICATION_INSTALLED,
status: APPLICATION_STATUS.INSTALLED,
});
expect(vm.installButtonLabel).toEqual('Installed');
......@@ -117,10 +112,10 @@ describe('Application Row', () => {
expect(vm.installButtonDisabled).toEqual(true);
});
it('has enabled "Install" when APPLICATION_ERROR', () => {
it('has enabled "Install" when APPLICATION_STATUS.ERROR', () => {
vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE,
status: APPLICATION_ERROR,
status: APPLICATION_STATUS.ERROR,
});
expect(vm.installButtonLabel).toEqual('Install');
......@@ -131,7 +126,7 @@ describe('Application Row', () => {
it('has loading "Install" when REQUEST_LOADING', () => {
vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE,
status: APPLICATION_INSTALLABLE,
status: APPLICATION_STATUS.INSTALLABLE,
requestStatus: REQUEST_LOADING,
});
......@@ -143,7 +138,7 @@ describe('Application Row', () => {
it('has disabled "Install" when REQUEST_SUCCESS', () => {
vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE,
status: APPLICATION_INSTALLABLE,
status: APPLICATION_STATUS.INSTALLABLE,
requestStatus: REQUEST_SUCCESS,
});
......@@ -155,7 +150,7 @@ describe('Application Row', () => {
it('has enabled "Install" when REQUEST_FAILURE (so you can try installing again)', () => {
vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE,
status: APPLICATION_INSTALLABLE,
status: APPLICATION_STATUS.INSTALLABLE,
requestStatus: REQUEST_FAILURE,
});
......@@ -168,7 +163,7 @@ describe('Application Row', () => {
spyOn(eventHub, '$emit');
vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE,
status: APPLICATION_INSTALLABLE,
status: APPLICATION_STATUS.INSTALLABLE,
});
const installButton = vm.$el.querySelector('.js-cluster-application-install-button');
......@@ -184,7 +179,7 @@ describe('Application Row', () => {
spyOn(eventHub, '$emit');
vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE,
status: APPLICATION_INSTALLABLE,
status: APPLICATION_STATUS.INSTALLABLE,
installApplicationRequestParams: { hostname: 'jupyter' },
});
const installButton = vm.$el.querySelector('.js-cluster-application-install-button');
......@@ -201,7 +196,7 @@ describe('Application Row', () => {
spyOn(eventHub, '$emit');
vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE,
status: APPLICATION_INSTALLING,
status: APPLICATION_STATUS.INSTALLING,
});
const installButton = vm.$el.querySelector('.js-cluster-application-install-button');
......@@ -225,11 +220,11 @@ describe('Application Row', () => {
expect(generalErrorMessage).toBeNull();
});
it('shows status reason when APPLICATION_ERROR', () => {
it('shows status reason when APPLICATION_STATUS.ERROR', () => {
const statusReason = 'We broke it 0.0';
vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE,
status: APPLICATION_ERROR,
status: APPLICATION_STATUS.ERROR,
statusReason,
});
const generalErrorMessage = vm.$el.querySelector('.js-cluster-application-general-error-message');
......@@ -243,7 +238,7 @@ describe('Application Row', () => {
const requestReason = 'We broke thre request 0.0';
vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE,
status: APPLICATION_INSTALLABLE,
status: APPLICATION_STATUS.INSTALLABLE,
requestStatus: REQUEST_FAILURE,
requestReason,
});
......
import {
APPLICATION_INSTALLED,
APPLICATION_INSTALLABLE,
APPLICATION_INSTALLING,
APPLICATION_ERROR,
} from '~/clusters/constants';
import { APPLICATION_STATUS } from '~/clusters/constants';
const CLUSTERS_MOCK_DATA = {
GET: {
......@@ -13,25 +8,25 @@ const CLUSTERS_MOCK_DATA = {
status_reason: 'Failed to request to CloudPlatform.',
applications: [{
name: 'helm',
status: APPLICATION_INSTALLABLE,
status: APPLICATION_STATUS.INSTALLABLE,
status_reason: null,
}, {
name: 'ingress',
status: APPLICATION_ERROR,
status: APPLICATION_STATUS.ERROR,
status_reason: 'Cannot connect',
external_ip: null,
}, {
name: 'runner',
status: APPLICATION_INSTALLING,
status: APPLICATION_STATUS.INSTALLING,
status_reason: null,
},
{
name: 'prometheus',
status: APPLICATION_ERROR,
status: APPLICATION_STATUS.ERROR,
status_reason: 'Cannot connect',
}, {
name: 'jupyter',
status: APPLICATION_INSTALLING,
status: APPLICATION_STATUS.INSTALLING,
status_reason: 'Cannot connect',
}],
},
......@@ -42,25 +37,25 @@ const CLUSTERS_MOCK_DATA = {
status_reason: 'Failed to request to CloudPlatform.',
applications: [{
name: 'helm',
status: APPLICATION_INSTALLED,
status: APPLICATION_STATUS.INSTALLED,
status_reason: null,
}, {
name: 'ingress',
status: APPLICATION_INSTALLED,
status: APPLICATION_STATUS.INSTALLED,
status_reason: 'Cannot connect',
external_ip: '1.1.1.1',
}, {
name: 'runner',
status: APPLICATION_INSTALLING,
status: APPLICATION_STATUS.INSTALLING,
status_reason: null,
},
{
name: 'prometheus',
status: APPLICATION_ERROR,
status: APPLICATION_STATUS.ERROR,
status_reason: 'Cannot connect',
}, {
name: 'jupyter',
status: APPLICATION_INSTALLABLE,
status: APPLICATION_STATUS.INSTALLABLE,
status_reason: 'Cannot connect',
}],
},
......
import ClustersStore from '~/clusters/stores/clusters_store';
import { APPLICATION_INSTALLING } from '~/clusters/constants';
import { APPLICATION_STATUS } from '~/clusters/constants';
import { CLUSTERS_MOCK_DATA } from '../services/mock_data';
describe('Clusters Store', () => {
......@@ -35,7 +35,7 @@ describe('Clusters Store', () => {
it('should store new request status', () => {
expect(store.state.applications.helm.requestStatus).toEqual(null);
const newStatus = APPLICATION_INSTALLING;
const newStatus = APPLICATION_STATUS.INSTALLING;
store.updateAppProperty('helm', 'requestStatus', newStatus);
expect(store.state.applications.helm.requestStatus).toEqual(newStatus);
......
......@@ -48,7 +48,11 @@ describe('DiffLineGutterContent', () => {
it('should return discussions for the given lineCode', () => {
const { lineCode } = getDiffFileMock().highlightedDiffLines[1];
const component = createComponent({ lineCode, showCommentButton: true });
const component = createComponent({
lineCode,
showCommentButton: true,
discussions: getDiscussionsMockData(),
});
setDiscussions(component);
......
......@@ -184,6 +184,104 @@ describe('Diffs Module Getters', () => {
});
});
describe('singleDiscussionByLineCode', () => {
it('returns found discussion per line Code', () => {
const discussionsMock = {};
discussionsMock.ABC = discussionMock;
expect(
getters.singleDiscussionByLineCode(localState, {}, null, {
discussionsByLineCode: () => discussionsMock,
})('DEF'),
).toEqual([]);
});
it('returns empty array when no discussions match', () => {
expect(
getters.singleDiscussionByLineCode(localState, {}, null, {
discussionsByLineCode: () => {},
})('DEF'),
).toEqual([]);
});
});
describe('shouldRenderParallelCommentRow', () => {
let line;
beforeEach(() => {
line = {};
line.left = {
lineCode: 'ABC',
};
line.right = {
lineCode: 'DEF',
};
});
it('returns true when discussion is expanded', () => {
discussionMock.expanded = true;
expect(
getters.shouldRenderParallelCommentRow(localState, {
singleDiscussionByLineCode: () => [discussionMock],
})(line),
).toEqual(true);
});
it('returns false when no discussion was found', () => {
localState.diffLineCommentForms.ABC = false;
localState.diffLineCommentForms.DEF = false;
expect(
getters.shouldRenderParallelCommentRow(localState, {
singleDiscussionByLineCode: () => [],
})(line),
).toEqual(false);
});
it('returns true when discussionForm was found', () => {
localState.diffLineCommentForms.ABC = {};
expect(
getters.shouldRenderParallelCommentRow(localState, {
singleDiscussionByLineCode: () => [discussionMock],
})(line),
).toEqual(true);
});
});
describe('shouldRenderInlineCommentRow', () => {
it('returns true when diffLineCommentForms has form', () => {
localState.diffLineCommentForms.ABC = {};
expect(
getters.shouldRenderInlineCommentRow(localState)({
lineCode: 'ABC',
}),
).toEqual(true);
});
it('returns false when no line discussions were found', () => {
expect(
getters.shouldRenderInlineCommentRow(localState, {
singleDiscussionByLineCode: () => [],
})('DEF'),
).toEqual(false);
});
it('returns true if all found discussions are expanded', () => {
discussionMock.expanded = true;
expect(
getters.shouldRenderInlineCommentRow(localState, {
singleDiscussionByLineCode: () => [discussionMock],
})('ABC'),
).toEqual(true);
});
});
describe('getDiffFileDiscussions', () => {
it('returns an array with discussions when fileHash matches and the discussion belongs to a diff', () => {
discussionMock.diff_file.file_hash = diffFileMock.fileHash;
......
......@@ -170,8 +170,6 @@ describe('ImageDiffViewer', () => {
vm.$el.querySelector('.view-modes-menu li:nth-child(3)').click();
vm.$nextTick(() => {
expect(vm.$el.querySelector('.dragger').style.left).toBe('100px');
dragSlider(vm.$el.querySelector('.dragger'));
vm.$nextTick(() => {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment