Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
34cd22d1a9
commit
2fe341d705
46 changed files with 391 additions and 223 deletions
|
@ -1 +0,0 @@
|
||||||
<svg width="17" height="17" viewBox="0 0 17 17" xmlns="http://www.w3.org/2000/svg"><path d="M.147 15.496l2.146-2.146-1.286-1.286a.55.55 0 0 1-.125-.616c.101-.238.277-.357.527-.357h4a.55.55 0 0 1 .402.17.55.55 0 0 1 .17.401v4c0 .25-.12.426-.358.527-.232.101-.437.06-.616-.125l-1.286-1.286-2.146 2.146-1.428-1.428zM14.996.646l1.428 1.43-2.146 2.145 1.286 1.286c.185.179.226.384.125.616-.101.238-.277.357-.527.357h-4a.55.55 0 0 1-.402-.17.55.55 0 0 1-.17-.401v-4c0-.25.12-.426.358-.527a.553.553 0 0 1 .616.125l1.286 1.286L14.996.647zm-13.42 0L3.72 2.794l1.286-1.286a.55.55 0 0 1 .616-.125c.238.101.357.277.357.527v4a.55.55 0 0 1-.17.402.55.55 0 0 1-.401.17h-4c-.25 0-.426-.12-.527-.358-.101-.232-.06-.437.125-.616l1.286-1.286L.147 2.075 1.575.647zm14.848 14.85l-1.428 1.428-2.146-2.146-1.286 1.286c-.179.185-.384.226-.616.125-.238-.101-.357-.277-.357-.527v-4a.55.55 0 0 1 .17-.402.55.55 0 0 1 .401-.17h4c.25 0 .426.12.527.358a.553.553 0 0 1-.125.616l-1.286 1.286 2.146 2.146z" fill-rule="evenodd"/></svg>
|
|
Before Width: | Height: | Size: 1,002 B |
|
@ -1 +0,0 @@
|
||||||
<svg width="15" height="15" viewBox="0 0 15 15" xmlns="http://www.w3.org/2000/svg"><path d="M8.591 5.056l2.147-2.146-1.286-1.286a.55.55 0 0 1-.125-.616c.101-.238.277-.357.527-.357h4a.55.55 0 0 1 .402.17.55.55 0 0 1 .17.401v4c0 .25-.12.426-.358.527-.232.101-.437.06-.616-.125l-1.286-1.286-2.146 2.147-1.429-1.43zM5.018 8.553l1.429 1.43L4.3 12.127l1.286 1.286c.185.179.226.384.125.616-.101.238-.277.357-.527.357h-4a.55.55 0 0 1-.402-.17.55.55 0 0 1-.17-.401v-4c0-.25.12-.426.358-.527a.553.553 0 0 1 .616.125L2.872 10.7l2.146-2.147zm4.964 0l2.146 2.147 1.286-1.286a.55.55 0 0 1 .616-.125c.238.101.357.277.357.527v4a.55.55 0 0 1-.17.402.55.55 0 0 1-.401.17h-4c-.25 0-.426-.12-.527-.358-.101-.232-.06-.437.125-.616l1.286-1.286-2.147-2.146 1.43-1.429zM6.447 5.018l-1.43 1.429L2.873 4.3 1.586 5.586c-.179.185-.384.226-.616.125-.238-.101-.357-.277-.357-.527v-4a.55.55 0 0 1 .17-.402.55.55 0 0 1 .401-.17h4c.25 0 .426.12.527.358a.553.553 0 0 1-.125.616L4.3 2.872l2.147 2.146z" fill-rule="evenodd"/></svg>
|
|
Before Width: | Height: | Size: 996 B |
|
@ -10,11 +10,19 @@ const notImplemented = () => {
|
||||||
throw new Error('Not implemented!');
|
throw new Error('Not implemented!');
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeIssueFromList = (state, listId, issueId) => {
|
const getListById = ({ state, listId }) => {
|
||||||
Vue.set(state.issuesByListId, listId, pull(state.issuesByListId[listId], issueId));
|
const listIndex = state.boardLists.findIndex(l => l.id === listId);
|
||||||
|
const list = state.boardLists[listIndex];
|
||||||
|
return { listIndex, list };
|
||||||
};
|
};
|
||||||
|
|
||||||
const addIssueToList = ({ state, listId, issueId, moveBeforeId, moveAfterId, atIndex }) => {
|
export const removeIssueFromList = ({ state, listId, issueId }) => {
|
||||||
|
Vue.set(state.issuesByListId, listId, pull(state.issuesByListId[listId], issueId));
|
||||||
|
const { listIndex, list } = getListById({ state, listId });
|
||||||
|
Vue.set(state.boardLists, listIndex, { ...list, issuesSize: list.issuesSize - 1 });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const addIssueToList = ({ state, listId, issueId, moveBeforeId, moveAfterId, atIndex }) => {
|
||||||
const listIssues = state.issuesByListId[listId];
|
const listIssues = state.issuesByListId[listId];
|
||||||
let newIndex = atIndex || 0;
|
let newIndex = atIndex || 0;
|
||||||
if (moveBeforeId) {
|
if (moveBeforeId) {
|
||||||
|
@ -24,6 +32,8 @@ const addIssueToList = ({ state, listId, issueId, moveBeforeId, moveAfterId, atI
|
||||||
}
|
}
|
||||||
listIssues.splice(newIndex, 0, issueId);
|
listIssues.splice(newIndex, 0, issueId);
|
||||||
Vue.set(state.issuesByListId, listId, listIssues);
|
Vue.set(state.issuesByListId, listId, listIssues);
|
||||||
|
const { listIndex, list } = getListById({ state, listId });
|
||||||
|
Vue.set(state.boardLists, listIndex, { ...list, issuesSize: list.issuesSize + 1 });
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -142,7 +152,7 @@ export default {
|
||||||
const issue = moveIssueListHelper(originalIssue, fromList, toList);
|
const issue = moveIssueListHelper(originalIssue, fromList, toList);
|
||||||
Vue.set(state.issues, issue.id, issue);
|
Vue.set(state.issues, issue.id, issue);
|
||||||
|
|
||||||
removeIssueFromList(state, fromListId, issue.id);
|
removeIssueFromList({ state, listId: fromListId, issueId: issue.id });
|
||||||
addIssueToList({ state, listId: toListId, issueId: issue.id, moveBeforeId, moveAfterId });
|
addIssueToList({ state, listId: toListId, issueId: issue.id, moveBeforeId, moveAfterId });
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -157,7 +167,7 @@ export default {
|
||||||
) => {
|
) => {
|
||||||
state.error = s__('Boards|An error occurred while moving the issue. Please try again.');
|
state.error = s__('Boards|An error occurred while moving the issue. Please try again.');
|
||||||
Vue.set(state.issues, originalIssue.id, originalIssue);
|
Vue.set(state.issues, originalIssue.id, originalIssue);
|
||||||
removeIssueFromList(state, toListId, originalIssue.id);
|
removeIssueFromList({ state, listId: toListId, issueId: originalIssue.id });
|
||||||
addIssueToList({
|
addIssueToList({
|
||||||
state,
|
state,
|
||||||
listId: fromListId,
|
listId: fromListId,
|
||||||
|
@ -187,7 +197,7 @@ export default {
|
||||||
|
|
||||||
[mutationTypes.ADD_ISSUE_TO_LIST_FAILURE]: (state, { list, issue }) => {
|
[mutationTypes.ADD_ISSUE_TO_LIST_FAILURE]: (state, { list, issue }) => {
|
||||||
state.error = s__('Boards|An error occurred while creating the issue. Please try again.');
|
state.error = s__('Boards|An error occurred while creating the issue. Please try again.');
|
||||||
removeIssueFromList(state, list.id, issue.id);
|
removeIssueFromList({ state, listId: list.id, issueId: issue.id });
|
||||||
},
|
},
|
||||||
|
|
||||||
[mutationTypes.SET_CURRENT_PAGE]: () => {
|
[mutationTypes.SET_CURRENT_PAGE]: () => {
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import collapseIcon from './icons/fullscreen_collapse.svg';
|
import { GlIcon } from '@gitlab/ui';
|
||||||
import expandIcon from './icons/fullscreen_expand.svg';
|
|
||||||
|
|
||||||
export default (ModalStore, boardsStore) => {
|
export default (ModalStore, boardsStore) => {
|
||||||
const issueBoardsContent = document.querySelector('.content-wrapper > .js-focus-mode-board');
|
const issueBoardsContent = document.querySelector('.content-wrapper > .js-focus-mode-board');
|
||||||
|
|
||||||
return new Vue({
|
return new Vue({
|
||||||
el: document.getElementById('js-toggle-focus-btn'),
|
el: document.getElementById('js-toggle-focus-btn'),
|
||||||
|
components: {
|
||||||
|
GlIcon,
|
||||||
|
},
|
||||||
data: {
|
data: {
|
||||||
modal: ModalStore.store,
|
modal: ModalStore.store,
|
||||||
store: boardsStore.state,
|
store: boardsStore.state,
|
||||||
|
@ -32,12 +34,7 @@ export default (ModalStore, boardsStore) => {
|
||||||
title="Toggle focus mode"
|
title="Toggle focus mode"
|
||||||
ref="toggleFocusModeButton"
|
ref="toggleFocusModeButton"
|
||||||
@click="toggleFocusMode">
|
@click="toggleFocusMode">
|
||||||
<span v-show="isFullscreen">
|
<gl-icon :name="isFullscreen ? 'minimize' : 'maximize'" />
|
||||||
${collapseIcon}
|
|
||||||
</span>
|
|
||||||
<span v-show="!isFullscreen">
|
|
||||||
${expandIcon}
|
|
||||||
</span>
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
|
|
|
@ -71,7 +71,7 @@ export default {
|
||||||
</gl-sprintf>
|
</gl-sprintf>
|
||||||
</p>
|
</p>
|
||||||
<a
|
<a
|
||||||
href="https://docs.gitlab.com/ee/ci/environments.html#stopping-an-environment"
|
href="https://docs.gitlab.com/ee/ci/environments/#stopping-an-environment"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>{{ s__('Environments|Learn more about stopping environments') }}</a
|
>{{ s__('Environments|Learn more about stopping environments') }}</a
|
||||||
|
|
|
@ -61,8 +61,8 @@ export const getFormatter = (format = SUPPORTED_FORMATS.engineering) => {
|
||||||
* @function
|
* @function
|
||||||
* @param {Number} value - Number to format
|
* @param {Number} value - Number to format
|
||||||
* @param {Number} fractionDigits - precision decimals
|
* @param {Number} fractionDigits - precision decimals
|
||||||
* @param {Number} maxLength - Max lenght of formatted number
|
* @param {Number} maxLength - Max length of formatted number
|
||||||
* if lenght is exceeded, exponential format is used.
|
* if length is exceeded, exponential format is used.
|
||||||
*/
|
*/
|
||||||
return numberFormatter();
|
return numberFormatter();
|
||||||
}
|
}
|
||||||
|
@ -73,8 +73,8 @@ export const getFormatter = (format = SUPPORTED_FORMATS.engineering) => {
|
||||||
* @function
|
* @function
|
||||||
* @param {Number} value - Number to format, `1` is rendered as `100%`
|
* @param {Number} value - Number to format, `1` is rendered as `100%`
|
||||||
* @param {Number} fractionDigits - number of precision decimals
|
* @param {Number} fractionDigits - number of precision decimals
|
||||||
* @param {Number} maxLength - Max lenght of formatted number
|
* @param {Number} maxLength - Max length of formatted number
|
||||||
* if lenght is exceeded, exponential format is used.
|
* if length is exceeded, exponential format is used.
|
||||||
*/
|
*/
|
||||||
return numberFormatter('percent');
|
return numberFormatter('percent');
|
||||||
}
|
}
|
||||||
|
@ -85,8 +85,8 @@ export const getFormatter = (format = SUPPORTED_FORMATS.engineering) => {
|
||||||
* @function
|
* @function
|
||||||
* @param {Number} value - Number to format, `100` is rendered as `100%`
|
* @param {Number} value - Number to format, `100` is rendered as `100%`
|
||||||
* @param {Number} fractionDigits - number of precision decimals
|
* @param {Number} fractionDigits - number of precision decimals
|
||||||
* @param {Number} maxLength - Max lenght of formatted number
|
* @param {Number} maxLength - Max length of formatted number
|
||||||
* if lenght is exceeded, exponential format is used.
|
* if length is exceeded, exponential format is used.
|
||||||
*/
|
*/
|
||||||
return numberFormatter('percent', 1 / 100);
|
return numberFormatter('percent', 1 / 100);
|
||||||
}
|
}
|
||||||
|
@ -100,8 +100,8 @@ export const getFormatter = (format = SUPPORTED_FORMATS.engineering) => {
|
||||||
* @function
|
* @function
|
||||||
* @param {Number} value - Number to format, `1` is rendered as `1s`
|
* @param {Number} value - Number to format, `1` is rendered as `1s`
|
||||||
* @param {Number} fractionDigits - number of precision decimals
|
* @param {Number} fractionDigits - number of precision decimals
|
||||||
* @param {Number} maxLength - Max lenght of formatted number
|
* @param {Number} maxLength - Max length of formatted number
|
||||||
* if lenght is exceeded, exponential format is used.
|
* if length is exceeded, exponential format is used.
|
||||||
*/
|
*/
|
||||||
return suffixFormatter(s__('Units|s'));
|
return suffixFormatter(s__('Units|s'));
|
||||||
}
|
}
|
||||||
|
@ -112,8 +112,8 @@ export const getFormatter = (format = SUPPORTED_FORMATS.engineering) => {
|
||||||
* @function
|
* @function
|
||||||
* @param {Number} value - Number to format, `1` is formatted as `1ms`
|
* @param {Number} value - Number to format, `1` is formatted as `1ms`
|
||||||
* @param {Number} fractionDigits - number of precision decimals
|
* @param {Number} fractionDigits - number of precision decimals
|
||||||
* @param {Number} maxLength - Max lenght of formatted number
|
* @param {Number} maxLength - Max length of formatted number
|
||||||
* if lenght is exceeded, exponential format is used.
|
* if length is exceeded, exponential format is used.
|
||||||
*/
|
*/
|
||||||
return suffixFormatter(s__('Units|ms'));
|
return suffixFormatter(s__('Units|ms'));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui';
|
import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui';
|
||||||
import { isEqual } from 'lodash';
|
import { isEqual, get } from 'lodash';
|
||||||
import expirationPolicyQuery from '../graphql/queries/get_expiration_policy.graphql';
|
import expirationPolicyQuery from '../graphql/queries/get_expiration_policy.graphql';
|
||||||
import { FETCH_SETTINGS_ERROR_MESSAGE } from '../../shared/constants';
|
import { FETCH_SETTINGS_ERROR_MESSAGE } from '../../shared/constants';
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ export default {
|
||||||
},
|
},
|
||||||
update: data => data.project?.containerExpirationPolicy,
|
update: data => data.project?.containerExpirationPolicy,
|
||||||
result({ data }) {
|
result({ data }) {
|
||||||
this.workingCopy = { ...data.project?.containerExpirationPolicy };
|
this.workingCopy = { ...get(data, 'project.containerExpirationPolicy', {}) };
|
||||||
},
|
},
|
||||||
error(e) {
|
error(e) {
|
||||||
this.fetchSettingsError = e;
|
this.fetchSettingsError = e;
|
||||||
|
@ -74,7 +74,7 @@ export default {
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<settings-form
|
<settings-form
|
||||||
v-if="containerExpirationPolicy"
|
v-if="!isDisabled"
|
||||||
v-model="workingCopy"
|
v-model="workingCopy"
|
||||||
:is-loading="$apollo.queries.containerExpirationPolicy.loading"
|
:is-loading="$apollo.queries.containerExpirationPolicy.loading"
|
||||||
:is-edited="isEdited"
|
:is-edited="isEdited"
|
||||||
|
|
|
@ -55,6 +55,14 @@ export default {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
prefilledForm() {
|
||||||
|
return {
|
||||||
|
...this.value,
|
||||||
|
cadence: this.findDefaultOption('cadence'),
|
||||||
|
keepN: this.findDefaultOption('keepN'),
|
||||||
|
olderThan: this.findDefaultOption('olderThan'),
|
||||||
|
};
|
||||||
|
},
|
||||||
showLoadingIcon() {
|
showLoadingIcon() {
|
||||||
return this.isLoading || this.mutationLoading;
|
return this.isLoading || this.mutationLoading;
|
||||||
},
|
},
|
||||||
|
@ -77,6 +85,9 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
findDefaultOption(option) {
|
||||||
|
return this.value[option] || this.$options.formOptions[option].find(f => f.default)?.key;
|
||||||
|
},
|
||||||
reset() {
|
reset() {
|
||||||
this.track('reset_form');
|
this.track('reset_form');
|
||||||
this.apiErrors = null;
|
this.apiErrors = null;
|
||||||
|
@ -135,7 +146,7 @@ export default {
|
||||||
</template>
|
</template>
|
||||||
<template #default>
|
<template #default>
|
||||||
<expiration-policy-fields
|
<expiration-policy-fields
|
||||||
:value="value"
|
:value="prefilledForm"
|
||||||
:form-options="$options.formOptions"
|
:form-options="$options.formOptions"
|
||||||
:is-loading="isLoading"
|
:is-loading="isLoading"
|
||||||
:api-errors="apiErrors"
|
:api-errors="apiErrors"
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import { GlToast } from '@gitlab/ui';
|
import { GlToast } from '@gitlab/ui';
|
||||||
import Translate from '~/vue_shared/translate';
|
import Translate from '~/vue_shared/translate';
|
||||||
|
import { parseBoolean } from '~/lib/utils/common_utils';
|
||||||
import RegistrySettingsApp from './components/registry_settings_app.vue';
|
import RegistrySettingsApp from './components/registry_settings_app.vue';
|
||||||
import { apolloProvider } from './graphql/index';
|
import { apolloProvider } from './graphql/index';
|
||||||
|
|
||||||
|
@ -21,9 +22,9 @@ export default () => {
|
||||||
},
|
},
|
||||||
provide: {
|
provide: {
|
||||||
projectPath,
|
projectPath,
|
||||||
isAdmin,
|
isAdmin: parseBoolean(isAdmin),
|
||||||
adminSettingsPath,
|
adminSettingsPath,
|
||||||
enableHistoricEntries,
|
enableHistoricEntries: parseBoolean(enableHistoricEntries),
|
||||||
},
|
},
|
||||||
render(createElement) {
|
render(createElement) {
|
||||||
return createElement('registry-settings-app', {});
|
return createElement('registry-settings-app', {});
|
||||||
|
|
|
@ -1,24 +1,34 @@
|
||||||
import initNotes from '~/init_notes';
|
|
||||||
import loadAwardsHandler from '~/awards_handler';
|
|
||||||
|
|
||||||
if (!gon.features.snippetsVue) {
|
if (!gon.features.snippetsVue) {
|
||||||
const LineHighlighterModule = import('~/line_highlighter');
|
const LineHighlighterModule = import('~/line_highlighter');
|
||||||
const BlobViewerModule = import('~/blob/viewer');
|
const BlobViewerModule = import('~/blob/viewer');
|
||||||
const ZenModeModule = import('~/zen_mode');
|
const ZenModeModule = import('~/zen_mode');
|
||||||
const SnippetEmbedModule = import('~/snippet/snippet_embed');
|
const SnippetEmbedModule = import('~/snippet/snippet_embed');
|
||||||
|
const initNotesModule = import('~/init_notes');
|
||||||
|
const loadAwardsHandlerModule = import('~/awards_handler');
|
||||||
|
|
||||||
Promise.all([LineHighlighterModule, BlobViewerModule, ZenModeModule, SnippetEmbedModule])
|
Promise.all([
|
||||||
|
LineHighlighterModule,
|
||||||
|
BlobViewerModule,
|
||||||
|
ZenModeModule,
|
||||||
|
SnippetEmbedModule,
|
||||||
|
initNotesModule,
|
||||||
|
loadAwardsHandlerModule,
|
||||||
|
])
|
||||||
.then(
|
.then(
|
||||||
([
|
([
|
||||||
{ default: LineHighlighter },
|
{ default: LineHighlighter },
|
||||||
{ default: BlobViewer },
|
{ default: BlobViewer },
|
||||||
{ default: ZenMode },
|
{ default: ZenMode },
|
||||||
{ default: SnippetEmbed },
|
{ default: SnippetEmbed },
|
||||||
|
{ default: initNotes },
|
||||||
|
{ default: loadAwardsHandler },
|
||||||
]) => {
|
]) => {
|
||||||
new LineHighlighter(); // eslint-disable-line no-new
|
new LineHighlighter(); // eslint-disable-line no-new
|
||||||
new BlobViewer(); // eslint-disable-line no-new
|
new BlobViewer(); // eslint-disable-line no-new
|
||||||
new ZenMode(); // eslint-disable-line no-new
|
new ZenMode(); // eslint-disable-line no-new
|
||||||
SnippetEmbed();
|
SnippetEmbed();
|
||||||
|
initNotes();
|
||||||
|
loadAwardsHandler();
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
|
@ -27,7 +37,12 @@ if (!gon.features.snippetsVue) {
|
||||||
.then(({ SnippetShowInit }) => {
|
.then(({ SnippetShowInit }) => {
|
||||||
SnippetShowInit();
|
SnippetShowInit();
|
||||||
})
|
})
|
||||||
|
.then(() => {
|
||||||
|
return Promise.all([import('~/init_notes'), import('~/awards_handler')]);
|
||||||
|
})
|
||||||
|
.then(([{ default: initNotes }, { default: loadAwardsHandler }]) => {
|
||||||
|
initNotes();
|
||||||
|
loadAwardsHandler();
|
||||||
|
})
|
||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
}
|
}
|
||||||
initNotes();
|
|
||||||
loadAwardsHandler();
|
|
||||||
|
|
|
@ -143,7 +143,7 @@ export default function simulateDrag(options) {
|
||||||
const dragInterval = setInterval(() => {
|
const dragInterval = setInterval(() => {
|
||||||
const progress = (new Date().getTime() - startTime) / duration;
|
const progress = (new Date().getTime() - startTime) / duration;
|
||||||
const x = fromRect.cx + (toRect.cx - fromRect.cx) * progress;
|
const x = fromRect.cx + (toRect.cx - fromRect.cx) * progress;
|
||||||
const y = fromRect.cy + (toRect.cy - fromRect.cy) * progress;
|
const y = fromRect.cy + (toRect.cy - fromRect.cy + options.extraHeight) * progress;
|
||||||
const overEl = fromEl.ownerDocument.elementFromPoint(x, y);
|
const overEl = fromEl.ownerDocument.elementFromPoint(x, y);
|
||||||
|
|
||||||
simulateEvent(overEl, 'pointermove', {
|
simulateEvent(overEl, 'pointermove', {
|
||||||
|
|
|
@ -26,7 +26,6 @@
|
||||||
@import './pages/incident_management_list';
|
@import './pages/incident_management_list';
|
||||||
@import './pages/issuable';
|
@import './pages/issuable';
|
||||||
@import './pages/issues/issue_count_badge';
|
@import './pages/issues/issue_count_badge';
|
||||||
@import './pages/issues/issues_list';
|
|
||||||
@import './pages/issues';
|
@import './pages/issues';
|
||||||
@import './pages/labels';
|
@import './pages/labels';
|
||||||
@import './pages/login';
|
@import './pages/login';
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
.user-can-drag {
|
|
||||||
cursor: grab;
|
|
||||||
}
|
|
||||||
|
|
||||||
.is-ghost {
|
|
||||||
opacity: 0.3;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
61
app/assets/stylesheets/page_bundles/issues_list.scss
Normal file
61
app/assets/stylesheets/page_bundles/issues_list.scss
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
@import 'mixins_and_variables_and_functions';
|
||||||
|
|
||||||
|
.issues-list {
|
||||||
|
&.manual-ordering {
|
||||||
|
background-color: var(--gray-10, $gray-10);
|
||||||
|
border-radius: $border-radius-default;
|
||||||
|
padding: $gl-padding-8;
|
||||||
|
|
||||||
|
.issue {
|
||||||
|
background-color: var(--white, $white);
|
||||||
|
margin-bottom: $gl-padding-8;
|
||||||
|
border-radius: $border-radius-default;
|
||||||
|
border: 1px solid var(--border-color, $border-color);
|
||||||
|
box-shadow: 0 1px 2px $issue-boards-card-shadow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.issue {
|
||||||
|
padding: 10px $gl-padding;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.issue-labels,
|
||||||
|
.author-link {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-merge-request-unmerged {
|
||||||
|
height: 13px;
|
||||||
|
margin-bottom: 3px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.issuable-list-root {
|
||||||
|
.gl-label-link {
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.svg-container.jira-logo-container {
|
||||||
|
svg {
|
||||||
|
vertical-align: text-bottom;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-can-drag {
|
||||||
|
cursor: grab;
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-ghost {
|
||||||
|
opacity: 0.3;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
|
@ -1,38 +1,3 @@
|
||||||
.issues-list {
|
|
||||||
&.manual-ordering {
|
|
||||||
background-color: $gray-light;
|
|
||||||
border-radius: $border-radius-default;
|
|
||||||
padding: $gl-padding-8;
|
|
||||||
|
|
||||||
.issue {
|
|
||||||
background-color: $white;
|
|
||||||
margin-bottom: $gl-padding-8;
|
|
||||||
border-radius: $border-radius-default;
|
|
||||||
border: 1px solid $gray-100;
|
|
||||||
box-shadow: 0 1px 2px $issue-boards-card-shadow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.issue {
|
|
||||||
padding: 10px $gl-padding;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
.title {
|
|
||||||
margin-bottom: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.issue-labels,
|
|
||||||
.author-link {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-merge-request-unmerged {
|
|
||||||
height: 13px;
|
|
||||||
margin-bottom: 3px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.issue-realtime-pre-pulse {
|
.issue-realtime-pre-pulse {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
@ -369,13 +334,3 @@ ul.related-merge-requests > li {
|
||||||
.issuable-header-slide-leave-to {
|
.issuable-header-slide-leave-to {
|
||||||
transform: translateY(-100%);
|
transform: translateY(-100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.issuable-list-root {
|
|
||||||
.gl-label-link {
|
|
||||||
text-decoration: none;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
.svg-container.jira-logo-container {
|
|
||||||
svg {
|
|
||||||
vertical-align: text-bottom;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -6,7 +6,7 @@ require 'uri'
|
||||||
module ApplicationHelper
|
module ApplicationHelper
|
||||||
include StartupCssHelper
|
include StartupCssHelper
|
||||||
|
|
||||||
# See https://docs.gitlab.com/ee/development/ee_features.html#code-in-app-views
|
# See https://docs.gitlab.com/ee/development/ee_features.html#code-in-appviews
|
||||||
# rubocop: disable CodeReuse/ActiveRecord
|
# rubocop: disable CodeReuse/ActiveRecord
|
||||||
# We allow partial to be nil so that collection views can be passed in
|
# We allow partial to be nil so that collection views can be passed in
|
||||||
# `render partial: 'some/view', collection: @some_collection`
|
# `render partial: 'some/view', collection: @some_collection`
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
# The usage of the ReactiveCaching module is documented here:
|
# The usage of the ReactiveCaching module is documented here:
|
||||||
# https://docs.gitlab.com/ee/development/utilities.html#reactivecaching
|
# https://docs.gitlab.com/ee/development/reactive_caching.md
|
||||||
module ReactiveCaching
|
module ReactiveCaching
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ module Ci
|
||||||
rescue => e
|
rescue => e
|
||||||
# Tracks this error with application logs, Sentry, and Prometheus.
|
# Tracks this error with application logs, Sentry, and Prometheus.
|
||||||
# If `archive!` keeps failing for over a week, that could incur data loss.
|
# If `archive!` keeps failing for over a week, that could incur data loss.
|
||||||
# (See more https://docs.gitlab.com/ee/administration/job_traces.html#new-live-trace-architecture)
|
# (See more https://docs.gitlab.com/ee/administration/job_logs.html#new-incremental-logging-architecture)
|
||||||
# In order to avoid interrupting the system, we do not raise an exception here.
|
# In order to avoid interrupting the system, we do not raise an exception here.
|
||||||
archive_error(e, job, worker_name)
|
archive_error(e, job, worker_name)
|
||||||
end
|
end
|
||||||
|
|
|
@ -18,7 +18,7 @@ module Metrics
|
||||||
# Determines whether the provided params are sufficient
|
# Determines whether the provided params are sufficient
|
||||||
# to uniquely identify a panel from a yml-defined dashboard.
|
# to uniquely identify a panel from a yml-defined dashboard.
|
||||||
#
|
#
|
||||||
# See https://docs.gitlab.com/ee/operations/metrics/dashboards/index.html#defining-custom-dashboards-per-project
|
# See https://docs.gitlab.com/ee/operations/metrics/dashboards/index.html
|
||||||
# for additional info on defining custom dashboards.
|
# for additional info on defining custom dashboards.
|
||||||
def valid_params?(params)
|
def valid_params?(params)
|
||||||
[
|
[
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
- @can_bulk_update = can?(current_user, :admin_issue, @group) && @group.feature_available?(:group_bulk_edit)
|
- @can_bulk_update = can?(current_user, :admin_issue, @group) && @group.feature_available?(:group_bulk_edit)
|
||||||
|
|
||||||
- page_title _("Issues")
|
- page_title _("Issues")
|
||||||
|
- add_page_specific_style 'page_bundles/issues_list'
|
||||||
= content_for :meta_tags do
|
= content_for :meta_tags do
|
||||||
= auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{@group.name} issues")
|
= auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{@group.name} issues")
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
- page_title _("Issues")
|
- page_title _("Issues")
|
||||||
- new_issue_email = @project.new_issuable_address(current_user, 'issue')
|
- new_issue_email = @project.new_issuable_address(current_user, 'issue')
|
||||||
- add_page_specific_style 'page_bundles/issues'
|
- add_page_specific_style 'page_bundles/issues_list'
|
||||||
|
|
||||||
= content_for :meta_tags do
|
= content_for :meta_tags do
|
||||||
= auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{@project.name} issues")
|
= auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{@project.name} issues")
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
- @can_bulk_update = false
|
- @can_bulk_update = false
|
||||||
|
|
||||||
- page_title _("Service Desk")
|
- page_title _("Service Desk")
|
||||||
|
- add_page_specific_style 'page_bundles/issues_list'
|
||||||
- content_for :breadcrumbs_extra do
|
- content_for :breadcrumbs_extra do
|
||||||
= render "projects/issues/nav_btns", show_export_button: false, show_rss_button: false
|
= render "projects/issues/nav_btns", show_export_button: false, show_rss_button: false
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,6 @@
|
||||||
- can_report_spam = @issue.submittable_as_spam_by?(current_user)
|
- can_report_spam = @issue.submittable_as_spam_by?(current_user)
|
||||||
- can_create_issue = show_new_issue_link?(@project)
|
- can_create_issue = show_new_issue_link?(@project)
|
||||||
- related_branches_path = related_branches_project_issue_path(@project, @issue)
|
- related_branches_path = related_branches_project_issue_path(@project, @issue)
|
||||||
- add_page_specific_style 'page_bundles/issues'
|
|
||||||
|
|
||||||
= render_if_exists "projects/issues/alert_blocked", issue: @issue, current_user: current_user
|
= render_if_exists "projects/issues/alert_blocked", issue: @issue, current_user: current_user
|
||||||
= render "projects/issues/alert_moved_from_service_desk", issue: @issue
|
= render "projects/issues/alert_moved_from_service_desk", issue: @issue
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Update toggle focus mode icon to gl-icon
|
||||||
|
merge_request: 43888
|
||||||
|
author:
|
||||||
|
type: changed
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Fix the ability to assign labels based on license feature availability.
|
||||||
|
merge_request: 44171
|
||||||
|
author:
|
||||||
|
type: fixed
|
5
changelogs/unreleased/docs-test-hardcoded-docs-links.yml
Normal file
5
changelogs/unreleased/docs-test-hardcoded-docs-links.yml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Update doc links in app
|
||||||
|
merge_request: 44134
|
||||||
|
author:
|
||||||
|
type: other
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Fix GraphQL backward pagination when merge requests are ordered by merged_at
|
||||||
|
merge_request: 43701
|
||||||
|
author:
|
||||||
|
type: fixed
|
|
@ -189,7 +189,7 @@ module Gitlab
|
||||||
config.assets.precompile << "page_bundles/boards.css"
|
config.assets.precompile << "page_bundles/boards.css"
|
||||||
config.assets.precompile << "page_bundles/cycle_analytics.css"
|
config.assets.precompile << "page_bundles/cycle_analytics.css"
|
||||||
config.assets.precompile << "page_bundles/ide.css"
|
config.assets.precompile << "page_bundles/ide.css"
|
||||||
config.assets.precompile << "page_bundles/issues.css"
|
config.assets.precompile << "page_bundles/issues_list.css"
|
||||||
config.assets.precompile << "page_bundles/jira_connect.css"
|
config.assets.precompile << "page_bundles/jira_connect.css"
|
||||||
config.assets.precompile << "page_bundles/milestone.css"
|
config.assets.precompile << "page_bundles/milestone.css"
|
||||||
config.assets.precompile << "page_bundles/todos.css"
|
config.assets.precompile << "page_bundles/todos.css"
|
||||||
|
|
|
@ -73,18 +73,3 @@ The **Merge Request Analytics** feature can be accessed only:
|
||||||
|
|
||||||
- On [Starter](https://about.gitlab.com/pricing/) and above.
|
- On [Starter](https://about.gitlab.com/pricing/) and above.
|
||||||
- By users with [Reporter access](../permissions.md) and above.
|
- By users with [Reporter access](../permissions.md) and above.
|
||||||
|
|
||||||
## Enable and disable related feature flags
|
|
||||||
|
|
||||||
Merge Request Analytics is disabled by default but can be enabled using the following
|
|
||||||
[feature flag](../../development/feature_flags/development.md#enabling-a-feature-flag-locally-in-development):
|
|
||||||
|
|
||||||
- `project_merge_request_analytics`
|
|
||||||
|
|
||||||
A GitLab administrator can:
|
|
||||||
|
|
||||||
- Enable this feature by running the following command in a Rails console:
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
Feature.enable(:project_merge_request_analytics)
|
|
||||||
```
|
|
||||||
|
|
|
@ -62,29 +62,3 @@ The **Productivity Analytics** dashboard can be accessed only:
|
||||||
|
|
||||||
- On [Premium or Silver tier](https://about.gitlab.com/pricing/) and above.
|
- On [Premium or Silver tier](https://about.gitlab.com/pricing/) and above.
|
||||||
- By users with [Reporter access](../permissions.md) and above.
|
- By users with [Reporter access](../permissions.md) and above.
|
||||||
|
|
||||||
## Enabling and disabling using feature flags
|
|
||||||
|
|
||||||
Productivity Analytics is:
|
|
||||||
|
|
||||||
- [Enabled by default](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/18754) from GitLab 12.4,
|
|
||||||
but can be disabled using the following feature flags:
|
|
||||||
- `productivity_analytics`.
|
|
||||||
- `productivity_analytics_scatterplot_enabled`.
|
|
||||||
- Disabled by default in GitLab 12.3, but can be enabled using the following feature flag:
|
|
||||||
- `productivity_analytics`.
|
|
||||||
|
|
||||||
A GitLab administrator can:
|
|
||||||
|
|
||||||
- Disable this feature from GitLab 12.4 by running the follow in a Rails console:
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
Feature.disable(:productivity_analytics)
|
|
||||||
Feature.disable(:productivity_analytics_scatterplot_enabled)
|
|
||||||
```
|
|
||||||
|
|
||||||
- Enable this feature in GitLab 12.3 by running the following in a Rails console:
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
Feature.enable(:productivity_analytics)
|
|
||||||
```
|
|
||||||
|
|
|
@ -110,8 +110,7 @@ module Gitlab
|
||||||
end
|
end
|
||||||
|
|
||||||
if last
|
if last
|
||||||
# grab one more than we need
|
paginated_nodes = LastItems.take_items(sliced_nodes, limit_value + 1)
|
||||||
paginated_nodes = sliced_nodes.last(limit_value + 1)
|
|
||||||
|
|
||||||
# there is an extra node, so there is a previous page
|
# there is an extra node, so there is a previous page
|
||||||
@has_previous_page = paginated_nodes.count > limit_value
|
@has_previous_page = paginated_nodes.count > limit_value
|
||||||
|
|
57
lib/gitlab/graphql/pagination/keyset/last_items.rb
Normal file
57
lib/gitlab/graphql/pagination/keyset/last_items.rb
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Gitlab
|
||||||
|
module Graphql
|
||||||
|
module Pagination
|
||||||
|
module Keyset
|
||||||
|
# This class handles the last(N) ActiveRecord call even if a special ORDER BY configuration is present.
|
||||||
|
# For the last(N) call, ActiveRecord calls reverse_order, however for some cases it raises
|
||||||
|
# ActiveRecord::IrreversibleOrderError error.
|
||||||
|
class LastItems
|
||||||
|
# rubocop: disable CodeReuse/ActiveRecord
|
||||||
|
def self.take_items(scope, count)
|
||||||
|
if custom_order = lookup_custom_reverse_order(scope.order_values)
|
||||||
|
items = scope.reorder(*custom_order).first(count) # returns a single record when count is nil
|
||||||
|
items.is_a?(Array) ? items.reverse : items
|
||||||
|
else
|
||||||
|
scope.last(count)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
# rubocop: enable CodeReuse/ActiveRecord
|
||||||
|
|
||||||
|
# Detect special ordering and provide the reversed order
|
||||||
|
def self.lookup_custom_reverse_order(order_values)
|
||||||
|
if ordering_by_merged_at_and_mr_id_desc?(order_values)
|
||||||
|
[
|
||||||
|
Gitlab::Database.nulls_first_order('merge_request_metrics.merged_at', 'ASC'), # reversing the order
|
||||||
|
MergeRequest.arel_table[:id].asc
|
||||||
|
]
|
||||||
|
elsif ordering_by_merged_at_and_mr_id_asc?(order_values)
|
||||||
|
[
|
||||||
|
Gitlab::Database.nulls_first_order('merge_request_metrics.merged_at', 'DESC'),
|
||||||
|
MergeRequest.arel_table[:id].asc
|
||||||
|
]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.ordering_by_merged_at_and_mr_id_desc?(order_values)
|
||||||
|
order_values.size == 2 &&
|
||||||
|
order_values.first.to_s == Gitlab::Database.nulls_last_order('merge_request_metrics.merged_at', 'DESC') &&
|
||||||
|
order_values.last.is_a?(Arel::Nodes::Descending) &&
|
||||||
|
order_values.last.to_sql == MergeRequest.arel_table[:id].desc.to_sql
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.ordering_by_merged_at_and_mr_id_asc?(order_values)
|
||||||
|
order_values.size == 2 &&
|
||||||
|
order_values.first.to_s == Gitlab::Database.nulls_last_order('merge_request_metrics.merged_at', 'ASC') &&
|
||||||
|
order_values.last.is_a?(Arel::Nodes::Descending) &&
|
||||||
|
order_values.last.to_sql == MergeRequest.arel_table[:id].desc.to_sql
|
||||||
|
end
|
||||||
|
|
||||||
|
private_class_method :ordering_by_merged_at_and_mr_id_desc?
|
||||||
|
private_class_method :ordering_by_merged_at_and_mr_id_asc?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -22,7 +22,7 @@ end
|
||||||
|
|
||||||
def rename_image(file, milestone)
|
def rename_image(file, milestone)
|
||||||
path = File.dirname(file)
|
path = File.dirname(file)
|
||||||
basename = File.basename(file)
|
basename = File.basename(file, ".*")
|
||||||
final_name = File.join(path, "#{basename}_v#{milestone}.png")
|
final_name = File.join(path, "#{basename}_v#{milestone}.png")
|
||||||
FileUtils.mv(file, final_name)
|
FileUtils.mv(file, final_name)
|
||||||
end
|
end
|
||||||
|
|
|
@ -42,7 +42,7 @@ RSpec.describe LabelsFinder do
|
||||||
|
|
||||||
finder = described_class.new(user)
|
finder = described_class.new(user)
|
||||||
|
|
||||||
expect(finder.execute).to eq [group_label_2, group_label_3, project_label_1, group_label_1, project_label_2, project_label_4]
|
expect(finder.execute).to match_array([group_label_2, group_label_3, project_label_1, group_label_1, project_label_2, project_label_4])
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns labels available if nil title is supplied' do
|
it 'returns labels available if nil title is supplied' do
|
||||||
|
@ -50,7 +50,7 @@ RSpec.describe LabelsFinder do
|
||||||
# params[:title] will return `nil` regardless whether it is specified
|
# params[:title] will return `nil` regardless whether it is specified
|
||||||
finder = described_class.new(user, title: nil)
|
finder = described_class.new(user, title: nil)
|
||||||
|
|
||||||
expect(finder.execute).to eq [group_label_2, group_label_3, project_label_1, group_label_1, project_label_2, project_label_4]
|
expect(finder.execute).to match_array([group_label_2, group_label_3, project_label_1, group_label_1, project_label_2, project_label_4])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -60,7 +60,7 @@ RSpec.describe LabelsFinder do
|
||||||
::Projects::UpdateService.new(project_1, user, archived: true).execute
|
::Projects::UpdateService.new(project_1, user, archived: true).execute
|
||||||
finder = described_class.new(user, **group_params(group_1))
|
finder = described_class.new(user, **group_params(group_1))
|
||||||
|
|
||||||
expect(finder.execute).to eq [group_label_2, group_label_1, project_label_5]
|
expect(finder.execute).to match_array([group_label_2, group_label_1, project_label_5])
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when only_group_labels is true' do
|
context 'when only_group_labels is true' do
|
||||||
|
@ -69,7 +69,7 @@ RSpec.describe LabelsFinder do
|
||||||
|
|
||||||
finder = described_class.new(user, only_group_labels: true, **group_params(group_1))
|
finder = described_class.new(user, only_group_labels: true, **group_params(group_1))
|
||||||
|
|
||||||
expect(finder.execute).to eq [group_label_2, group_label_1]
|
expect(finder.execute).to match_array([group_label_2, group_label_1])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -86,7 +86,7 @@ RSpec.describe LabelsFinder do
|
||||||
it 'returns group labels' do
|
it 'returns group labels' do
|
||||||
finder = described_class.new(user, **group_params(empty_group))
|
finder = described_class.new(user, **group_params(empty_group))
|
||||||
|
|
||||||
expect(finder.execute).to eq [empty_group_label_1, empty_group_label_2]
|
expect(finder.execute).to match_array([empty_group_label_1, empty_group_label_2])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -98,7 +98,7 @@ RSpec.describe LabelsFinder do
|
||||||
|
|
||||||
finder = described_class.new(user, **group_params(private_subgroup_1), only_group_labels: true, include_ancestor_groups: true)
|
finder = described_class.new(user, **group_params(private_subgroup_1), only_group_labels: true, include_ancestor_groups: true)
|
||||||
|
|
||||||
expect(finder.execute).to eq [private_group_label_1, private_subgroup_label_1]
|
expect(finder.execute).to match_array([private_group_label_1, private_subgroup_label_1])
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'ignores labels from groups which user can not read' do
|
it 'ignores labels from groups which user can not read' do
|
||||||
|
@ -106,7 +106,7 @@ RSpec.describe LabelsFinder do
|
||||||
|
|
||||||
finder = described_class.new(user, **group_params(private_subgroup_1), only_group_labels: true, include_ancestor_groups: true)
|
finder = described_class.new(user, **group_params(private_subgroup_1), only_group_labels: true, include_ancestor_groups: true)
|
||||||
|
|
||||||
expect(finder.execute).to eq [private_subgroup_label_1]
|
expect(finder.execute).to match_array([private_subgroup_label_1])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -117,7 +117,7 @@ RSpec.describe LabelsFinder do
|
||||||
|
|
||||||
finder = described_class.new(user, **group_params(private_group_1), only_group_labels: true, include_descendant_groups: true)
|
finder = described_class.new(user, **group_params(private_group_1), only_group_labels: true, include_descendant_groups: true)
|
||||||
|
|
||||||
expect(finder.execute).to eq [private_group_label_1, private_subgroup_label_1]
|
expect(finder.execute).to match_array([private_group_label_1, private_subgroup_label_1])
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'ignores labels from groups which user can not read' do
|
it 'ignores labels from groups which user can not read' do
|
||||||
|
@ -125,7 +125,7 @@ RSpec.describe LabelsFinder do
|
||||||
|
|
||||||
finder = described_class.new(user, **group_params(private_group_1), only_group_labels: true, include_descendant_groups: true)
|
finder = described_class.new(user, **group_params(private_group_1), only_group_labels: true, include_descendant_groups: true)
|
||||||
|
|
||||||
expect(finder.execute).to eq [private_subgroup_label_1]
|
expect(finder.execute).to match_array([private_subgroup_label_1])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -140,13 +140,13 @@ RSpec.describe LabelsFinder do
|
||||||
|
|
||||||
shared_examples 'with full visibility' do
|
shared_examples 'with full visibility' do
|
||||||
it 'returns all projects labels' do
|
it 'returns all projects labels' do
|
||||||
expect(finder.execute).to eq [group_label_1, limited_visibility_label, visible_label]
|
expect(finder.execute).to match_array([group_label_1, limited_visibility_label, visible_label])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
shared_examples 'with limited visibility' do
|
shared_examples 'with limited visibility' do
|
||||||
it 'returns only authorized projects labels' do
|
it 'returns only authorized projects labels' do
|
||||||
expect(finder.execute).to eq [group_label_1, visible_label]
|
expect(finder.execute).to match_array([group_label_1, visible_label])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -249,7 +249,7 @@ RSpec.describe LabelsFinder do
|
||||||
it 'returns labels available for the project' do
|
it 'returns labels available for the project' do
|
||||||
finder = described_class.new(user, project_id: project_1.id)
|
finder = described_class.new(user, project_id: project_1.id)
|
||||||
|
|
||||||
expect(finder.execute).to eq [group_label_2, project_label_1, group_label_1]
|
expect(finder.execute).to match_array([group_label_2, project_label_1, group_label_1])
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'as an administrator' do
|
context 'as an administrator' do
|
||||||
|
@ -272,13 +272,13 @@ RSpec.describe LabelsFinder do
|
||||||
it 'returns label with that title' do
|
it 'returns label with that title' do
|
||||||
finder = described_class.new(user, title: 'Group Label 2')
|
finder = described_class.new(user, title: 'Group Label 2')
|
||||||
|
|
||||||
expect(finder.execute).to eq [group_label_2]
|
expect(finder.execute).to match_array([group_label_2])
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns label with title alias' do
|
it 'returns label with title alias' do
|
||||||
finder = described_class.new(user, name: 'Group Label 2')
|
finder = described_class.new(user, name: 'Group Label 2')
|
||||||
|
|
||||||
expect(finder.execute).to eq [group_label_2]
|
expect(finder.execute).to match_array([group_label_2])
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns no labels if empty title is supplied' do
|
it 'returns no labels if empty title is supplied' do
|
||||||
|
@ -304,19 +304,19 @@ RSpec.describe LabelsFinder do
|
||||||
it 'returns labels with a partially matching title' do
|
it 'returns labels with a partially matching title' do
|
||||||
finder = described_class.new(user, search: '(group)')
|
finder = described_class.new(user, search: '(group)')
|
||||||
|
|
||||||
expect(finder.execute).to eq [group_label_1]
|
expect(finder.execute).to match_array([group_label_1])
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns labels with a partially matching description' do
|
it 'returns labels with a partially matching description' do
|
||||||
finder = described_class.new(user, search: 'awesome')
|
finder = described_class.new(user, search: 'awesome')
|
||||||
|
|
||||||
expect(finder.execute).to eq [project_label_1]
|
expect(finder.execute).to match_array([project_label_1])
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns labels matching a single character' do
|
it 'returns labels matching a single character' do
|
||||||
finder = described_class.new(user, search: '(')
|
finder = described_class.new(user, search: '(')
|
||||||
|
|
||||||
expect(finder.execute).to eq [group_label_1]
|
expect(finder.execute).to match_array([group_label_1])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -326,7 +326,7 @@ RSpec.describe LabelsFinder do
|
||||||
|
|
||||||
finder = described_class.new(user, subscribed: 'true')
|
finder = described_class.new(user, subscribed: 'true')
|
||||||
|
|
||||||
expect(finder.execute).to eq [project_label_1]
|
expect(finder.execute).to match_array([project_label_1])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -322,6 +322,7 @@ describe('Board Store Mutations', () => {
|
||||||
state = {
|
state = {
|
||||||
...state,
|
...state,
|
||||||
issuesByListId: listIssues,
|
issuesByListId: listIssues,
|
||||||
|
boardLists: mockListsWithModel,
|
||||||
};
|
};
|
||||||
|
|
||||||
mutations.MOVE_ISSUE_FAILURE(state, {
|
mutations.MOVE_ISSUE_FAILURE(state, {
|
||||||
|
@ -389,6 +390,7 @@ describe('Board Store Mutations', () => {
|
||||||
...state,
|
...state,
|
||||||
issuesByListId: listIssues,
|
issuesByListId: listIssues,
|
||||||
issues,
|
issues,
|
||||||
|
boardLists: mockListsWithModel,
|
||||||
};
|
};
|
||||||
|
|
||||||
mutations.ADD_ISSUE_TO_LIST_FAILURE(state, { list: mockLists[0], issue: mockIssue2 });
|
mutations.ADD_ISSUE_TO_LIST_FAILURE(state, { list: mockLists[0], issue: mockIssue2 });
|
||||||
|
|
|
@ -11,7 +11,7 @@ import {
|
||||||
UNAVAILABLE_USER_FEATURE_TEXT,
|
UNAVAILABLE_USER_FEATURE_TEXT,
|
||||||
} from '~/registry/settings/constants';
|
} from '~/registry/settings/constants';
|
||||||
|
|
||||||
import { expirationPolicyPayload } from '../mock_data';
|
import { expirationPolicyPayload, emptyExpirationPolicyPayload } from '../mock_data';
|
||||||
|
|
||||||
const localVue = createLocalVue();
|
const localVue = createLocalVue();
|
||||||
|
|
||||||
|
@ -115,4 +115,23 @@ describe('Registry Settings App', () => {
|
||||||
expect(findAlert().html()).toContain(FETCH_SETTINGS_ERROR_MESSAGE);
|
expect(findAlert().html()).toContain(FETCH_SETTINGS_ERROR_MESSAGE);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('empty API response', () => {
|
||||||
|
it.each`
|
||||||
|
enableHistoricEntries | isShown
|
||||||
|
${true} | ${true}
|
||||||
|
${false} | ${false}
|
||||||
|
`('is $isShown that the form is shown', async ({ enableHistoricEntries, isShown }) => {
|
||||||
|
const requests = mountComponentWithApollo({
|
||||||
|
provide: {
|
||||||
|
...defaultProvidedValues,
|
||||||
|
enableHistoricEntries,
|
||||||
|
},
|
||||||
|
resolver: jest.fn().mockResolvedValue(emptyExpirationPolicyPayload()),
|
||||||
|
});
|
||||||
|
await Promise.all(requests);
|
||||||
|
|
||||||
|
expect(findSettingsComponent().exists()).toBe(isShown);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -123,6 +123,15 @@ describe('Settings Form', () => {
|
||||||
findFields().vm.$emit('input', { newValue: 'foo', modified: 'baz' });
|
findFields().vm.$emit('input', { newValue: 'foo', modified: 'baz' });
|
||||||
expect(findFields().props('apiErrors')).toEqual({});
|
expect(findFields().props('apiErrors')).toEqual({});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('shows the default option when none are selected', () => {
|
||||||
|
mountComponent({ props: { value: {} } });
|
||||||
|
expect(findFields().props('value')).toEqual({
|
||||||
|
cadence: 'EVERY_DAY',
|
||||||
|
keepN: 'TEN_TAGS',
|
||||||
|
olderThan: 'NINETY_DAYS',
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('form', () => {
|
describe('form', () => {
|
||||||
|
|
|
@ -14,6 +14,14 @@ export const expirationPolicyPayload = override => ({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const emptyExpirationPolicyPayload = () => ({
|
||||||
|
data: {
|
||||||
|
project: {
|
||||||
|
containerExpirationPolicy: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export const expirationPolicyMutationPayload = ({ override, errors = [] } = {}) => ({
|
export const expirationPolicyMutationPayload = ({ override, errors = [] } = {}) => ({
|
||||||
data: {
|
data: {
|
||||||
updateContainerExpirationPolicy: {
|
updateContainerExpirationPolicy: {
|
||||||
|
|
|
@ -5,19 +5,19 @@ require "spec_helper"
|
||||||
RSpec.describe NotesHelper do
|
RSpec.describe NotesHelper do
|
||||||
include RepoHelpers
|
include RepoHelpers
|
||||||
|
|
||||||
let(:owner) { create(:owner) }
|
let_it_be(:owner) { create(:owner) }
|
||||||
let(:group) { create(:group) }
|
let_it_be(:group) { create(:group) }
|
||||||
let(:project) { create(:project, namespace: group) }
|
let_it_be(:project) { create(:project, namespace: group) }
|
||||||
let(:maintainer) { create(:user) }
|
let_it_be(:maintainer) { create(:user) }
|
||||||
let(:reporter) { create(:user) }
|
let_it_be(:reporter) { create(:user) }
|
||||||
let(:guest) { create(:user) }
|
let_it_be(:guest) { create(:user) }
|
||||||
|
|
||||||
let(:owner_note) { create(:note, author: owner, project: project) }
|
let_it_be(:owner_note) { create(:note, author: owner, project: project) }
|
||||||
let(:maintainer_note) { create(:note, author: maintainer, project: project) }
|
let_it_be(:maintainer_note) { create(:note, author: maintainer, project: project) }
|
||||||
let(:reporter_note) { create(:note, author: reporter, project: project) }
|
let_it_be(:reporter_note) { create(:note, author: reporter, project: project) }
|
||||||
let!(:notes) { [owner_note, maintainer_note, reporter_note] }
|
let!(:notes) { [owner_note, maintainer_note, reporter_note] }
|
||||||
|
|
||||||
before do
|
before_all do
|
||||||
group.add_owner(owner)
|
group.add_owner(owner)
|
||||||
project.add_maintainer(maintainer)
|
project.add_maintainer(maintainer)
|
||||||
project.add_reporter(reporter)
|
project.add_reporter(reporter)
|
||||||
|
@ -72,14 +72,14 @@ RSpec.describe NotesHelper do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#discussion_path' do
|
describe '#discussion_path' do
|
||||||
let(:project) { create(:project, :repository) }
|
let_it_be(:project) { create(:project, :repository) }
|
||||||
let(:anchor) { discussion.line_code }
|
let(:anchor) { discussion.line_code }
|
||||||
|
|
||||||
context 'for a merge request discusion' do
|
context 'for a merge request discusion' do
|
||||||
let(:merge_request) { create(:merge_request, source_project: project, target_project: project, importing: true) }
|
let_it_be(:merge_request) { create(:merge_request, source_project: project, target_project: project, importing: true) }
|
||||||
let!(:merge_request_diff1) { merge_request.merge_request_diffs.create!(head_commit_sha: '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') }
|
let_it_be(:merge_request_diff1) { merge_request.merge_request_diffs.create!(head_commit_sha: '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') }
|
||||||
let!(:merge_request_diff2) { merge_request.merge_request_diffs.create!(head_commit_sha: nil) }
|
let_it_be(:merge_request_diff2) { merge_request.merge_request_diffs.create!(head_commit_sha: nil) }
|
||||||
let!(:merge_request_diff3) { merge_request.merge_request_diffs.create!(head_commit_sha: '5937ac0a7beb003549fc5fd26fc247adbce4a52e') }
|
let_it_be(:merge_request_diff3) { merge_request.merge_request_diffs.create!(head_commit_sha: '5937ac0a7beb003549fc5fd26fc247adbce4a52e') }
|
||||||
|
|
||||||
context 'for a diff discussion' do
|
context 'for a diff discussion' do
|
||||||
context 'when the discussion is active' do
|
context 'when the discussion is active' do
|
||||||
|
@ -229,20 +229,18 @@ RSpec.describe NotesHelper do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'return project notes path for project snippet' do
|
it 'return project notes path for project snippet' do
|
||||||
namespace = create(:namespace, path: 'nm')
|
@project = project
|
||||||
@project = create(:project, path: 'test', namespace: namespace)
|
|
||||||
@snippet = create(:project_snippet, project: @project)
|
@snippet = create(:project_snippet, project: @project)
|
||||||
@noteable = @snippet
|
@noteable = @snippet
|
||||||
|
|
||||||
expect(helper.notes_url).to eq("/nm/test/noteable/project_snippet/#{@noteable.id}/notes")
|
expect(helper.notes_url).to eq("/#{project.full_path}/noteable/project_snippet/#{@noteable.id}/notes")
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'return project notes path for other noteables' do
|
it 'return project notes path for other noteables' do
|
||||||
namespace = create(:namespace, path: 'nm')
|
@project = project
|
||||||
@project = create(:project, path: 'test', namespace: namespace)
|
|
||||||
@noteable = create(:issue, project: @project)
|
@noteable = create(:issue, project: @project)
|
||||||
|
|
||||||
expect(helper.notes_url).to eq("/nm/test/noteable/issue/#{@noteable.id}/notes")
|
expect(helper.notes_url).to eq("/#{@project.full_path}/noteable/issue/#{@noteable.id}/notes")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -254,19 +252,17 @@ RSpec.describe NotesHelper do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'return project notes path for project snippet' do
|
it 'return project notes path for project snippet' do
|
||||||
namespace = create(:namespace, path: 'nm')
|
@project = project
|
||||||
@project = create(:project, path: 'test', namespace: namespace)
|
|
||||||
note = create(:note_on_project_snippet, project: @project)
|
note = create(:note_on_project_snippet, project: @project)
|
||||||
|
|
||||||
expect(helper.note_url(note)).to eq("/nm/test/notes/#{note.id}")
|
expect(helper.note_url(note)).to eq("/#{project.full_path}/notes/#{note.id}")
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'return project notes path for other noteables' do
|
it 'return project notes path for other noteables' do
|
||||||
namespace = create(:namespace, path: 'nm')
|
@project = project
|
||||||
@project = create(:project, path: 'test', namespace: namespace)
|
|
||||||
note = create(:note_on_issue, project: @project)
|
note = create(:note_on_issue, project: @project)
|
||||||
|
|
||||||
expect(helper.note_url(note)).to eq("/nm/test/notes/#{note.id}")
|
expect(helper.note_url(note)).to eq("/#{project.full_path}/notes/#{note.id}")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -279,8 +275,7 @@ RSpec.describe NotesHelper do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns namespace, project and note for project snippet' do
|
it 'returns namespace, project and note for project snippet' do
|
||||||
namespace = create(:namespace, path: 'nm')
|
@project = project
|
||||||
@project = create(:project, path: 'test', namespace: namespace)
|
|
||||||
@snippet = create(:project_snippet, project: @project)
|
@snippet = create(:project_snippet, project: @project)
|
||||||
@note = create(:note_on_personal_snippet)
|
@note = create(:note_on_personal_snippet)
|
||||||
|
|
||||||
|
@ -288,8 +283,7 @@ RSpec.describe NotesHelper do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns namespace, project and note path for other noteables' do
|
it 'returns namespace, project and note path for other noteables' do
|
||||||
namespace = create(:namespace, path: 'nm')
|
@project = project
|
||||||
@project = create(:project, path: 'test', namespace: namespace)
|
|
||||||
@note = create(:note_on_issue, project: @project)
|
@note = create(:note_on_issue, project: @project)
|
||||||
|
|
||||||
expect(helper.form_resources).to eq([@project, @note])
|
expect(helper.form_resources).to eq([@project, @note])
|
||||||
|
@ -297,7 +291,6 @@ RSpec.describe NotesHelper do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#noteable_note_url' do
|
describe '#noteable_note_url' do
|
||||||
let(:project) { create(:project) }
|
|
||||||
let(:issue) { create(:issue, project: project) }
|
let(:issue) { create(:issue, project: project) }
|
||||||
let(:note) { create(:note_on_issue, noteable: issue, project: project) }
|
let(:note) { create(:note_on_issue, noteable: issue, project: project) }
|
||||||
|
|
||||||
|
|
26
spec/lib/gitlab/graphql/pagination/keyset/last_items_spec.rb
Normal file
26
spec/lib/gitlab/graphql/pagination/keyset/last_items_spec.rb
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
RSpec.describe Gitlab::Graphql::Pagination::Keyset::LastItems do
|
||||||
|
let_it_be(:merge_request) { create(:merge_request) }
|
||||||
|
let(:scope) { MergeRequest.order_merged_at_asc.with_order_id_desc }
|
||||||
|
|
||||||
|
subject { described_class.take_items(*args) }
|
||||||
|
|
||||||
|
context 'when the `count` parameter is nil' do
|
||||||
|
let(:args) { [scope, nil] }
|
||||||
|
|
||||||
|
it 'returns a single record' do
|
||||||
|
expect(subject).to eq(merge_request)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the `count` parameter is given' do
|
||||||
|
let(:args) { [scope, 1] }
|
||||||
|
|
||||||
|
it 'returns an array' do
|
||||||
|
expect(subject).to eq([merge_request])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -13,6 +13,7 @@ RSpec.describe 'getting merge request listings nested in a project' do
|
||||||
let_it_be(:merge_request_b) { create(:merge_request, :closed, :unique_branches, source_project: project) }
|
let_it_be(:merge_request_b) { create(:merge_request, :closed, :unique_branches, source_project: project) }
|
||||||
let_it_be(:merge_request_c) { create(:labeled_merge_request, :closed, :unique_branches, source_project: project, labels: [label]) }
|
let_it_be(:merge_request_c) { create(:labeled_merge_request, :closed, :unique_branches, source_project: project, labels: [label]) }
|
||||||
let_it_be(:merge_request_d) { create(:merge_request, :locked, :unique_branches, source_project: project) }
|
let_it_be(:merge_request_d) { create(:merge_request, :locked, :unique_branches, source_project: project) }
|
||||||
|
let_it_be(:merge_request_e) { create(:merge_request, :unique_branches, source_project: project) }
|
||||||
|
|
||||||
let(:results) { graphql_data.dig('project', 'mergeRequests', 'nodes') }
|
let(:results) { graphql_data.dig('project', 'mergeRequests', 'nodes') }
|
||||||
|
|
||||||
|
@ -118,7 +119,7 @@ RSpec.describe 'getting merge request listings nested in a project' do
|
||||||
|
|
||||||
context 'there are no search params' do
|
context 'there are no search params' do
|
||||||
let(:search_params) { nil }
|
let(:search_params) { nil }
|
||||||
let(:mrs) { [merge_request_a, merge_request_b, merge_request_c, merge_request_d] }
|
let(:mrs) { [merge_request_a, merge_request_b, merge_request_c, merge_request_d, merge_request_e] }
|
||||||
|
|
||||||
it_behaves_like 'searching with parameters'
|
it_behaves_like 'searching with parameters'
|
||||||
end
|
end
|
||||||
|
@ -241,16 +242,50 @@ RSpec.describe 'getting merge request listings nested in a project' do
|
||||||
let(:expected_results) do
|
let(:expected_results) do
|
||||||
[
|
[
|
||||||
merge_request_b,
|
merge_request_b,
|
||||||
merge_request_c,
|
|
||||||
merge_request_d,
|
merge_request_d,
|
||||||
|
merge_request_c,
|
||||||
|
merge_request_e,
|
||||||
merge_request_a
|
merge_request_a
|
||||||
].map(&:to_gid).map(&:to_s)
|
].map(&:to_gid).map(&:to_s)
|
||||||
end
|
end
|
||||||
|
|
||||||
before do
|
before do
|
||||||
merge_request_c.metrics.update!(merged_at: 5.days.ago)
|
five_days_ago = 5.days.ago
|
||||||
|
|
||||||
|
merge_request_d.metrics.update!(merged_at: five_days_ago)
|
||||||
|
|
||||||
|
# same merged_at, the second order column will decide (merge_request.id)
|
||||||
|
merge_request_c.metrics.update!(merged_at: five_days_ago)
|
||||||
|
|
||||||
merge_request_b.metrics.update!(merged_at: 1.day.ago)
|
merge_request_b.metrics.update!(merged_at: 1.day.ago)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when paginating backwards' do
|
||||||
|
let(:params) { 'first: 2, sort: MERGED_AT_DESC' }
|
||||||
|
let(:page_info) { 'pageInfo { startCursor endCursor }' }
|
||||||
|
|
||||||
|
before do
|
||||||
|
post_graphql(pagination_query(params, page_info), current_user: current_user)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'paginates backwards correctly' do
|
||||||
|
# first page
|
||||||
|
first_page_response_data = graphql_dig_at(Gitlab::Json.parse(response.body), :data, *data_path, :edges)
|
||||||
|
end_cursor = graphql_dig_at(Gitlab::Json.parse(response.body), :data, :project, :mergeRequests, :pageInfo, :endCursor)
|
||||||
|
|
||||||
|
# second page
|
||||||
|
params = "first: 2, after: \"#{end_cursor}\", sort: MERGED_AT_DESC"
|
||||||
|
post_graphql(pagination_query(params, page_info), current_user: current_user)
|
||||||
|
start_cursor = graphql_dig_at(Gitlab::Json.parse(response.body), :data, :project, :mergeRequests, :pageInfo, :start_cursor)
|
||||||
|
|
||||||
|
# going back to the first page
|
||||||
|
|
||||||
|
params = "last: 2, before: \"#{start_cursor}\", sort: MERGED_AT_DESC"
|
||||||
|
post_graphql(pagination_query(params, page_info), current_user: current_user)
|
||||||
|
backward_paginated_response_data = graphql_dig_at(Gitlab::Json.parse(response.body), :data, *data_path, :edges)
|
||||||
|
expect(first_page_response_data).to eq(backward_paginated_response_data)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,25 +3,32 @@
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
RSpec.describe Ci::RetryBuildService do
|
RSpec.describe Ci::RetryBuildService do
|
||||||
let_it_be(:user) { create(:user) }
|
let_it_be(:reporter) { create(:user) }
|
||||||
|
let_it_be(:developer) { create(:user) }
|
||||||
let_it_be(:project) { create(:project, :repository) }
|
let_it_be(:project) { create(:project, :repository) }
|
||||||
let_it_be(:pipeline) do
|
let_it_be(:pipeline) do
|
||||||
create(:ci_pipeline, project: project,
|
create(:ci_pipeline, project: project,
|
||||||
sha: 'b83d6e391c22777fca1ed3012fce84f633d7fed0')
|
sha: 'b83d6e391c22777fca1ed3012fce84f633d7fed0')
|
||||||
end
|
end
|
||||||
|
|
||||||
let(:stage) do
|
let_it_be(:stage) do
|
||||||
create(:ci_stage_entity, project: project,
|
create(:ci_stage_entity, project: project,
|
||||||
pipeline: pipeline,
|
pipeline: pipeline,
|
||||||
name: 'test')
|
name: 'test')
|
||||||
end
|
end
|
||||||
|
|
||||||
let(:build) { create(:ci_build, pipeline: pipeline, stage_id: stage.id) }
|
let_it_be_with_refind(:build) { create(:ci_build, pipeline: pipeline, stage_id: stage.id) }
|
||||||
|
let(:user) { developer }
|
||||||
|
|
||||||
let(:service) do
|
let(:service) do
|
||||||
described_class.new(project, user)
|
described_class.new(project, user)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
before_all do
|
||||||
|
project.add_developer(developer)
|
||||||
|
project.add_reporter(reporter)
|
||||||
|
end
|
||||||
|
|
||||||
clone_accessors = described_class.clone_accessors
|
clone_accessors = described_class.clone_accessors
|
||||||
|
|
||||||
reject_accessors =
|
reject_accessors =
|
||||||
|
@ -53,9 +60,9 @@ RSpec.describe Ci::RetryBuildService do
|
||||||
pipeline_id report_results pending_state pages_deployments].freeze
|
pipeline_id report_results pending_state pages_deployments].freeze
|
||||||
|
|
||||||
shared_examples 'build duplication' do
|
shared_examples 'build duplication' do
|
||||||
let(:another_pipeline) { create(:ci_empty_pipeline, project: project) }
|
let_it_be(:another_pipeline) { create(:ci_empty_pipeline, project: project) }
|
||||||
|
|
||||||
let(:build) do
|
let_it_be(:build) do
|
||||||
create(:ci_build, :failed, :expired, :erased, :queued, :coverage, :tags,
|
create(:ci_build, :failed, :expired, :erased, :queued, :coverage, :tags,
|
||||||
:allowed_to_fail, :on_tag, :triggered, :teardown_environment, :resource_group,
|
:allowed_to_fail, :on_tag, :triggered, :teardown_environment, :resource_group,
|
||||||
description: 'my-job', stage: 'test', stage_id: stage.id,
|
description: 'my-job', stage: 'test', stage_id: stage.id,
|
||||||
|
@ -63,7 +70,7 @@ RSpec.describe Ci::RetryBuildService do
|
||||||
scheduled_at: 10.seconds.since)
|
scheduled_at: 10.seconds.since)
|
||||||
end
|
end
|
||||||
|
|
||||||
before do
|
before_all do
|
||||||
# Test correctly behaviour of deprecated artifact because it can be still in use
|
# Test correctly behaviour of deprecated artifact because it can be still in use
|
||||||
stub_feature_flags(drop_license_management_artifact: false)
|
stub_feature_flags(drop_license_management_artifact: false)
|
||||||
|
|
||||||
|
@ -81,8 +88,6 @@ RSpec.describe Ci::RetryBuildService do
|
||||||
|
|
||||||
create(:ci_job_variable, job: build)
|
create(:ci_job_variable, job: build)
|
||||||
create(:ci_build_need, build: build)
|
create(:ci_build_need, build: build)
|
||||||
|
|
||||||
build.reload
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'clone accessors' do
|
describe 'clone accessors' do
|
||||||
|
@ -162,8 +167,6 @@ RSpec.describe Ci::RetryBuildService do
|
||||||
context 'when user has ability to execute build' do
|
context 'when user has ability to execute build' do
|
||||||
before do
|
before do
|
||||||
stub_not_protect_default_branch
|
stub_not_protect_default_branch
|
||||||
|
|
||||||
project.add_developer(user)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it_behaves_like 'build duplication'
|
it_behaves_like 'build duplication'
|
||||||
|
@ -235,7 +238,6 @@ RSpec.describe Ci::RetryBuildService do
|
||||||
|
|
||||||
context 'when the pipeline is a child pipeline and the bridge is depended' do
|
context 'when the pipeline is a child pipeline and the bridge is depended' do
|
||||||
let!(:parent_pipeline) { create(:ci_pipeline, project: project) }
|
let!(:parent_pipeline) { create(:ci_pipeline, project: project) }
|
||||||
let!(:pipeline) { create(:ci_pipeline, project: project) }
|
|
||||||
let!(:bridge) { create(:ci_bridge, :strategy_depend, pipeline: parent_pipeline, status: 'success') }
|
let!(:bridge) { create(:ci_bridge, :strategy_depend, pipeline: parent_pipeline, status: 'success') }
|
||||||
let!(:source_pipeline) { create(:ci_sources_pipeline, pipeline: pipeline, source_job: bridge) }
|
let!(:source_pipeline) { create(:ci_sources_pipeline, pipeline: pipeline, source_job: bridge) }
|
||||||
|
|
||||||
|
@ -248,6 +250,8 @@ RSpec.describe Ci::RetryBuildService do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when user does not have ability to execute build' do
|
context 'when user does not have ability to execute build' do
|
||||||
|
let(:user) { reporter }
|
||||||
|
|
||||||
it 'raises an error' do
|
it 'raises an error' do
|
||||||
expect { service.execute(build) }
|
expect { service.execute(build) }
|
||||||
.to raise_error Gitlab::Access::AccessDeniedError
|
.to raise_error Gitlab::Access::AccessDeniedError
|
||||||
|
@ -265,8 +269,6 @@ RSpec.describe Ci::RetryBuildService do
|
||||||
context 'when user has ability to execute build' do
|
context 'when user has ability to execute build' do
|
||||||
before do
|
before do
|
||||||
stub_not_protect_default_branch
|
stub_not_protect_default_branch
|
||||||
|
|
||||||
project.add_developer(user)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it_behaves_like 'build duplication'
|
it_behaves_like 'build duplication'
|
||||||
|
@ -316,6 +318,8 @@ RSpec.describe Ci::RetryBuildService do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when user does not have ability to execute build' do
|
context 'when user does not have ability to execute build' do
|
||||||
|
let(:user) { reporter }
|
||||||
|
|
||||||
it 'raises an error' do
|
it 'raises an error' do
|
||||||
expect { service.reprocess!(build) }
|
expect { service.reprocess!(build) }
|
||||||
.to raise_error Gitlab::Access::AccessDeniedError
|
.to raise_error Gitlab::Access::AccessDeniedError
|
||||||
|
|
|
@ -138,7 +138,7 @@ RSpec.describe Projects::AutocompleteService do
|
||||||
def expect_labels_to_equal(labels, expected_labels)
|
def expect_labels_to_equal(labels, expected_labels)
|
||||||
expect(labels.size).to eq(expected_labels.size)
|
expect(labels.size).to eq(expected_labels.size)
|
||||||
extract_title = lambda { |label| label['title'] }
|
extract_title = lambda { |label| label['title'] }
|
||||||
expect(labels.map(&extract_title)).to eq(expected_labels.map(&extract_title))
|
expect(labels.map(&extract_title)).to match_array(expected_labels.map(&extract_title))
|
||||||
end
|
end
|
||||||
|
|
||||||
let(:user) { create(:user) }
|
let(:user) { create(:user) }
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module DragTo
|
module DragTo
|
||||||
def drag_to(list_from_index: 0, from_index: 0, to_index: 0, list_to_index: 0, selector: '', scrollable: 'body', duration: 1000, perform_drop: true)
|
# rubocop:disable Metrics/ParameterLists
|
||||||
|
def drag_to(list_from_index: 0, from_index: 0, to_index: 0, list_to_index: 0, selector: '', scrollable: 'body', duration: 1000, perform_drop: true, extra_height: 0)
|
||||||
js = <<~JS
|
js = <<~JS
|
||||||
simulateDrag({
|
simulateDrag({
|
||||||
scrollable: document.querySelector('#{scrollable}'),
|
scrollable: document.querySelector('#{scrollable}'),
|
||||||
|
@ -14,7 +15,8 @@ module DragTo
|
||||||
el: document.querySelectorAll('#{selector}')[#{list_to_index}],
|
el: document.querySelectorAll('#{selector}')[#{list_to_index}],
|
||||||
index: #{to_index}
|
index: #{to_index}
|
||||||
},
|
},
|
||||||
performDrop: #{perform_drop}
|
performDrop: #{perform_drop},
|
||||||
|
extraHeight: #{extra_height}
|
||||||
});
|
});
|
||||||
JS
|
JS
|
||||||
evaluate_script(js)
|
evaluate_script(js)
|
||||||
|
@ -23,6 +25,7 @@ module DragTo
|
||||||
loop while drag_active?
|
loop while drag_active?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
# rubocop:enable Metrics/ParameterLists
|
||||||
|
|
||||||
def drag_active?
|
def drag_active?
|
||||||
page.evaluate_script('window.SIMULATE_DRAG_ACTIVE').nonzero?
|
page.evaluate_script('window.SIMULATE_DRAG_ACTIVE').nonzero?
|
||||||
|
|
|
@ -29,8 +29,8 @@ module StubbedFeature
|
||||||
end
|
end
|
||||||
|
|
||||||
# Replace #enabled? method with the optional stubbed/unstubbed version.
|
# Replace #enabled? method with the optional stubbed/unstubbed version.
|
||||||
def enabled?(*args)
|
def enabled?(*args, **kwargs)
|
||||||
feature_flag = super(*args)
|
feature_flag = super
|
||||||
return feature_flag unless stub?
|
return feature_flag unless stub?
|
||||||
|
|
||||||
# If feature flag is not persisted we mark the feature flag as enabled
|
# If feature flag is not persisted we mark the feature flag as enabled
|
||||||
|
|
Loading…
Reference in a new issue