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({ ...@@ -53,6 +53,16 @@ gl.issueBoards.BoardSidebar = Vue.extend({
canRemove() { canRemove() {
return !this.list.preset; 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: { watch: {
detail: { detail: {
......
...@@ -6,7 +6,7 @@ import Poll from '../lib/utils/poll'; ...@@ -6,7 +6,7 @@ import Poll from '../lib/utils/poll';
import initSettingsPanels from '../settings_panels'; import initSettingsPanels from '../settings_panels';
import eventHub from './event_hub'; import eventHub from './event_hub';
import { import {
APPLICATION_INSTALLED, APPLICATION_STATUS,
REQUEST_LOADING, REQUEST_LOADING,
REQUEST_SUCCESS, REQUEST_SUCCESS,
REQUEST_FAILURE, REQUEST_FAILURE,
...@@ -177,8 +177,8 @@ export default class Clusters { ...@@ -177,8 +177,8 @@ export default class Clusters {
checkForNewInstalls(prevApplicationMap, newApplicationMap) { checkForNewInstalls(prevApplicationMap, newApplicationMap) {
const appTitles = Object.keys(newApplicationMap) const appTitles = Object.keys(newApplicationMap)
.filter(appId => newApplicationMap[appId].status === APPLICATION_INSTALLED && .filter(appId => newApplicationMap[appId].status === APPLICATION_STATUS.INSTALLED &&
prevApplicationMap[appId].status !== APPLICATION_INSTALLED && prevApplicationMap[appId].status !== APPLICATION_STATUS.INSTALLED &&
prevApplicationMap[appId].status !== null) prevApplicationMap[appId].status !== null)
.map(appId => newApplicationMap[appId].title); .map(appId => newApplicationMap[appId].title);
......
...@@ -4,12 +4,7 @@ ...@@ -4,12 +4,7 @@
import eventHub from '../event_hub'; import eventHub from '../event_hub';
import loadingButton from '../../vue_shared/components/loading_button.vue'; import loadingButton from '../../vue_shared/components/loading_button.vue';
import { import {
APPLICATION_NOT_INSTALLABLE, APPLICATION_STATUS,
APPLICATION_SCHEDULED,
APPLICATION_INSTALLABLE,
APPLICATION_INSTALLING,
APPLICATION_INSTALLED,
APPLICATION_ERROR,
REQUEST_LOADING, REQUEST_LOADING,
REQUEST_SUCCESS, REQUEST_SUCCESS,
REQUEST_FAILURE, REQUEST_FAILURE,
...@@ -59,49 +54,57 @@ ...@@ -59,49 +54,57 @@
}, },
}, },
computed: { computed: {
isUnknownStatus() {
return !this.isKnownStatus && this.status !== null;
},
isKnownStatus() {
return Object.values(APPLICATION_STATUS).includes(this.status);
},
rowJsClass() { rowJsClass() {
return `js-cluster-application-row-${this.id}`; return `js-cluster-application-row-${this.id}`;
}, },
installButtonLoading() { installButtonLoading() {
return !this.status || return !this.status ||
this.status === APPLICATION_SCHEDULED || this.status === APPLICATION_STATUS.SCHEDULED ||
this.status === APPLICATION_INSTALLING || this.status === APPLICATION_STATUS.INSTALLING ||
this.requestStatus === REQUEST_LOADING; this.requestStatus === REQUEST_LOADING;
}, },
installButtonDisabled() { 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 // we already made a request to install and are just waiting for the real-time
// to sync up. // to sync up.
return (this.status !== APPLICATION_INSTALLABLE return ((this.status !== APPLICATION_STATUS.INSTALLABLE
&& this.status !== APPLICATION_ERROR) || && this.status !== APPLICATION_STATUS.ERROR) ||
this.requestStatus === REQUEST_LOADING || this.requestStatus === REQUEST_LOADING ||
this.requestStatus === REQUEST_SUCCESS; this.requestStatus === REQUEST_SUCCESS) && this.isKnownStatus;
}, },
installButtonLabel() { installButtonLabel() {
let label; let label;
if ( if (
this.status === APPLICATION_NOT_INSTALLABLE || this.status === APPLICATION_STATUS.NOT_INSTALLABLE ||
this.status === APPLICATION_INSTALLABLE || this.status === APPLICATION_STATUS.INSTALLABLE ||
this.status === APPLICATION_ERROR this.status === APPLICATION_STATUS.ERROR ||
this.isUnknownStatus
) { ) {
label = s__('ClusterIntegration|Install'); label = s__('ClusterIntegration|Install');
} else if (this.status === APPLICATION_SCHEDULED || } else if (this.status === APPLICATION_STATUS.SCHEDULED ||
this.status === APPLICATION_INSTALLING) { this.status === APPLICATION_STATUS.INSTALLING) {
label = s__('ClusterIntegration|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'); label = s__('ClusterIntegration|Installed');
} }
return label; return label;
}, },
showManageButton() { showManageButton() {
return this.manageLink && this.status === APPLICATION_INSTALLED; return this.manageLink && this.status === APPLICATION_STATUS.INSTALLED;
}, },
manageButtonLabel() { manageButtonLabel() {
return s__('ClusterIntegration|Manage'); return s__('ClusterIntegration|Manage');
}, },
hasError() { hasError() {
return this.status === APPLICATION_ERROR || return this.status === APPLICATION_STATUS.ERROR ||
this.requestStatus === REQUEST_FAILURE; this.requestStatus === REQUEST_FAILURE;
}, },
generalErrorDescription() { generalErrorDescription() {
...@@ -182,7 +185,7 @@ ...@@ -182,7 +185,7 @@
</div> </div>
</div> </div>
<div <div
v-if="hasError" v-if="hasError || isUnknownStatus"
class="gl-responsive-table-row-layout" class="gl-responsive-table-row-layout"
role="row" role="row"
> >
......
...@@ -3,7 +3,7 @@ import _ from 'underscore'; ...@@ -3,7 +3,7 @@ import _ from 'underscore';
import { s__, sprintf } from '../../locale'; import { s__, sprintf } from '../../locale';
import applicationRow from './application_row.vue'; import applicationRow from './application_row.vue';
import clipboardButton from '../../vue_shared/components/clipboard_button.vue'; import clipboardButton from '../../vue_shared/components/clipboard_button.vue';
import { APPLICATION_INSTALLED, INGRESS } from '../constants'; import { APPLICATION_STATUS, INGRESS } from '../constants';
export default { export default {
components: { components: {
...@@ -58,7 +58,7 @@ export default { ...@@ -58,7 +58,7 @@ export default {
return INGRESS; return INGRESS;
}, },
ingressInstalled() { ingressInstalled() {
return this.applications.ingress.status === APPLICATION_INSTALLED; return this.applications.ingress.status === APPLICATION_STATUS.INSTALLED;
}, },
ingressExternalIp() { ingressExternalIp() {
return this.applications.ingress.externalIp; return this.applications.ingress.externalIp;
...@@ -122,7 +122,7 @@ export default { ...@@ -122,7 +122,7 @@ export default {
); );
}, },
jupyterInstalled() { jupyterInstalled() {
return this.applications.jupyter.status === APPLICATION_INSTALLED; return this.applications.jupyter.status === APPLICATION_STATUS.INSTALLED;
}, },
jupyterHostname() { jupyterHostname() {
return this.applications.jupyter.hostname; return this.applications.jupyter.hostname;
......
// These need to match what is returned from the server // These need to match what is returned from the server
export const APPLICATION_NOT_INSTALLABLE = 'not_installable'; export const APPLICATION_STATUS = {
export const APPLICATION_INSTALLABLE = 'installable'; NOT_INSTALLABLE: 'not_installable',
export const APPLICATION_SCHEDULED = 'scheduled'; INSTALLABLE: 'installable',
export const APPLICATION_INSTALLING = 'installing'; SCHEDULED: 'scheduled',
export const APPLICATION_INSTALLED = 'installed'; INSTALLING: 'installing',
export const APPLICATION_ERROR = 'errored'; INSTALLED: 'installed',
UPDATED: 'updated',
ERROR: 'errored',
};
// These are only used client-side // These are only used client-side
export const REQUEST_LOADING = 'request-loading'; export const REQUEST_LOADING = 'request-loading';
......
...@@ -19,3 +19,4 @@ import './polyfills/custom_event'; ...@@ -19,3 +19,4 @@ import './polyfills/custom_event';
import './polyfills/element'; import './polyfills/element';
import './polyfills/event'; import './polyfills/event';
import './polyfills/nodelist'; 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 { ...@@ -71,13 +71,23 @@ export default {
required: false, required: false,
default: false, default: false,
}, },
isHover: {
type: Boolean,
required: false,
default: false,
},
discussions: {
type: Array,
required: false,
default: () => [],
},
}, },
computed: { computed: {
...mapState({ ...mapState({
diffViewType: state => state.diffs.diffViewType, diffViewType: state => state.diffs.diffViewType,
diffFiles: state => state.diffs.diffFiles, diffFiles: state => state.diffs.diffFiles,
}), }),
...mapGetters(['isLoggedIn', 'discussionsByLineCode']), ...mapGetters(['isLoggedIn']),
lineHref() { lineHref() {
return this.lineCode ? `#${this.lineCode}` : '#'; return this.lineCode ? `#${this.lineCode}` : '#';
}, },
...@@ -85,26 +95,22 @@ export default { ...@@ -85,26 +95,22 @@ export default {
return ( return (
this.isLoggedIn && this.isLoggedIn &&
this.showCommentButton && this.showCommentButton &&
this.isHover &&
!this.isMatchLine && !this.isMatchLine &&
!this.isContextLine && !this.isContextLine &&
!this.hasDiscussions && !this.isMetaLine &&
!this.isMetaLine !this.hasDiscussions
); );
}, },
discussions() {
return this.discussionsByLineCode[this.lineCode] || [];
},
hasDiscussions() { hasDiscussions() {
return this.discussions.length > 0; return this.discussions.length > 0;
}, },
shouldShowAvatarsOnGutter() { shouldShowAvatarsOnGutter() {
let render = this.hasDiscussions && this.showCommentButton;
if (!this.lineType && this.linePosition === LINE_POSITION_RIGHT) { if (!this.lineType && this.linePosition === LINE_POSITION_RIGHT) {
render = false; return false;
} }
return render; return this.showCommentButton && this.hasDiscussions;
}, },
}, },
methods: { methods: {
...@@ -176,7 +182,7 @@ export default { ...@@ -176,7 +182,7 @@ export default {
v-else v-else
> >
<button <button
v-show="shouldShowCommentButton" v-if="shouldShowCommentButton"
type="button" type="button"
class="add-diff-note js-add-diff-note-button" class="add-diff-note js-add-diff-note-button"
title="Add a comment to this line" title="Add a comment to this line"
......
...@@ -67,6 +67,11 @@ export default { ...@@ -67,6 +67,11 @@ export default {
required: false, required: false,
default: false, default: false,
}, },
discussions: {
type: Array,
required: false,
default: () => [],
},
}, },
computed: { computed: {
...mapGetters(['isLoggedIn']), ...mapGetters(['isLoggedIn']),
...@@ -132,10 +137,12 @@ export default { ...@@ -132,10 +137,12 @@ export default {
:line-number="lineNumber" :line-number="lineNumber"
:meta-data="normalizedLine.metaData" :meta-data="normalizedLine.metaData"
:show-comment-button="showCommentButton" :show-comment-button="showCommentButton"
:is-hover="isHover"
:is-bottom="isBottom" :is-bottom="isBottom"
:is-match-line="isMatchLine" :is-match-line="isMatchLine"
:is-context-line="isContentLine" :is-context-line="isContentLine"
:is-meta-line="isMetaLine" :is-meta-line="isMetaLine"
:discussions="discussions"
/> />
</td> </td>
</template> </template>
<script> <script>
import { mapState, mapGetters } from 'vuex'; import { mapState } from 'vuex';
import diffDiscussions from './diff_discussions.vue'; import diffDiscussions from './diff_discussions.vue';
import diffLineNoteForm from './diff_line_note_form.vue'; import diffLineNoteForm from './diff_line_note_form.vue';
...@@ -21,15 +21,16 @@ export default { ...@@ -21,15 +21,16 @@ export default {
type: Number, type: Number,
required: true, required: true,
}, },
discussions: {
type: Array,
required: false,
default: () => [],
},
}, },
computed: { computed: {
...mapState({ ...mapState({
diffLineCommentForms: state => state.diffs.diffLineCommentForms, diffLineCommentForms: state => state.diffs.diffLineCommentForms,
}), }),
...mapGetters(['discussionsByLineCode']),
discussions() {
return this.discussionsByLineCode[this.line.lineCode] || [];
},
className() { className() {
return this.discussions.length ? '' : 'js-temp-notes-holder'; return this.discussions.length ? '' : 'js-temp-notes-holder';
}, },
......
...@@ -33,6 +33,11 @@ export default { ...@@ -33,6 +33,11 @@ export default {
required: false, required: false,
default: false, default: false,
}, },
discussions: {
type: Array,
required: false,
default: () => [],
},
}, },
data() { data() {
return { return {
...@@ -89,6 +94,7 @@ export default { ...@@ -89,6 +94,7 @@ export default {
:is-bottom="isBottom" :is-bottom="isBottom"
:is-hover="isHover" :is-hover="isHover"
:show-comment-button="true" :show-comment-button="true"
:discussions="discussions"
class="diff-line-num old_line" class="diff-line-num old_line"
/> />
<diff-table-cell <diff-table-cell
...@@ -98,6 +104,7 @@ export default { ...@@ -98,6 +104,7 @@ export default {
:line-type="newLineType" :line-type="newLineType"
:is-bottom="isBottom" :is-bottom="isBottom"
:is-hover="isHover" :is-hover="isHover"
:discussions="discussions"
class="diff-line-num new_line" class="diff-line-num new_line"
/> />
<td <td
......
...@@ -20,8 +20,11 @@ export default { ...@@ -20,8 +20,11 @@ export default {
}, },
}, },
computed: { computed: {
...mapGetters('diffs', ['commitId']), ...mapGetters('diffs', [
...mapGetters(['discussionsByLineCode']), 'commitId',
'shouldRenderInlineCommentRow',
'singleDiscussionByLineCode',
]),
...mapState({ ...mapState({
diffLineCommentForms: state => state.diffs.diffLineCommentForms, diffLineCommentForms: state => state.diffs.diffLineCommentForms,
}), }),
...@@ -36,15 +39,8 @@ export default { ...@@ -36,15 +39,8 @@ export default {
}, },
}, },
methods: { methods: {
shouldRenderCommentRow(line) { discussionsList(line) {
if (this.diffLineCommentForms[line.lineCode]) return true; return line.lineCode !== undefined ? this.singleDiscussionByLineCode(line.lineCode) : [];
const lineDiscussions = this.discussionsByLineCode[line.lineCode];
if (lineDiscussions === undefined) {
return false;
}
return lineDiscussions.every(discussion => discussion.expanded);
}, },
}, },
}; };
...@@ -65,13 +61,15 @@ export default { ...@@ -65,13 +61,15 @@ export default {
:line="line" :line="line"
:is-bottom="index + 1 === diffLinesLength" :is-bottom="index + 1 === diffLinesLength"
:key="line.lineCode" :key="line.lineCode"
:discussions="discussionsList(line)"
/> />
<inline-diff-comment-row <inline-diff-comment-row
v-if="shouldRenderCommentRow(line)" v-if="shouldRenderInlineCommentRow(line)"
:diff-file-hash="diffFile.fileHash" :diff-file-hash="diffFile.fileHash"
:line="line" :line="line"
:line-index="index" :line-index="index"
:key="index" :key="index"
:discussions="discussionsList(line)"
/> />
</template> </template>
</tbody> </tbody>
......
<script> <script>
import { mapState, mapGetters } from 'vuex'; import { mapState } from 'vuex';
import diffDiscussions from './diff_discussions.vue'; import diffDiscussions from './diff_discussions.vue';
import diffLineNoteForm from './diff_line_note_form.vue'; import diffLineNoteForm from './diff_line_note_form.vue';
...@@ -21,30 +21,34 @@ export default { ...@@ -21,30 +21,34 @@ export default {
type: Number, type: Number,
required: true, required: true,
}, },
leftDiscussions: {
type: Array,
required: false,
default: () => [],
},
rightDiscussions: {
type: Array,
required: false,
default: () => [],
},
}, },
computed: { computed: {
...mapState({ ...mapState({
diffLineCommentForms: state => state.diffs.diffLineCommentForms, diffLineCommentForms: state => state.diffs.diffLineCommentForms,
}), }),
...mapGetters(['discussionsByLineCode']),
leftLineCode() { leftLineCode() {
return this.line.left.lineCode; return this.line.left.lineCode;
}, },
rightLineCode() { rightLineCode() {
return this.line.right.lineCode; return this.line.right.lineCode;
}, },
hasDiscussion() {
const discussions = this.discussionsByLineCode;
return discussions[this.leftLineCode] || discussions[this.rightLineCode];
},
hasExpandedDiscussionOnLeft() { hasExpandedDiscussionOnLeft() {
const discussions = this.discussionsByLineCode[this.leftLineCode]; const discussions = this.leftDiscussions;
return discussions ? discussions.every(discussion => discussion.expanded) : false; return discussions ? discussions.every(discussion => discussion.expanded) : false;
}, },
hasExpandedDiscussionOnRight() { hasExpandedDiscussionOnRight() {
const discussions = this.discussionsByLineCode[this.rightLineCode]; const discussions = this.rightDiscussions;
return discussions ? discussions.every(discussion => discussion.expanded) : false; return discussions ? discussions.every(discussion => discussion.expanded) : false;
}, },
...@@ -52,17 +56,18 @@ export default { ...@@ -52,17 +56,18 @@ export default {
return this.hasExpandedDiscussionOnLeft || this.hasExpandedDiscussionOnRight; return this.hasExpandedDiscussionOnLeft || this.hasExpandedDiscussionOnRight;
}, },
shouldRenderDiscussionsOnLeft() { shouldRenderDiscussionsOnLeft() {
return this.discussionsByLineCode[this.leftLineCode] && this.hasExpandedDiscussionOnLeft; return this.leftDiscussions && this.hasExpandedDiscussionOnLeft;
}, },
shouldRenderDiscussionsOnRight() { shouldRenderDiscussionsOnRight() {
return ( return this.rightDiscussions && this.hasExpandedDiscussionOnRight && this.line.right.type;
this.discussionsByLineCode[this.rightLineCode] && },
this.hasExpandedDiscussionOnRight && showRightSideCommentForm() {
this.line.right.type return this.line.right.type && this.diffLineCommentForms[this.rightLineCode];
);
}, },
className() { 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 { ...@@ -80,13 +85,12 @@ export default {
class="content" class="content"
> >
<diff-discussions <diff-discussions
v-if="discussionsByLineCode[leftLineCode].length" v-if="leftDiscussions.length"
:discussions="discussionsByLineCode[leftLineCode]" :discussions="leftDiscussions"
/> />
</div> </div>
<diff-line-note-form <diff-line-note-form
v-if="diffLineCommentForms[leftLineCode] && v-if="diffLineCommentForms[leftLineCode]"
diffLineCommentForms[leftLineCode]"
:diff-file-hash="diffFileHash" :diff-file-hash="diffFileHash"
:line="line.left" :line="line.left"
:note-target-line="line.left" :note-target-line="line.left"
...@@ -100,13 +104,12 @@ export default { ...@@ -100,13 +104,12 @@ export default {
class="content" class="content"
> >
<diff-discussions <diff-discussions
v-if="discussionsByLineCode[rightLineCode].length" v-if="rightDiscussions.length"
:discussions="discussionsByLineCode[rightLineCode]" :discussions="rightDiscussions"
/> />
</div> </div>
<diff-line-note-form <diff-line-note-form
v-if="diffLineCommentForms[rightLineCode] && v-if="showRightSideCommentForm"
diffLineCommentForms[rightLineCode] && line.right.type"
:diff-file-hash="diffFileHash" :diff-file-hash="diffFileHash"
:line="line.right" :line="line.right"
:note-target-line="line.right" :note-target-line="line.right"
......
...@@ -36,6 +36,16 @@ export default { ...@@ -36,6 +36,16 @@ export default {
required: false, required: false,
default: false, default: false,
}, },
leftDiscussions: {
type: Array,
required: false,
default: () => [],
},
rightDiscussions: {
type: Array,
required: false,
default: () => [],
},
}, },
data() { data() {
return { return {
...@@ -116,6 +126,7 @@ export default { ...@@ -116,6 +126,7 @@ export default {
:is-hover="isLeftHover" :is-hover="isLeftHover"
:show-comment-button="true" :show-comment-button="true"
:diff-view-type="parallelDiffViewType" :diff-view-type="parallelDiffViewType"
:discussions="leftDiscussions"
class="diff-line-num old_line" class="diff-line-num old_line"
/> />
<td <td
...@@ -136,6 +147,7 @@ export default { ...@@ -136,6 +147,7 @@ export default {
:is-hover="isRightHover" :is-hover="isRightHover"
:show-comment-button="true" :show-comment-button="true"
:diff-view-type="parallelDiffViewType" :diff-view-type="parallelDiffViewType"
:discussions="rightDiscussions"
class="diff-line-num new_line" class="diff-line-num new_line"
/> />
<td <td
......
...@@ -21,8 +21,11 @@ export default { ...@@ -21,8 +21,11 @@ export default {
}, },
}, },
computed: { computed: {
...mapGetters('diffs', ['commitId']), ...mapGetters('diffs', [
...mapGetters(['discussionsByLineCode']), 'commitId',
'singleDiscussionByLineCode',
'shouldRenderParallelCommentRow',
]),
...mapState({ ...mapState({
diffLineCommentForms: state => state.diffs.diffLineCommentForms, diffLineCommentForms: state => state.diffs.diffLineCommentForms,
}), }),
...@@ -53,29 +56,9 @@ export default { ...@@ -53,29 +56,9 @@ export default {
}, },
}, },
methods: { methods: {
shouldRenderCommentRow(line) { discussionsByLine(line, leftOrRight) {
const leftLineCode = line.left.lineCode; return line[leftOrRight] && line[leftOrRight].lineCode !== undefined ?
const rightLineCode = line.right.lineCode; this.singleDiscussionByLineCode(line[leftOrRight].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;
}, },
}, },
}; };
...@@ -98,13 +81,17 @@ export default { ...@@ -98,13 +81,17 @@ export default {
:line="line" :line="line"
:is-bottom="index + 1 === diffLinesLength" :is-bottom="index + 1 === diffLinesLength"
:key="index" :key="index"
:left-discussions="discussionsByLine(line, 'left')"
:right-discussions="discussionsByLine(line, 'right')"
/> />
<parallel-diff-comment-row <parallel-diff-comment-row
v-if="shouldRenderCommentRow(line)" v-if="shouldRenderParallelCommentRow(line)"
:key="`dcr-${index}`" :key="`dcr-${index}`"
:line="line" :line="line"
:diff-file-hash="diffFile.fileHash" :diff-file-hash="diffFile.fileHash"
:line-index="index" :line-index="index"
:left-discussions="discussionsByLine(line, 'left')"
:right-discussions="discussionsByLine(line, 'right')"
/> />
</template> </template>
</tbody> </tbody>
......
...@@ -64,6 +64,47 @@ export const getDiffFileDiscussions = (state, getters, rootState, rootGetters) = ...@@ -64,6 +64,47 @@ export const getDiffFileDiscussions = (state, getters, rootState, rootGetters) =
discussion.diff_discussion && _.isEqual(discussion.diff_file.file_hash, diff.fileHash), 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 // prevent babel-plugin-rewire from generating an invalid default during karma∂ tests
export const getDiffFileByHash = state => fileHash => export const getDiffFileByHash = state => fileHash =>
state.diffFiles.find(file => file.fileHash === fileHash); state.diffFiles.find(file => file.fileHash === fileHash);
......
...@@ -173,3 +173,24 @@ export function trimFirstCharOfLineContent(line = {}) { ...@@ -173,3 +173,24 @@ export function trimFirstCharOfLineContent(line = {}) {
return parsedLine; 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() { ...@@ -616,7 +616,11 @@ GitLabDropdown = (function() {
} }
if (this.options.opened) { 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'); return this.dropdown.trigger('shown.gl.dropdown');
......
...@@ -78,17 +78,10 @@ export default { ...@@ -78,17 +78,10 @@ export default {
> >
<div <div
:class="{ 'project-row-contents': !isGroup }" :class="{ 'project-row-contents': !isGroup }"
class="group-row-contents"> class="group-row-contents d-flex justify-content-end align-items-center"
<item-actions >
v-if="isGroup"
:group="group"
:parent-group="parentGroup"
/>
<item-stats
:item="group"
/>
<div <div
class="folder-toggle-wrap" class="folder-toggle-wrap append-right-4 d-flex align-items-center"
> >
<item-caret <item-caret
:is-group-open="group.isOpen" :is-group-open="group.isOpen"
...@@ -100,7 +93,7 @@ export default { ...@@ -100,7 +93,7 @@ export default {
</div> </div>
<div <div
:class="{ 'content-loading': group.isChildrenLoading }" :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 <a
:href="group.relativePath" :href="group.relativePath"
...@@ -120,32 +113,46 @@ export default { ...@@ -120,32 +113,46 @@ export default {
</a> </a>
</div> </div>
<div <div
class="title namespace-title" class="group-text flex-grow"
> >
<a <div
v-tooltip class="title namespace-title append-right-8"
: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 }} <a
</span> v-tooltip
</div> :href="group.relativePath"
<div :title="group.fullName"
v-if="group.description" class="no-expand"
class="description"> data-placement="bottom"
<span v-html="group.description"> >{{
</span> // 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> </div>
<item-stats
:item="group"
class="group-stats prepend-top-2"
/>
<item-actions
v-if="isGroup"
:group="group"
:parent-group="parentGroup"
/>
</div> </div>
<group-folder <group-folder
v-if="group.isOpen && hasChildren" v-if="group.isOpen && hasChildren"
......
...@@ -39,7 +39,7 @@ export default class LabelsSelect { ...@@ -39,7 +39,7 @@ export default class LabelsSelect {
showNo = $dropdown.data('showNo'); showNo = $dropdown.data('showNo');
showAny = $dropdown.data('showAny'); showAny = $dropdown.data('showAny');
showMenuAbove = $dropdown.data('showMenuAbove'); showMenuAbove = $dropdown.data('showMenuAbove');
defaultLabel = $dropdown.data('defaultLabel'); defaultLabel = $dropdown.data('defaultLabel') || 'Label';
abilityName = $dropdown.data('abilityName'); abilityName = $dropdown.data('abilityName');
$selectbox = $dropdown.closest('.selectbox'); $selectbox = $dropdown.closest('.selectbox');
$block = $selectbox.closest('.block'); $block = $selectbox.closest('.block');
...@@ -244,21 +244,21 @@ export default class LabelsSelect { ...@@ -244,21 +244,21 @@ export default class LabelsSelect {
var $dropdownInputField = $dropdownParent.find('.dropdown-input-field'); var $dropdownInputField = $dropdownParent.find('.dropdown-input-field');
var isSelected = el !== null ? el.hasClass('is-active') : false; var isSelected = el !== null ? el.hasClass('is-active') : false;
var { title } = selected; var title = selected ? selected.title : null;
var selectedLabels = this.selected; var selectedLabels = this.selected;
if ($dropdownInputField.length && $dropdownInputField.val().length) { if ($dropdownInputField.length && $dropdownInputField.val().length) {
$dropdownParent.find('.dropdown-input-clear').trigger('click'); $dropdownParent.find('.dropdown-input-clear').trigger('click');
} }
if (selected.id === 0) { if (selected && selected.id === 0) {
this.selected = []; this.selected = [];
return 'No Label'; return 'No Label';
} }
else if (isSelected) { else if (isSelected) {
this.selected.push(title); this.selected.push(title);
} }
else { else if (!isSelected && title) {
var index = this.selected.indexOf(title); var index = this.selected.indexOf(title);
this.selected.splice(index, 1); this.selected.splice(index, 1);
} }
...@@ -409,6 +409,14 @@ export default class LabelsSelect { ...@@ -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 // Set dropdown data
......
...@@ -19,11 +19,17 @@ export default class LazyLoader { ...@@ -19,11 +19,17 @@ export default class LazyLoader {
scrollContainer.addEventListener('load', () => this.loadCheck()); scrollContainer.addEventListener('load', () => this.loadCheck());
} }
searchLazyImages() { searchLazyImages() {
this.lazyImages = [].slice.call(document.querySelectorAll('.lazy')); const that = this;
requestIdleCallback(
() => {
that.lazyImages = [].slice.call(document.querySelectorAll('.lazy'));
if (this.lazyImages.length) { if (that.lazyImages.length) {
this.checkElementsInView(); that.checkElementsInView();
} }
},
{ timeout: 500 },
);
} }
startContentObserver() { startContentObserver() {
const contentNode = document.querySelector(this.observerNode) || document.querySelector('body'); const contentNode = document.querySelector(this.observerNode) || document.querySelector('body');
...@@ -56,7 +62,9 @@ export default class LazyLoader { ...@@ -56,7 +62,9 @@ export default class LazyLoader {
const imgBound = imgTop + imgBoundRect.height; const imgBound = imgTop + imgBoundRect.height;
if (scrollTop < imgBound && visHeight > imgTop) { if (scrollTop < imgBound && visHeight > imgTop) {
LazyLoader.loadImage(selectedImage); requestAnimationFrame(() => {
LazyLoader.loadImage(selectedImage);
});
return false; return false;
} }
......
...@@ -42,6 +42,9 @@ export default { ...@@ -42,6 +42,9 @@ export default {
}, },
methods: { methods: {
onImgLoad() { onImgLoad() {
requestIdleCallback(this.calculateImgSize, { timeout: 1000 });
},
calculateImgSize() {
const { contentImg } = this.$refs; const { contentImg } = this.$refs;
if (contentImg) { if (contentImg) {
......
...@@ -459,6 +459,7 @@ img.emoji { ...@@ -459,6 +459,7 @@ img.emoji {
.prepend-left-15 { margin-left: 15px; } .prepend-left-15 { margin-left: 15px; }
.prepend-left-default { margin-left: $gl-padding; } .prepend-left-default { margin-left: $gl-padding; }
.prepend-left-20 { margin-left: 20px; } .prepend-left-20 { margin-left: 20px; }
.append-right-4 { margin-right: 4px; }
.append-right-5 { margin-right: 5px; } .append-right-5 { margin-right: 5px; }
.append-right-8 { margin-right: 8px; } .append-right-8 { margin-right: 8px; }
.append-right-10 { margin-right: 10px; } .append-right-10 { margin-right: 10px; }
...@@ -478,3 +479,5 @@ img.emoji { ...@@ -478,3 +479,5 @@ img.emoji {
.center { text-align: center; } .center { text-align: center; }
.vertical-align-middle { vertical-align: middle; } .vertical-align-middle { vertical-align: middle; }
.flex-align-self-center { align-self: center; } .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 { ...@@ -339,9 +339,8 @@ table.pipeline-project-metrics tr td {
} }
.folder-toggle-wrap { .folder-toggle-wrap {
float: left;
line-height: $list-text-height;
font-size: 0; font-size: 0;
flex-shrink: 0;
span { span {
font-size: $gl-font-size; font-size: $gl-font-size;
...@@ -357,7 +356,7 @@ table.pipeline-project-metrics tr td { ...@@ -357,7 +356,7 @@ table.pipeline-project-metrics tr td {
width: 15px; width: 15px;
svg { svg {
margin-bottom: 2px; margin-bottom: 1px;
} }
} }
...@@ -440,9 +439,17 @@ table.pipeline-project-metrics tr td { ...@@ -440,9 +439,17 @@ table.pipeline-project-metrics tr td {
cursor: pointer; cursor: pointer;
} }
.avatar-container > a { .group-text {
width: 100%; min-width: 0; // allows for truncated text within flex children
text-decoration: none; }
.avatar-container {
flex-shrink: 0;
> a {
width: 100%;
text-decoration: none;
}
} }
&.has-more-items { &.has-more-items {
...@@ -450,9 +457,18 @@ table.pipeline-project-metrics tr td { ...@@ -450,9 +457,18 @@ table.pipeline-project-metrics tr td {
padding: 20px 10px; padding: 20px 10px;
} }
.description {
p {
@include str-truncated;
max-width: none;
}
}
.stats { .stats {
position: relative; position: relative;
line-height: 46px; line-height: normal;
flex-shrink: 0;
> span { > span {
display: inline-flex; display: inline-flex;
...@@ -471,14 +487,20 @@ table.pipeline-project-metrics tr td { ...@@ -471,14 +487,20 @@ table.pipeline-project-metrics tr td {
} }
.controls { .controls {
margin-left: 5px; flex-shrink: 0;
> .btn { > .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 { .project-row-contents .stats {
line-height: inherit; line-height: inherit;
...@@ -500,18 +522,6 @@ table.pipeline-project-metrics tr td { ...@@ -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 { .js-groups-list-holder {
.groups-list-loading { .groups-list-loading {
font-size: 34px; font-size: 34px;
......
...@@ -20,13 +20,13 @@ class ApplicationController < ActionController::Base ...@@ -20,13 +20,13 @@ class ApplicationController < ActionController::Base
before_action :ldap_security_check before_action :ldap_security_check
before_action :sentry_context before_action :sentry_context
before_action :default_headers 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 :configure_permitted_parameters, if: :devise_controller?
before_action :require_email, unless: :devise_controller? before_action :require_email, unless: :devise_controller?
around_action :set_locale 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 protect_from_forgery with: :exception, prepend: true
...@@ -432,6 +432,10 @@ class ApplicationController < ActionController::Base ...@@ -432,6 +432,10 @@ class ApplicationController < ActionController::Base
request.path.start_with?('/-/peek') request.path.start_with?('/-/peek')
end end
def json_request?
request.format.json?
end
def should_enforce_terms? def should_enforce_terms?
return false unless Gitlab::CurrentSettings.current_application_settings.enforce_terms return false unless Gitlab::CurrentSettings.current_application_settings.enforce_terms
......
...@@ -5,7 +5,7 @@ class Projects::RunnersController < Projects::ApplicationController ...@@ -5,7 +5,7 @@ class Projects::RunnersController < Projects::ApplicationController
layout 'project_settings' layout 'project_settings'
def index def index
redirect_to project_settings_ci_cd_path(@project) redirect_to project_settings_ci_cd_path(@project, anchor: 'js-runners-settings')
end end
def edit def edit
...@@ -50,13 +50,13 @@ class Projects::RunnersController < Projects::ApplicationController ...@@ -50,13 +50,13 @@ class Projects::RunnersController < Projects::ApplicationController
def toggle_shared_runners def toggle_shared_runners
project.toggle!(:shared_runners_enabled) 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 end
def toggle_group_runners def toggle_group_runners
project.toggle_ci_cd_settings!(:group_runners_enabled) 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 end
protected protected
......
...@@ -7,7 +7,7 @@ class Projects::TriggersController < Projects::ApplicationController ...@@ -7,7 +7,7 @@ class Projects::TriggersController < Projects::ApplicationController
layout 'project_settings' layout 'project_settings'
def index def index
redirect_to project_settings_ci_cd_path(@project) redirect_to project_settings_ci_cd_path(@project, anchor: 'js-pipeline-triggers')
end end
def create def create
...@@ -19,7 +19,7 @@ class Projects::TriggersController < Projects::ApplicationController ...@@ -19,7 +19,7 @@ class Projects::TriggersController < Projects::ApplicationController
flash[:alert] = 'You could not create a new trigger.' flash[:alert] = 'You could not create a new trigger.'
end end
redirect_to project_settings_ci_cd_path(@project) redirect_to project_settings_ci_cd_path(@project, anchor: 'js-pipeline-triggers')
end end
def take_ownership def take_ownership
...@@ -29,7 +29,7 @@ class Projects::TriggersController < Projects::ApplicationController ...@@ -29,7 +29,7 @@ class Projects::TriggersController < Projects::ApplicationController
flash[:alert] = 'You could not take ownership of trigger.' flash[:alert] = 'You could not take ownership of trigger.'
end end
redirect_to project_settings_ci_cd_path(@project) redirect_to project_settings_ci_cd_path(@project, anchor: 'js-pipeline-triggers')
end end
def edit def edit
...@@ -37,7 +37,7 @@ class Projects::TriggersController < Projects::ApplicationController ...@@ -37,7 +37,7 @@ class Projects::TriggersController < Projects::ApplicationController
def update def update
if trigger.update(trigger_params) 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 else
render action: "edit" render action: "edit"
end end
...@@ -50,7 +50,7 @@ class Projects::TriggersController < Projects::ApplicationController ...@@ -50,7 +50,7 @@ class Projects::TriggersController < Projects::ApplicationController
flash[:alert] = "Could not remove the trigger." flash[:alert] = "Could not remove the trigger."
end 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 end
private private
......
...@@ -153,7 +153,7 @@ module Issuable ...@@ -153,7 +153,7 @@ module Issuable
end end
# Break ties with the ID column for pagination # Break ties with the ID column for pagination
sorted.order(id: :desc) sorted.with_order_id_desc
end end
def order_due_date_and_labels_priority(excluded_labels: []) def order_due_date_and_labels_priority(excluded_labels: [])
......
...@@ -6,6 +6,7 @@ module Sortable ...@@ -6,6 +6,7 @@ module Sortable
extend ActiveSupport::Concern extend ActiveSupport::Concern
included do included do
scope :with_order_id_desc, -> { order(id: :desc) }
scope :order_id_desc, -> { reorder(id: :desc) } scope :order_id_desc, -> { reorder(id: :desc) }
scope :order_id_asc, -> { reorder(id: :asc) } scope :order_id_asc, -> { reorder(id: :asc) }
scope :order_created_desc, -> { reorder(created_at: :desc) } scope :order_created_desc, -> { reorder(created_at: :desc) }
......
...@@ -150,22 +150,25 @@ class Milestone < ActiveRecord::Base ...@@ -150,22 +150,25 @@ class Milestone < ActiveRecord::Base
end end
def self.sort_by_attribute(method) def self.sort_by_attribute(method)
case method.to_s sorted =
when 'due_date_asc' case method.to_s
reorder(Gitlab::Database.nulls_last_order('due_date', 'ASC')) when 'due_date_asc'
when 'due_date_desc' reorder(Gitlab::Database.nulls_last_order('due_date', 'ASC'))
reorder(Gitlab::Database.nulls_last_order('due_date', 'DESC')) when 'due_date_desc'
when 'name_asc' reorder(Gitlab::Database.nulls_last_order('due_date', 'DESC'))
reorder(Arel::Nodes::Ascending.new(arel_table[:title].lower)) when 'name_asc'
when 'name_desc' reorder(Arel::Nodes::Ascending.new(arel_table[:title].lower))
reorder(Arel::Nodes::Descending.new(arel_table[:title].lower)) when 'name_desc'
when 'start_date_asc' reorder(Arel::Nodes::Descending.new(arel_table[:title].lower))
reorder(Gitlab::Database.nulls_last_order('start_date', 'ASC')) when 'start_date_asc'
when 'start_date_desc' reorder(Gitlab::Database.nulls_last_order('start_date', 'ASC'))
reorder(Gitlab::Database.nulls_last_order('start_date', 'DESC')) when 'start_date_desc'
else reorder(Gitlab::Database.nulls_last_order('start_date', 'DESC'))
order_by(method) else
end order_by(method)
end
sorted.with_order_id_desc
end end
## ##
......
.row .row
.col-lg-12 .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) = form_errors(@project)
%fieldset.builds-feature.js-auto-devops-settings %fieldset.builds-feature.js-auto-devops-settings
.form-group .form-group
......
.row.prepend-top-default .row.prepend-top-default
.col-lg-12 .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) = form_errors(@project)
%fieldset.builds-feature %fieldset.builds-feature
.form-group.append-bottom-default.js-secret-runner-token .form-group.append-bottom-default.js-secret-runner-token
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
":value" => "label.id" } ":value" => "label.id" }
.dropdown .dropdown
%button.dropdown-menu-toggle.js-label-select.js-multiselect.js-issue-board-sidebar{ type: "button", %button.dropdown-menu-toggle.js-label-select.js-multiselect.js-issue-board-sidebar{ type: "button",
"v-bind:data-selected" => "selectedLabels",
data: { toggle: "dropdown", data: { toggle: "dropdown",
field_name: "issue[label_names][]", field_name: "issue[label_names][]",
show_no: "true", show_no: "true",
...@@ -28,7 +29,7 @@ ...@@ -28,7 +29,7 @@
namespace_path: @namespace_path, namespace_path: @namespace_path,
project_path: @project.try(:path) } } project_path: @project.try(:path) } }
%span.dropdown-toggle-text %span.dropdown-toggle-text
= _("Label") {{ labelDropdownTitle }}
= icon('chevron-down') = icon('chevron-down')
.dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable
= render partial: "shared/issuable/label_page_default" = 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 ...@@ -56,6 +56,57 @@ describe ApplicationController do
end end
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 describe "#authenticate_user_from_personal_access_token!" do
before do before do
stub_authentication_activity_metrics(debug: false) stub_authentication_activity_metrics(debug: false)
......
...@@ -42,16 +42,45 @@ describe Projects::MilestonesController do ...@@ -42,16 +42,45 @@ describe Projects::MilestonesController do
describe "#index" do describe "#index" do
context "as html" do context "as html" do
before do def render_index(project:, page:)
get :index, namespace_id: project.namespace.id, project_id: project.id get :index, namespace_id: project.namespace.id,
project_id: project.id,
page: page
end end
it "queries only projects milestones" do it "queries only projects milestones" do
render_index project: project, page: 1
milestones = assigns(:milestones) milestones = assigns(:milestones)
expect(milestones.count).to eq(1) expect(milestones.count).to eq(1)
expect(milestones.where(project_id: nil)).to be_empty expect(milestones.where(project_id: nil)).to be_empty
end 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 end
context "as json" do context "as json" do
......
import Clusters from '~/clusters/clusters_bundle'; import Clusters from '~/clusters/clusters_bundle';
import { import {
APPLICATION_INSTALLABLE,
APPLICATION_INSTALLING,
APPLICATION_INSTALLED,
REQUEST_LOADING, REQUEST_LOADING,
REQUEST_SUCCESS, REQUEST_SUCCESS,
REQUEST_FAILURE, REQUEST_FAILURE,
APPLICATION_STATUS,
} from '~/clusters/constants'; } from '~/clusters/constants';
import getSetTimeoutPromise from 'spec/helpers/set_timeout_promise_helper'; import getSetTimeoutPromise from 'spec/helpers/set_timeout_promise_helper';
...@@ -84,7 +82,7 @@ describe('Clusters', () => { ...@@ -84,7 +82,7 @@ describe('Clusters', () => {
it('does not show alert when things transition from initial null state to something', () => { it('does not show alert when things transition from initial null state to something', () => {
cluster.checkForNewInstalls(INITIAL_APP_MAP, { cluster.checkForNewInstalls(INITIAL_APP_MAP, {
...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'); const flashMessage = document.querySelector('.js-cluster-application-notice .flash-text');
...@@ -94,10 +92,10 @@ describe('Clusters', () => { ...@@ -94,10 +92,10 @@ describe('Clusters', () => {
it('shows an alert when something gets newly installed', () => { it('shows an alert when something gets newly installed', () => {
cluster.checkForNewInstalls({ cluster.checkForNewInstalls({
...INITIAL_APP_MAP, ...INITIAL_APP_MAP,
helm: { status: APPLICATION_INSTALLING, title: 'Helm Tiller' }, helm: { status: APPLICATION_STATUS.INSTALLING, title: 'Helm Tiller' },
}, { }, {
...INITIAL_APP_MAP, ...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'); const flashMessage = document.querySelector('.js-cluster-application-notice .flash-text');
...@@ -108,12 +106,12 @@ describe('Clusters', () => { ...@@ -108,12 +106,12 @@ describe('Clusters', () => {
it('shows an alert when multiple things gets newly installed', () => { it('shows an alert when multiple things gets newly installed', () => {
cluster.checkForNewInstalls({ cluster.checkForNewInstalls({
...INITIAL_APP_MAP, ...INITIAL_APP_MAP,
helm: { status: APPLICATION_INSTALLING, title: 'Helm Tiller' }, helm: { status: APPLICATION_STATUS.INSTALLING, title: 'Helm Tiller' },
ingress: { status: APPLICATION_INSTALLABLE, title: 'Ingress' }, ingress: { status: APPLICATION_STATUS.INSTALLABLE, title: 'Ingress' },
}, { }, {
...INITIAL_APP_MAP, ...INITIAL_APP_MAP,
helm: { status: APPLICATION_INSTALLED, title: 'Helm Tiller' }, helm: { status: APPLICATION_STATUS.INSTALLED, title: 'Helm Tiller' },
ingress: { status: APPLICATION_INSTALLED, title: 'Ingress' }, ingress: { status: APPLICATION_STATUS.INSTALLED, title: 'Ingress' },
}); });
const flashMessage = document.querySelector('.js-cluster-application-notice .flash-text'); const flashMessage = document.querySelector('.js-cluster-application-notice .flash-text');
......
import Vue from 'vue'; import Vue from 'vue';
import eventHub from '~/clusters/event_hub'; import eventHub from '~/clusters/event_hub';
import { import {
APPLICATION_NOT_INSTALLABLE, APPLICATION_STATUS,
APPLICATION_SCHEDULED,
APPLICATION_INSTALLABLE,
APPLICATION_INSTALLING,
APPLICATION_INSTALLED,
APPLICATION_ERROR,
REQUEST_LOADING, REQUEST_LOADING,
REQUEST_SUCCESS, REQUEST_SUCCESS,
REQUEST_FAILURE, REQUEST_FAILURE,
...@@ -62,10 +57,10 @@ describe('Application Row', () => { ...@@ -62,10 +57,10 @@ describe('Application Row', () => {
expect(vm.installButtonLabel).toBeUndefined(); expect(vm.installButtonLabel).toBeUndefined();
}); });
it('has disabled "Install" when APPLICATION_NOT_INSTALLABLE', () => { it('has disabled "Install" when APPLICATION_STATUS.NOT_INSTALLABLE', () => {
vm = mountComponent(ApplicationRow, { vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE, ...DEFAULT_APPLICATION_STATE,
status: APPLICATION_NOT_INSTALLABLE, status: APPLICATION_STATUS.NOT_INSTALLABLE,
}); });
expect(vm.installButtonLabel).toEqual('Install'); expect(vm.installButtonLabel).toEqual('Install');
...@@ -73,10 +68,10 @@ describe('Application Row', () => { ...@@ -73,10 +68,10 @@ describe('Application Row', () => {
expect(vm.installButtonDisabled).toEqual(true); expect(vm.installButtonDisabled).toEqual(true);
}); });
it('has enabled "Install" when APPLICATION_INSTALLABLE', () => { it('has enabled "Install" when APPLICATION_STATUS.INSTALLABLE', () => {
vm = mountComponent(ApplicationRow, { vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE, ...DEFAULT_APPLICATION_STATE,
status: APPLICATION_INSTALLABLE, status: APPLICATION_STATUS.INSTALLABLE,
}); });
expect(vm.installButtonLabel).toEqual('Install'); expect(vm.installButtonLabel).toEqual('Install');
...@@ -84,10 +79,10 @@ describe('Application Row', () => { ...@@ -84,10 +79,10 @@ describe('Application Row', () => {
expect(vm.installButtonDisabled).toEqual(false); expect(vm.installButtonDisabled).toEqual(false);
}); });
it('has loading "Installing" when APPLICATION_SCHEDULED', () => { it('has loading "Installing" when APPLICATION_STATUS.SCHEDULED', () => {
vm = mountComponent(ApplicationRow, { vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE, ...DEFAULT_APPLICATION_STATE,
status: APPLICATION_SCHEDULED, status: APPLICATION_STATUS.SCHEDULED,
}); });
expect(vm.installButtonLabel).toEqual('Installing'); expect(vm.installButtonLabel).toEqual('Installing');
...@@ -95,10 +90,10 @@ describe('Application Row', () => { ...@@ -95,10 +90,10 @@ describe('Application Row', () => {
expect(vm.installButtonDisabled).toEqual(true); expect(vm.installButtonDisabled).toEqual(true);
}); });
it('has loading "Installing" when APPLICATION_INSTALLING', () => { it('has loading "Installing" when APPLICATION_STATUS.INSTALLING', () => {
vm = mountComponent(ApplicationRow, { vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE, ...DEFAULT_APPLICATION_STATE,
status: APPLICATION_INSTALLING, status: APPLICATION_STATUS.INSTALLING,
}); });
expect(vm.installButtonLabel).toEqual('Installing'); expect(vm.installButtonLabel).toEqual('Installing');
...@@ -106,10 +101,10 @@ describe('Application Row', () => { ...@@ -106,10 +101,10 @@ describe('Application Row', () => {
expect(vm.installButtonDisabled).toEqual(true); expect(vm.installButtonDisabled).toEqual(true);
}); });
it('has disabled "Installed" when APPLICATION_INSTALLED', () => { it('has disabled "Installed" when APPLICATION_STATUS.INSTALLED', () => {
vm = mountComponent(ApplicationRow, { vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE, ...DEFAULT_APPLICATION_STATE,
status: APPLICATION_INSTALLED, status: APPLICATION_STATUS.INSTALLED,
}); });
expect(vm.installButtonLabel).toEqual('Installed'); expect(vm.installButtonLabel).toEqual('Installed');
...@@ -117,10 +112,10 @@ describe('Application Row', () => { ...@@ -117,10 +112,10 @@ describe('Application Row', () => {
expect(vm.installButtonDisabled).toEqual(true); expect(vm.installButtonDisabled).toEqual(true);
}); });
it('has enabled "Install" when APPLICATION_ERROR', () => { it('has enabled "Install" when APPLICATION_STATUS.ERROR', () => {
vm = mountComponent(ApplicationRow, { vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE, ...DEFAULT_APPLICATION_STATE,
status: APPLICATION_ERROR, status: APPLICATION_STATUS.ERROR,
}); });
expect(vm.installButtonLabel).toEqual('Install'); expect(vm.installButtonLabel).toEqual('Install');
...@@ -131,7 +126,7 @@ describe('Application Row', () => { ...@@ -131,7 +126,7 @@ describe('Application Row', () => {
it('has loading "Install" when REQUEST_LOADING', () => { it('has loading "Install" when REQUEST_LOADING', () => {
vm = mountComponent(ApplicationRow, { vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE, ...DEFAULT_APPLICATION_STATE,
status: APPLICATION_INSTALLABLE, status: APPLICATION_STATUS.INSTALLABLE,
requestStatus: REQUEST_LOADING, requestStatus: REQUEST_LOADING,
}); });
...@@ -143,7 +138,7 @@ describe('Application Row', () => { ...@@ -143,7 +138,7 @@ describe('Application Row', () => {
it('has disabled "Install" when REQUEST_SUCCESS', () => { it('has disabled "Install" when REQUEST_SUCCESS', () => {
vm = mountComponent(ApplicationRow, { vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE, ...DEFAULT_APPLICATION_STATE,
status: APPLICATION_INSTALLABLE, status: APPLICATION_STATUS.INSTALLABLE,
requestStatus: REQUEST_SUCCESS, requestStatus: REQUEST_SUCCESS,
}); });
...@@ -155,7 +150,7 @@ describe('Application Row', () => { ...@@ -155,7 +150,7 @@ describe('Application Row', () => {
it('has enabled "Install" when REQUEST_FAILURE (so you can try installing again)', () => { it('has enabled "Install" when REQUEST_FAILURE (so you can try installing again)', () => {
vm = mountComponent(ApplicationRow, { vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE, ...DEFAULT_APPLICATION_STATE,
status: APPLICATION_INSTALLABLE, status: APPLICATION_STATUS.INSTALLABLE,
requestStatus: REQUEST_FAILURE, requestStatus: REQUEST_FAILURE,
}); });
...@@ -168,7 +163,7 @@ describe('Application Row', () => { ...@@ -168,7 +163,7 @@ describe('Application Row', () => {
spyOn(eventHub, '$emit'); spyOn(eventHub, '$emit');
vm = mountComponent(ApplicationRow, { vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE, ...DEFAULT_APPLICATION_STATE,
status: APPLICATION_INSTALLABLE, status: APPLICATION_STATUS.INSTALLABLE,
}); });
const installButton = vm.$el.querySelector('.js-cluster-application-install-button'); const installButton = vm.$el.querySelector('.js-cluster-application-install-button');
...@@ -184,7 +179,7 @@ describe('Application Row', () => { ...@@ -184,7 +179,7 @@ describe('Application Row', () => {
spyOn(eventHub, '$emit'); spyOn(eventHub, '$emit');
vm = mountComponent(ApplicationRow, { vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE, ...DEFAULT_APPLICATION_STATE,
status: APPLICATION_INSTALLABLE, status: APPLICATION_STATUS.INSTALLABLE,
installApplicationRequestParams: { hostname: 'jupyter' }, installApplicationRequestParams: { hostname: 'jupyter' },
}); });
const installButton = vm.$el.querySelector('.js-cluster-application-install-button'); const installButton = vm.$el.querySelector('.js-cluster-application-install-button');
...@@ -201,7 +196,7 @@ describe('Application Row', () => { ...@@ -201,7 +196,7 @@ describe('Application Row', () => {
spyOn(eventHub, '$emit'); spyOn(eventHub, '$emit');
vm = mountComponent(ApplicationRow, { vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE, ...DEFAULT_APPLICATION_STATE,
status: APPLICATION_INSTALLING, status: APPLICATION_STATUS.INSTALLING,
}); });
const installButton = vm.$el.querySelector('.js-cluster-application-install-button'); const installButton = vm.$el.querySelector('.js-cluster-application-install-button');
...@@ -225,11 +220,11 @@ describe('Application Row', () => { ...@@ -225,11 +220,11 @@ describe('Application Row', () => {
expect(generalErrorMessage).toBeNull(); 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'; const statusReason = 'We broke it 0.0';
vm = mountComponent(ApplicationRow, { vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE, ...DEFAULT_APPLICATION_STATE,
status: APPLICATION_ERROR, status: APPLICATION_STATUS.ERROR,
statusReason, statusReason,
}); });
const generalErrorMessage = vm.$el.querySelector('.js-cluster-application-general-error-message'); const generalErrorMessage = vm.$el.querySelector('.js-cluster-application-general-error-message');
...@@ -243,7 +238,7 @@ describe('Application Row', () => { ...@@ -243,7 +238,7 @@ describe('Application Row', () => {
const requestReason = 'We broke thre request 0.0'; const requestReason = 'We broke thre request 0.0';
vm = mountComponent(ApplicationRow, { vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE, ...DEFAULT_APPLICATION_STATE,
status: APPLICATION_INSTALLABLE, status: APPLICATION_STATUS.INSTALLABLE,
requestStatus: REQUEST_FAILURE, requestStatus: REQUEST_FAILURE,
requestReason, requestReason,
}); });
......
import { import { APPLICATION_STATUS } from '~/clusters/constants';
APPLICATION_INSTALLED,
APPLICATION_INSTALLABLE,
APPLICATION_INSTALLING,
APPLICATION_ERROR,
} from '~/clusters/constants';
const CLUSTERS_MOCK_DATA = { const CLUSTERS_MOCK_DATA = {
GET: { GET: {
...@@ -13,25 +8,25 @@ const CLUSTERS_MOCK_DATA = { ...@@ -13,25 +8,25 @@ const CLUSTERS_MOCK_DATA = {
status_reason: 'Failed to request to CloudPlatform.', status_reason: 'Failed to request to CloudPlatform.',
applications: [{ applications: [{
name: 'helm', name: 'helm',
status: APPLICATION_INSTALLABLE, status: APPLICATION_STATUS.INSTALLABLE,
status_reason: null, status_reason: null,
}, { }, {
name: 'ingress', name: 'ingress',
status: APPLICATION_ERROR, status: APPLICATION_STATUS.ERROR,
status_reason: 'Cannot connect', status_reason: 'Cannot connect',
external_ip: null, external_ip: null,
}, { }, {
name: 'runner', name: 'runner',
status: APPLICATION_INSTALLING, status: APPLICATION_STATUS.INSTALLING,
status_reason: null, status_reason: null,
}, },
{ {
name: 'prometheus', name: 'prometheus',
status: APPLICATION_ERROR, status: APPLICATION_STATUS.ERROR,
status_reason: 'Cannot connect', status_reason: 'Cannot connect',
}, { }, {
name: 'jupyter', name: 'jupyter',
status: APPLICATION_INSTALLING, status: APPLICATION_STATUS.INSTALLING,
status_reason: 'Cannot connect', status_reason: 'Cannot connect',
}], }],
}, },
...@@ -42,25 +37,25 @@ const CLUSTERS_MOCK_DATA = { ...@@ -42,25 +37,25 @@ const CLUSTERS_MOCK_DATA = {
status_reason: 'Failed to request to CloudPlatform.', status_reason: 'Failed to request to CloudPlatform.',
applications: [{ applications: [{
name: 'helm', name: 'helm',
status: APPLICATION_INSTALLED, status: APPLICATION_STATUS.INSTALLED,
status_reason: null, status_reason: null,
}, { }, {
name: 'ingress', name: 'ingress',
status: APPLICATION_INSTALLED, status: APPLICATION_STATUS.INSTALLED,
status_reason: 'Cannot connect', status_reason: 'Cannot connect',
external_ip: '1.1.1.1', external_ip: '1.1.1.1',
}, { }, {
name: 'runner', name: 'runner',
status: APPLICATION_INSTALLING, status: APPLICATION_STATUS.INSTALLING,
status_reason: null, status_reason: null,
}, },
{ {
name: 'prometheus', name: 'prometheus',
status: APPLICATION_ERROR, status: APPLICATION_STATUS.ERROR,
status_reason: 'Cannot connect', status_reason: 'Cannot connect',
}, { }, {
name: 'jupyter', name: 'jupyter',
status: APPLICATION_INSTALLABLE, status: APPLICATION_STATUS.INSTALLABLE,
status_reason: 'Cannot connect', status_reason: 'Cannot connect',
}], }],
}, },
......
import ClustersStore from '~/clusters/stores/clusters_store'; 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'; import { CLUSTERS_MOCK_DATA } from '../services/mock_data';
describe('Clusters Store', () => { describe('Clusters Store', () => {
...@@ -35,7 +35,7 @@ describe('Clusters Store', () => { ...@@ -35,7 +35,7 @@ describe('Clusters Store', () => {
it('should store new request status', () => { it('should store new request status', () => {
expect(store.state.applications.helm.requestStatus).toEqual(null); expect(store.state.applications.helm.requestStatus).toEqual(null);
const newStatus = APPLICATION_INSTALLING; const newStatus = APPLICATION_STATUS.INSTALLING;
store.updateAppProperty('helm', 'requestStatus', newStatus); store.updateAppProperty('helm', 'requestStatus', newStatus);
expect(store.state.applications.helm.requestStatus).toEqual(newStatus); expect(store.state.applications.helm.requestStatus).toEqual(newStatus);
......
...@@ -48,7 +48,11 @@ describe('DiffLineGutterContent', () => { ...@@ -48,7 +48,11 @@ describe('DiffLineGutterContent', () => {
it('should return discussions for the given lineCode', () => { it('should return discussions for the given lineCode', () => {
const { lineCode } = getDiffFileMock().highlightedDiffLines[1]; const { lineCode } = getDiffFileMock().highlightedDiffLines[1];
const component = createComponent({ lineCode, showCommentButton: true }); const component = createComponent({
lineCode,
showCommentButton: true,
discussions: getDiscussionsMockData(),
});
setDiscussions(component); setDiscussions(component);
......
...@@ -184,6 +184,104 @@ describe('Diffs Module Getters', () => { ...@@ -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', () => { describe('getDiffFileDiscussions', () => {
it('returns an array with discussions when fileHash matches and the discussion belongs to a diff', () => { it('returns an array with discussions when fileHash matches and the discussion belongs to a diff', () => {
discussionMock.diff_file.file_hash = diffFileMock.fileHash; discussionMock.diff_file.file_hash = diffFileMock.fileHash;
......
...@@ -170,8 +170,6 @@ describe('ImageDiffViewer', () => { ...@@ -170,8 +170,6 @@ describe('ImageDiffViewer', () => {
vm.$el.querySelector('.view-modes-menu li:nth-child(3)').click(); vm.$el.querySelector('.view-modes-menu li:nth-child(3)').click();
vm.$nextTick(() => { vm.$nextTick(() => {
expect(vm.$el.querySelector('.dragger').style.left).toBe('100px');
dragSlider(vm.$el.querySelector('.dragger')); dragSlider(vm.$el.querySelector('.dragger'));
vm.$nextTick(() => { 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