Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
72241c5e0a
commit
f1500a385a
|
@ -804,7 +804,6 @@ Graphql/Descriptions:
|
|||
- 'ee/app/graphql/types/epic_health_status_type.rb'
|
||||
- 'ee/app/graphql/types/epic_issue_type.rb'
|
||||
- 'ee/app/graphql/types/epic_tree/epic_tree_node_input_type.rb'
|
||||
- 'ee/app/graphql/types/epic_type.rb'
|
||||
- 'ee/app/graphql/types/external_issue_type.rb'
|
||||
- 'ee/app/graphql/types/geo/geo_node_type.rb'
|
||||
- 'ee/app/graphql/types/geo/merge_request_diff_registry_type.rb'
|
||||
|
|
|
@ -1 +1 @@
|
|||
2c7c204731f6e4f1c8cdb3d8a705caf7acf6689d
|
||||
c73d7cae656b0bedfa40a4865c8d886516eda78b
|
||||
|
|
|
@ -67,13 +67,23 @@ export const publishReview = ({ commit, dispatch, getters }) => {
|
|||
.catch(() => commit(types.RECEIVE_PUBLISH_REVIEW_ERROR));
|
||||
};
|
||||
|
||||
export const updateDiscussionsAfterPublish = ({ dispatch, getters, rootGetters }) =>
|
||||
dispatch('fetchDiscussions', { path: getters.getNotesData.discussionsPath }, { root: true }).then(
|
||||
() =>
|
||||
dispatch('diffs/assignDiscussionsToDiff', rootGetters.discussionsStructuredByLineCode, {
|
||||
root: true,
|
||||
}),
|
||||
);
|
||||
export const updateDiscussionsAfterPublish = async ({ dispatch, getters, rootGetters }) => {
|
||||
if (window.gon?.features?.paginatedNotes) {
|
||||
await dispatch('stopPolling', null, { root: true });
|
||||
await dispatch('fetchData', null, { root: true });
|
||||
await dispatch('restartPolling', null, { root: true });
|
||||
} else {
|
||||
await dispatch(
|
||||
'fetchDiscussions',
|
||||
{ path: getters.getNotesData.discussionsPath },
|
||||
{ root: true },
|
||||
);
|
||||
}
|
||||
|
||||
dispatch('diffs/assignDiscussionsToDiff', rootGetters.discussionsStructuredByLineCode, {
|
||||
root: true,
|
||||
});
|
||||
};
|
||||
|
||||
export const updateDraft = (
|
||||
{ commit, getters },
|
||||
|
|
|
@ -4,6 +4,7 @@ import OrderedLayout from '~/vue_shared/components/ordered_layout.vue';
|
|||
import highlightCurrentUser from '~/behaviors/markdown/highlight_current_user';
|
||||
import { __ } from '~/locale';
|
||||
import initUserPopovers from '~/user_popovers';
|
||||
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
import { getLocationHash, doesHashExistInUrl } from '../../lib/utils/url_utility';
|
||||
import { deprecatedCreateFlash as Flash } from '../../flash';
|
||||
import * as constants from '../constants';
|
||||
|
@ -30,6 +31,7 @@ export default {
|
|||
discussionFilterNote,
|
||||
OrderedLayout,
|
||||
},
|
||||
mixins: [glFeatureFlagsMixin()],
|
||||
props: {
|
||||
noteableData: {
|
||||
type: Object,
|
||||
|
@ -57,7 +59,6 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
isFetching: false,
|
||||
currentFilter: null,
|
||||
};
|
||||
},
|
||||
|
@ -68,6 +69,7 @@ export default {
|
|||
'convertedDisscussionIds',
|
||||
'getNotesDataByProp',
|
||||
'isLoading',
|
||||
'isFetching',
|
||||
'commentsDisabled',
|
||||
'getNoteableData',
|
||||
'userCanReply',
|
||||
|
@ -103,6 +105,13 @@ export default {
|
|||
},
|
||||
},
|
||||
watch: {
|
||||
async isFetching() {
|
||||
if (!this.isFetching) {
|
||||
await this.$nextTick();
|
||||
await this.startTaskList();
|
||||
await this.checkLocationHash();
|
||||
}
|
||||
},
|
||||
shouldShow() {
|
||||
if (!this.isNotesFetched) {
|
||||
this.fetchNotes();
|
||||
|
@ -153,6 +162,7 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
...mapActions([
|
||||
'setFetchingState',
|
||||
'setLoadingState',
|
||||
'fetchDiscussions',
|
||||
'poll',
|
||||
|
@ -183,7 +193,11 @@ export default {
|
|||
fetchNotes() {
|
||||
if (this.isFetching) return null;
|
||||
|
||||
this.isFetching = true;
|
||||
this.setFetchingState(true);
|
||||
|
||||
if (this.glFeatures.paginatedNotes) {
|
||||
return this.initPolling();
|
||||
}
|
||||
|
||||
return this.fetchDiscussions(this.getFetchDiscussionsConfig())
|
||||
.then(this.initPolling)
|
||||
|
@ -191,11 +205,8 @@ export default {
|
|||
this.setLoadingState(false);
|
||||
this.setNotesFetchedState(true);
|
||||
eventHub.$emit('fetchedNotesData');
|
||||
this.isFetching = false;
|
||||
this.setFetchingState(false);
|
||||
})
|
||||
.then(this.$nextTick)
|
||||
.then(this.startTaskList)
|
||||
.then(this.checkLocationHash)
|
||||
.catch(() => {
|
||||
this.setLoadingState(false);
|
||||
this.setNotesFetchedState(true);
|
||||
|
|
|
@ -15,6 +15,7 @@ import loadAwardsHandler from '../../awards_handler';
|
|||
import sidebarTimeTrackingEventHub from '../../sidebar/event_hub';
|
||||
import { isInViewport, scrollToElement, isInMRPage } from '../../lib/utils/common_utils';
|
||||
import { mergeUrlParams } from '../../lib/utils/url_utility';
|
||||
import eventHub from '../event_hub';
|
||||
import mrWidgetEventHub from '../../vue_merge_request_widget/event_hub';
|
||||
import * as utils from './utils';
|
||||
import * as types from './mutation_types';
|
||||
|
@ -420,14 +421,25 @@ export const saveNote = ({ commit, dispatch }, noteData) => {
|
|||
.catch(processErrors);
|
||||
};
|
||||
|
||||
const pollSuccessCallBack = (resp, commit, state, getters, dispatch) => {
|
||||
export const setFetchingState = ({ commit }, fetchingState) =>
|
||||
commit(types.SET_NOTES_FETCHING_STATE, fetchingState);
|
||||
|
||||
const pollSuccessCallBack = async (resp, commit, state, getters, dispatch) => {
|
||||
if (state.isResolvingDiscussion) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (window.gon?.features?.paginatedNotes && !resp.more && state.isFetching) {
|
||||
eventHub.$emit('fetchedNotesData');
|
||||
dispatch('setFetchingState', false);
|
||||
dispatch('setNotesFetchedState', true);
|
||||
dispatch('setLoadingState', false);
|
||||
}
|
||||
|
||||
if (resp.notes?.length) {
|
||||
dispatch('updateOrCreateNotes', resp.notes);
|
||||
await dispatch('updateOrCreateNotes', resp.notes);
|
||||
dispatch('startTaskList');
|
||||
dispatch('updateResolvableDiscussionsCounts');
|
||||
}
|
||||
|
||||
commit(types.SET_LAST_FETCHED_AT, resp.last_fetched_at);
|
||||
|
|
|
@ -48,6 +48,8 @@ export const persistSortOrder = (state) => state.persistSortOrder;
|
|||
|
||||
export const timelineEnabled = (state) => state.isTimelineEnabled;
|
||||
|
||||
export const isFetching = (state) => state.isFetching;
|
||||
|
||||
export const isLoading = (state) => state.isLoading;
|
||||
|
||||
export const getNotesDataByProp = (state) => (prop) => state.notesData[prop];
|
||||
|
|
|
@ -47,6 +47,7 @@ export default () => ({
|
|||
unresolvedDiscussionsCount: 0,
|
||||
descriptionVersions: {},
|
||||
isTimelineEnabled: false,
|
||||
isFetching: false,
|
||||
},
|
||||
actions,
|
||||
getters,
|
||||
|
|
|
@ -14,6 +14,7 @@ export const UPDATE_NOTE = 'UPDATE_NOTE';
|
|||
export const UPDATE_DISCUSSION = 'UPDATE_DISCUSSION';
|
||||
export const UPDATE_DISCUSSION_POSITION = 'UPDATE_DISCUSSION_POSITION';
|
||||
export const SET_DISCUSSION_DIFF_LINES = 'SET_DISCUSSION_DIFF_LINES';
|
||||
export const SET_NOTES_FETCHING_STATE = 'SET_NOTES_FETCHING_STATE';
|
||||
export const SET_NOTES_FETCHED_STATE = 'SET_NOTES_FETCHED_STATE';
|
||||
export const SET_NOTES_LOADING_STATE = 'SET_NOTES_LOADING_STATE';
|
||||
export const DISABLE_COMMENTS = 'DISABLE_COMMENTS';
|
||||
|
|
|
@ -32,6 +32,20 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
if (window.gon?.features?.paginatedNotes && note.base_discussion) {
|
||||
if (discussion.diff_file) {
|
||||
discussion.file_hash = discussion.diff_file.file_hash;
|
||||
|
||||
discussion.truncated_diff_lines = utils.prepareDiffLines(
|
||||
discussion.truncated_diff_lines || [],
|
||||
);
|
||||
}
|
||||
|
||||
discussion.resolvable = note.resolvable;
|
||||
discussion.expanded = note.base_discussion.expanded;
|
||||
discussion.resolved = note.resolved;
|
||||
}
|
||||
|
||||
// note.base_discussion = undefined; // No point keeping a reference to this
|
||||
delete note.base_discussion;
|
||||
discussion.notes = [note];
|
||||
|
@ -323,6 +337,10 @@ export default {
|
|||
state.isLoading = value;
|
||||
},
|
||||
|
||||
[types.SET_NOTES_FETCHING_STATE](state, value) {
|
||||
state.isFetching = value;
|
||||
},
|
||||
|
||||
[types.SET_DISCUSSION_DIFF_LINES](state, { discussionId, diffLines }) {
|
||||
const discussion = utils.findNoteObjectById(state.discussions, discussionId);
|
||||
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
import initCompareSelector from '~/projects/compare';
|
||||
|
||||
initCompareSelector();
|
|
@ -224,11 +224,11 @@ export default {
|
|||
|
||||
repositoryHelpText() {
|
||||
if (this.visibilityLevel === visibilityOptions.PRIVATE) {
|
||||
return s__('ProjectSettings|View and edit files in this project');
|
||||
return s__('ProjectSettings|View and edit files in this project.');
|
||||
}
|
||||
|
||||
return s__(
|
||||
'ProjectSettings|View and edit files in this project. Non-project members will only have read access',
|
||||
'ProjectSettings|View and edit files in this project. Non-project members will only have read access.',
|
||||
);
|
||||
},
|
||||
},
|
||||
|
@ -400,7 +400,7 @@ export default {
|
|||
name="project[request_access_enabled]"
|
||||
/>
|
||||
<input v-model="requestAccessEnabled" type="checkbox" />
|
||||
{{ s__('ProjectSettings|Allow users to request access') }}
|
||||
{{ s__('ProjectSettings|Users can request access') }}
|
||||
</label>
|
||||
</project-setting-row>
|
||||
</div>
|
||||
|
@ -411,7 +411,7 @@ export default {
|
|||
<project-setting-row
|
||||
ref="issues-settings"
|
||||
:label="s__('ProjectSettings|Issues')"
|
||||
:help-text="s__('ProjectSettings|Lightweight issue tracking system for this project')"
|
||||
:help-text="s__('ProjectSettings|Lightweight issue tracking system.')"
|
||||
>
|
||||
<project-feature-setting
|
||||
v-model="issuesAccessLevel"
|
||||
|
@ -434,7 +434,7 @@ export default {
|
|||
<project-setting-row
|
||||
ref="merge-request-settings"
|
||||
:label="s__('ProjectSettings|Merge requests')"
|
||||
:help-text="s__('ProjectSettings|Submit changes to be merged upstream')"
|
||||
:help-text="s__('ProjectSettings|Submit changes to be merged upstream.')"
|
||||
>
|
||||
<project-feature-setting
|
||||
v-model="mergeRequestsAccessLevel"
|
||||
|
@ -446,9 +446,7 @@ export default {
|
|||
<project-setting-row
|
||||
ref="fork-settings"
|
||||
:label="s__('ProjectSettings|Forks')"
|
||||
:help-text="
|
||||
s__('ProjectSettings|Allow users to make copies of your repository to a new project')
|
||||
"
|
||||
:help-text="s__('ProjectSettings|Users can copy the repository to a new project.')"
|
||||
>
|
||||
<project-feature-setting
|
||||
v-model="forkingAccessLevel"
|
||||
|
@ -460,7 +458,7 @@ export default {
|
|||
<project-setting-row
|
||||
ref="pipeline-settings"
|
||||
:label="s__('ProjectSettings|Pipelines')"
|
||||
:help-text="s__('ProjectSettings|Build, test, and deploy your changes')"
|
||||
:help-text="s__('ProjectSettings|Build, test, and deploy your changes.')"
|
||||
>
|
||||
<project-feature-setting
|
||||
v-model="buildsAccessLevel"
|
||||
|
@ -497,7 +495,7 @@ export default {
|
|||
:help-path="lfsHelpPath"
|
||||
:label="s__('ProjectSettings|Git Large File Storage (LFS)')"
|
||||
:help-text="
|
||||
s__('ProjectSettings|Manages large files such as audio, video, and graphics files')
|
||||
s__('ProjectSettings|Manages large files such as audio, video, and graphics files.')
|
||||
"
|
||||
>
|
||||
<project-feature-toggle
|
||||
|
@ -509,7 +507,7 @@ export default {
|
|||
<gl-sprintf
|
||||
:message="
|
||||
s__(
|
||||
'ProjectSettings|LFS objects from this repository are still available to forks. %{linkStart}How do I remove them?%{linkEnd}',
|
||||
'ProjectSettings|LFS objects from this repository are available to forks. %{linkStart}How do I remove them?%{linkEnd}',
|
||||
)
|
||||
"
|
||||
>
|
||||
|
@ -529,7 +527,7 @@ export default {
|
|||
:help-path="packagesHelpPath"
|
||||
:label="s__('ProjectSettings|Packages')"
|
||||
:help-text="
|
||||
s__('ProjectSettings|Every project can have its own space to store its packages')
|
||||
s__('ProjectSettings|Every project can have its own space to store its packages.')
|
||||
"
|
||||
>
|
||||
<project-feature-toggle
|
||||
|
@ -542,7 +540,7 @@ export default {
|
|||
<project-setting-row
|
||||
ref="analytics-settings"
|
||||
:label="s__('ProjectSettings|Analytics')"
|
||||
:help-text="s__('ProjectSettings|View project analytics')"
|
||||
:help-text="s__('ProjectSettings|View project analytics.')"
|
||||
>
|
||||
<project-feature-setting
|
||||
v-model="analyticsAccessLevel"
|
||||
|
@ -554,7 +552,7 @@ export default {
|
|||
v-if="requirementsAvailable"
|
||||
ref="requirements-settings"
|
||||
:label="s__('ProjectSettings|Requirements')"
|
||||
:help-text="s__('ProjectSettings|Requirements management system for this project')"
|
||||
:help-text="s__('ProjectSettings|Requirements management system.')"
|
||||
>
|
||||
<project-feature-setting
|
||||
v-model="requirementsAccessLevel"
|
||||
|
@ -576,7 +574,7 @@ export default {
|
|||
<project-setting-row
|
||||
ref="wiki-settings"
|
||||
:label="s__('ProjectSettings|Wiki')"
|
||||
:help-text="s__('ProjectSettings|Pages for project documentation')"
|
||||
:help-text="s__('ProjectSettings|Pages for project documentation.')"
|
||||
>
|
||||
<project-feature-setting
|
||||
v-model="wikiAccessLevel"
|
||||
|
@ -587,7 +585,7 @@ export default {
|
|||
<project-setting-row
|
||||
ref="snippet-settings"
|
||||
:label="s__('ProjectSettings|Snippets')"
|
||||
:help-text="s__('ProjectSettings|Share code pastes with others out of Git repository')"
|
||||
:help-text="s__('ProjectSettings|Share code with others outside the project.')"
|
||||
>
|
||||
<project-feature-setting
|
||||
v-model="snippetsAccessLevel"
|
||||
|
@ -601,7 +599,7 @@ export default {
|
|||
:help-path="pagesHelpPath"
|
||||
:label="s__('ProjectSettings|Pages')"
|
||||
:help-text="
|
||||
s__('ProjectSettings|With GitLab Pages you can host your static websites on GitLab')
|
||||
s__('ProjectSettings|With GitLab Pages you can host your static websites on GitLab.')
|
||||
"
|
||||
>
|
||||
<project-feature-setting
|
||||
|
@ -613,7 +611,7 @@ export default {
|
|||
<project-setting-row
|
||||
ref="operations-settings"
|
||||
:label="s__('ProjectSettings|Operations')"
|
||||
:help-text="s__('ProjectSettings|Environments, logs, cluster management, and more')"
|
||||
:help-text="s__('ProjectSettings|Environments, logs, cluster management, and more.')"
|
||||
>
|
||||
<project-feature-setting
|
||||
v-model="operationsAccessLevel"
|
||||
|
@ -625,11 +623,7 @@ export default {
|
|||
<project-setting-row
|
||||
ref="metrics-visibility-settings"
|
||||
:label="__('Metrics Dashboard')"
|
||||
:help-text="
|
||||
s__(
|
||||
'ProjectSettings|With Metrics Dashboard you can visualize this project performance metrics',
|
||||
)
|
||||
"
|
||||
:help-text="s__('ProjectSettings|Visualize the project\'s performance metrics.')"
|
||||
>
|
||||
<project-feature-setting
|
||||
v-model="metricsDashboardAccessLevel"
|
||||
|
@ -647,9 +641,7 @@ export default {
|
|||
{{ s__('ProjectSettings|Disable email notifications') }}
|
||||
</label>
|
||||
<span class="form-text text-muted">{{
|
||||
s__(
|
||||
'ProjectSettings|This setting will override user notification preferences for all project members.',
|
||||
)
|
||||
s__('ProjectSettings|Override user notification preferences for all project members.')
|
||||
}}</span>
|
||||
</project-setting-row>
|
||||
<project-setting-row class="mb-3">
|
||||
|
@ -665,7 +657,7 @@ export default {
|
|||
{{ s__('ProjectSettings|Show default award emojis') }}
|
||||
<template #help>{{
|
||||
s__(
|
||||
'ProjectSettings|When enabled, issues, merge requests, and snippets will always show thumbs-up and thumbs-down award emoji buttons.',
|
||||
'ProjectSettings|Always show thumbs-up and thumbs-down award emoji buttons on issues, merge requests, and snippets.',
|
||||
)
|
||||
}}</template>
|
||||
</gl-form-checkbox>
|
||||
|
@ -683,9 +675,7 @@ export default {
|
|||
<gl-form-checkbox v-model="allowEditingCommitMessages">
|
||||
{{ s__('ProjectSettings|Allow editing commit messages') }}
|
||||
<template #help>{{
|
||||
s__(
|
||||
'ProjectSettings|When enabled, commit authors will be able to edit commit messages on unprotected branches.',
|
||||
)
|
||||
s__('ProjectSettings|Commit authors can edit commit messages on unprotected branches.')
|
||||
}}</template>
|
||||
</gl-form-checkbox>
|
||||
</project-setting-row>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import { GlTooltipDirective, GlButton, GlLoadingIcon, GlIcon } from '@gitlab/ui';
|
||||
import { GlDropdown, GlDropdownItem, GlIcon, GlTooltipDirective } from '@gitlab/ui';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import { deprecatedCreateFlash as flash } from '~/flash';
|
||||
import createFlash from '~/flash';
|
||||
import { s__, __, sprintf } from '~/locale';
|
||||
import GlCountdown from '~/vue_shared/components/gl_countdown.vue';
|
||||
import eventHub from '../../event_hub';
|
||||
|
@ -11,10 +11,10 @@ export default {
|
|||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
components: {
|
||||
GlIcon,
|
||||
GlCountdown,
|
||||
GlButton,
|
||||
GlLoadingIcon,
|
||||
GlDropdown,
|
||||
GlDropdownItem,
|
||||
GlIcon,
|
||||
},
|
||||
props: {
|
||||
actions: {
|
||||
|
@ -61,7 +61,7 @@ export default {
|
|||
})
|
||||
.catch(() => {
|
||||
this.isLoading = false;
|
||||
flash(__('An error occurred while making the request.'));
|
||||
createFlash({ message: __('An error occurred while making the request.') });
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -76,39 +76,27 @@ export default {
|
|||
};
|
||||
</script>
|
||||
<template>
|
||||
<div class="btn-group">
|
||||
<button
|
||||
v-gl-tooltip
|
||||
type="button"
|
||||
:disabled="isLoading"
|
||||
class="dropdown-new gl-button btn btn-default js-pipeline-dropdown-manual-actions"
|
||||
:title="__('Run manual or delayed jobs')"
|
||||
data-toggle="dropdown"
|
||||
:aria-label="__('Run manual or delayed jobs')"
|
||||
<gl-dropdown
|
||||
v-gl-tooltip
|
||||
:title="__('Run manual or delayed jobs')"
|
||||
:loading="isLoading"
|
||||
data-testid="pipelines-manual-actions-dropdown"
|
||||
right
|
||||
icon="play"
|
||||
>
|
||||
<gl-dropdown-item
|
||||
v-for="action in actions"
|
||||
:key="action.path"
|
||||
:disabled="isActionDisabled(action)"
|
||||
@click="onClickAction(action)"
|
||||
>
|
||||
<gl-icon name="play" class="icon-play" />
|
||||
<gl-icon name="chevron-down" />
|
||||
<gl-loading-icon v-if="isLoading" />
|
||||
</button>
|
||||
|
||||
<ul class="dropdown-menu dropdown-menu-right">
|
||||
<li v-for="action in actions" :key="action.path">
|
||||
<gl-button
|
||||
category="tertiary"
|
||||
:class="{ disabled: isActionDisabled(action) }"
|
||||
:disabled="isActionDisabled(action)"
|
||||
class="js-pipeline-action-link"
|
||||
@click="onClickAction(action)"
|
||||
>
|
||||
<div class="d-flex justify-content-between flex-wrap">
|
||||
{{ action.name }}
|
||||
<span v-if="action.scheduled_at">
|
||||
<gl-icon name="clock" />
|
||||
<gl-countdown :end-date-string="action.scheduled_at" />
|
||||
</span>
|
||||
</div>
|
||||
</gl-button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="gl-display-flex gl-justify-content-space-between gl-flex-wrap">
|
||||
{{ action.name }}
|
||||
<span v-if="action.scheduled_at">
|
||||
<gl-icon name="clock" />
|
||||
<gl-countdown :end-date-string="action.scheduled_at" />
|
||||
</span>
|
||||
</div>
|
||||
</gl-dropdown-item>
|
||||
</gl-dropdown>
|
||||
</template>
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
<script>
|
||||
import { GlButton } from '@gitlab/ui';
|
||||
import csrf from '~/lib/utils/csrf';
|
||||
import RevisionDropdown from './revision_dropdown.vue';
|
||||
|
||||
export default {
|
||||
csrf,
|
||||
components: {
|
||||
RevisionDropdown,
|
||||
GlButton,
|
||||
},
|
||||
props: {
|
||||
projectCompareIndexPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
refsProjectPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
paramsFrom: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
paramsTo: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
projectMergeRequestPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
createMrPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onSubmit() {
|
||||
this.$refs.form.submit();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form
|
||||
ref="form"
|
||||
class="form-inline js-requires-input js-signature-container"
|
||||
method="POST"
|
||||
:action="projectCompareIndexPath"
|
||||
>
|
||||
<input :value="$options.csrf.token" type="hidden" name="authenticity_token" />
|
||||
<revision-dropdown
|
||||
:refs-project-path="refsProjectPath"
|
||||
revision-text="Source"
|
||||
params-name="to"
|
||||
:params-branch="paramsTo"
|
||||
/>
|
||||
<div class="compare-ellipsis gl-display-inline" data-testid="ellipsis">...</div>
|
||||
<revision-dropdown
|
||||
:refs-project-path="refsProjectPath"
|
||||
revision-text="Target"
|
||||
params-name="from"
|
||||
:params-branch="paramsFrom"
|
||||
/>
|
||||
<gl-button category="primary" variant="success" class="gl-ml-3" @click="onSubmit">
|
||||
{{ s__('CompareRevisions|Compare') }}
|
||||
</gl-button>
|
||||
<a
|
||||
v-if="projectMergeRequestPath"
|
||||
:href="projectMergeRequestPath"
|
||||
data-testid="projectMrButton"
|
||||
class="btn btn-default gl-button gl-ml-3"
|
||||
>
|
||||
{{ s__('CompareRevisions|View open merge request') }}
|
||||
</a>
|
||||
<a
|
||||
v-else-if="createMrPath"
|
||||
:href="createMrPath"
|
||||
data-testid="createMrButton"
|
||||
class="btn btn-default gl-button gl-ml-3"
|
||||
>
|
||||
{{ s__('CompareRevisions|Create merge request') }}
|
||||
</a>
|
||||
</form>
|
||||
</template>
|
|
@ -0,0 +1,145 @@
|
|||
<script>
|
||||
import { GlDropdown, GlDropdownItem, GlSearchBoxByType, GlDropdownSectionHeader } from '@gitlab/ui';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import createFlash from '~/flash';
|
||||
import { s__ } from '~/locale';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlDropdown,
|
||||
GlDropdownItem,
|
||||
GlDropdownSectionHeader,
|
||||
GlSearchBoxByType,
|
||||
},
|
||||
props: {
|
||||
refsProjectPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
revisionText: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
paramsName: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
paramsBranch: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
branches: [],
|
||||
tags: [],
|
||||
loading: true,
|
||||
searchTerm: '',
|
||||
selectedRevision: this.getDefaultBranch(),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
filteredBranches() {
|
||||
return this.branches.filter((branch) =>
|
||||
branch.toLowerCase().includes(this.searchTerm.toLowerCase()),
|
||||
);
|
||||
},
|
||||
hasFilteredBranches() {
|
||||
return this.filteredBranches.length;
|
||||
},
|
||||
filteredTags() {
|
||||
return this.tags.filter((tag) => tag.toLowerCase().includes(this.searchTerm.toLowerCase()));
|
||||
},
|
||||
hasFilteredTags() {
|
||||
return this.filteredTags.length;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.fetchBranchesAndTags();
|
||||
},
|
||||
methods: {
|
||||
fetchBranchesAndTags() {
|
||||
const endpoint = this.refsProjectPath;
|
||||
|
||||
return axios
|
||||
.get(endpoint)
|
||||
.then(({ data }) => {
|
||||
this.branches = data.Branches;
|
||||
this.tags = data.Tags;
|
||||
})
|
||||
.catch(() => {
|
||||
createFlash({
|
||||
message: `${s__(
|
||||
'CompareRevisions|There was an error while updating the branch/tag list. Please try again.',
|
||||
)}`,
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
getDefaultBranch() {
|
||||
return this.paramsBranch || s__('CompareRevisions|Select branch/tag');
|
||||
},
|
||||
onClick(revision) {
|
||||
this.selectedRevision = revision;
|
||||
},
|
||||
onSearchEnter() {
|
||||
this.selectedRevision = this.searchTerm;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="form-group compare-form-group" :class="`js-compare-${paramsName}-dropdown`">
|
||||
<div class="input-group inline-input-group">
|
||||
<span class="input-group-prepend">
|
||||
<div class="input-group-text">
|
||||
{{ revisionText }}
|
||||
</div>
|
||||
</span>
|
||||
<input type="hidden" :name="paramsName" :value="selectedRevision" />
|
||||
<gl-dropdown
|
||||
class="gl-flex-grow-1 gl-flex-basis-0 gl-min-w-0 gl-font-monospace"
|
||||
toggle-class="form-control compare-dropdown-toggle js-compare-dropdown gl-min-w-0 gl-rounded-top-left-none! gl-rounded-bottom-left-none!"
|
||||
:text="selectedRevision"
|
||||
header-text="Select Git revision"
|
||||
:loading="loading"
|
||||
>
|
||||
<template #header>
|
||||
<gl-search-box-by-type
|
||||
v-model.trim="searchTerm"
|
||||
:placeholder="s__('CompareRevisions|Filter by Git revision')"
|
||||
@keyup.enter="onSearchEnter"
|
||||
/>
|
||||
</template>
|
||||
<gl-dropdown-section-header v-if="hasFilteredBranches">
|
||||
{{ s__('CompareRevisions|Branches') }}
|
||||
</gl-dropdown-section-header>
|
||||
<gl-dropdown-item
|
||||
v-for="(branch, index) in filteredBranches"
|
||||
:key="`branch${index}`"
|
||||
is-check-item
|
||||
:is-checked="selectedRevision === branch"
|
||||
@click="onClick(branch)"
|
||||
>
|
||||
{{ branch }}
|
||||
</gl-dropdown-item>
|
||||
<gl-dropdown-section-header v-if="hasFilteredTags">
|
||||
{{ s__('CompareRevisions|Tags') }}
|
||||
</gl-dropdown-section-header>
|
||||
<gl-dropdown-item
|
||||
v-for="(tag, index) in filteredTags"
|
||||
:key="`tag${index}`"
|
||||
is-check-item
|
||||
:is-checked="selectedRevision === tag"
|
||||
@click="onClick(tag)"
|
||||
>
|
||||
{{ tag }}
|
||||
</gl-dropdown-item>
|
||||
</gl-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,33 @@
|
|||
import Vue from 'vue';
|
||||
import CompareApp from './components/app.vue';
|
||||
|
||||
export default function init() {
|
||||
const el = document.getElementById('js-compare-selector');
|
||||
const {
|
||||
refsProjectPath,
|
||||
paramsFrom,
|
||||
paramsTo,
|
||||
projectCompareIndexPath,
|
||||
projectMergeRequestPath,
|
||||
createMrPath,
|
||||
} = el.dataset;
|
||||
|
||||
return new Vue({
|
||||
el,
|
||||
components: {
|
||||
CompareApp,
|
||||
},
|
||||
render(createElement) {
|
||||
return createElement(CompareApp, {
|
||||
props: {
|
||||
refsProjectPath,
|
||||
paramsFrom,
|
||||
paramsTo,
|
||||
projectCompareIndexPath,
|
||||
projectMergeRequestPath,
|
||||
createMrPath,
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
|
@ -159,6 +159,7 @@ export default {
|
|||
.then((data) => {
|
||||
this.mr.setApprovals(data);
|
||||
eventHub.$emit('MRWidgetUpdateRequested');
|
||||
eventHub.$emit('ApprovalUpdated');
|
||||
this.$emit('updated');
|
||||
})
|
||||
.catch(errFn)
|
||||
|
|
|
@ -155,13 +155,14 @@ export default {
|
|||
>
|
||||
{{ $options.monitoringPipelineText }}
|
||||
<gl-link
|
||||
v-gl-tooltip
|
||||
:href="ciTroubleshootingDocsPath"
|
||||
target="_blank"
|
||||
:title="__('About this feature')"
|
||||
class="gl-display-flex gl-align-items-center gl-ml-2"
|
||||
>
|
||||
<gl-icon
|
||||
name="question"
|
||||
:size="12"
|
||||
:aria-label="__('Link to go to GitLab pipeline documentation')"
|
||||
/>
|
||||
</gl-link>
|
||||
|
|
|
@ -4,6 +4,7 @@ import autoMergeMixin from 'ee_else_ce/vue_merge_request_widget/mixins/auto_merg
|
|||
import autoMergeEnabledQuery from 'ee_else_ce/vue_merge_request_widget/queries/states/auto_merge_enabled.query.graphql';
|
||||
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
import { __ } from '~/locale';
|
||||
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||
import { deprecatedCreateFlash as Flash } from '../../../flash';
|
||||
import statusIcon from '../mr_widget_status_icon.vue';
|
||||
import MrWidgetAuthor from '../mr_widget_author.vue';
|
||||
|
@ -53,7 +54,11 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
loading() {
|
||||
return this.glFeatures.mergeRequestWidgetGraphql && this.$apollo.queries.state.loading;
|
||||
return (
|
||||
this.glFeatures.mergeRequestWidgetGraphql &&
|
||||
this.$apollo.queries.state.loading &&
|
||||
Object.keys(this.state).length === 0
|
||||
);
|
||||
},
|
||||
mergeUser() {
|
||||
if (this.glFeatures.mergeRequestWidgetGraphql) {
|
||||
|
@ -78,7 +83,7 @@ export default {
|
|||
canRemoveSourceBranch() {
|
||||
const { currentUserId } = this.mr;
|
||||
const mergeUserId = this.glFeatures.mergeRequestWidgetGraphql
|
||||
? this.state.mergeUser?.id
|
||||
? getIdFromGraphQLId(this.state.mergeUser?.id)
|
||||
: this.mr.mergeUserId;
|
||||
const canRemoveSourceBranch = this.glFeatures.mergeRequestWidgetGraphql
|
||||
? this.state.userPermissions.removeSourceBranch
|
||||
|
@ -96,7 +101,11 @@ export default {
|
|||
.cancelAutomaticMerge()
|
||||
.then((res) => res.data)
|
||||
.then((data) => {
|
||||
eventHub.$emit('UpdateWidgetData', data);
|
||||
if (this.glFeatures.mergeRequestWidgetGraphql) {
|
||||
eventHub.$emit('MRWidgetUpdateRequested');
|
||||
} else {
|
||||
eventHub.$emit('UpdateWidgetData', data);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.isCancellingAutoMerge = false;
|
||||
|
@ -119,6 +128,11 @@ export default {
|
|||
eventHub.$emit('MRWidgetUpdateRequested');
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
if (this.glFeatures.mergeRequestWidgetGraphql) {
|
||||
this.$apollo.queries.state.refetch();
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.isRemovingSourceBranch = false;
|
||||
Flash(__('Something went wrong. Please try again.'));
|
||||
|
|
|
@ -7,7 +7,7 @@ import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
|||
import simplePoll from '../../../lib/utils/simple_poll';
|
||||
import eventHub from '../../event_hub';
|
||||
import statusIcon from '../mr_widget_status_icon.vue';
|
||||
import rebaseQuery from '../../queries/states/ready_to_merge.query.graphql';
|
||||
import rebaseQuery from '../../queries/states/rebase.query.graphql';
|
||||
import mergeRequestQueryVariablesMixin from '../../mixins/merge_request_query_variables';
|
||||
import { deprecatedCreateFlash as Flash } from '../../../flash';
|
||||
|
||||
|
|
|
@ -53,8 +53,8 @@ export default {
|
|||
result({ data }) {
|
||||
this.state = {
|
||||
...data.project.mergeRequest,
|
||||
mergeRequestsFfOnlyEnabled: data.mergeRequestsFfOnlyEnabled,
|
||||
onlyAllowMergeIfPipelineSucceeds: data.onlyAllowMergeIfPipelineSucceeds,
|
||||
mergeRequestsFfOnlyEnabled: data.project.mergeRequestsFfOnlyEnabled,
|
||||
onlyAllowMergeIfPipelineSucceeds: data.project.onlyAllowMergeIfPipelineSucceeds,
|
||||
};
|
||||
this.removeSourceBranch = data.project.mergeRequest.shouldRemoveSourceBranch;
|
||||
this.commitMessage = data.project.mergeRequest.defaultMergeCommitMessage;
|
||||
|
@ -277,7 +277,20 @@ export default {
|
|||
return this.mr.mergeRequestDiffsPath;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
if (this.glFeatures.mergeRequestWidgetGraphql) {
|
||||
eventHub.$on('ApprovalUpdated', this.updateGraphqlState);
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.glFeatures.mergeRequestWidgetGraphql) {
|
||||
eventHub.$off('ApprovalUpdated', this.updateGraphqlState);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateGraphqlState() {
|
||||
return this.$apollo.queries.state.refetch();
|
||||
},
|
||||
updateMergeCommitMessage(includeDescription) {
|
||||
const commitMessage = this.glFeatures.mergeRequestWidgetGraphql
|
||||
? this.state.defaultMergeCommitMessage
|
||||
|
@ -326,6 +339,10 @@ export default {
|
|||
} else if (hasError) {
|
||||
eventHub.$emit('FailedToMerge', data.merge_error);
|
||||
}
|
||||
|
||||
if (this.glFeatures.mergeRequestWidgetGraphql) {
|
||||
this.updateGraphqlState();
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.isMakingRequest = false;
|
||||
|
@ -532,7 +549,7 @@ export default {
|
|||
</div>
|
||||
<merge-train-helper-text
|
||||
v-if="shouldRenderMergeTrainHelperText"
|
||||
:pipeline-id="pipeline.id"
|
||||
:pipeline-id="pipelineId"
|
||||
:pipeline-link="pipeline.path"
|
||||
:merge-train-length="stateData.mergeTrainsCount"
|
||||
:merge-train-when-pipeline-succeeds-docs-path="mr.mergeTrainWhenPipelineSucceedsDocsPath"
|
||||
|
|
|
@ -35,5 +35,8 @@ export default {
|
|||
shouldRenderMergeTrainHelperText() {
|
||||
return false;
|
||||
},
|
||||
pipelineId() {
|
||||
return this.pipeline.id;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -94,7 +94,6 @@ export default {
|
|||
state: {
|
||||
query: getStateQuery,
|
||||
manual: true,
|
||||
pollInterval: 10 * 1000,
|
||||
skip() {
|
||||
return !this.mr || !window.gon?.features?.mergeRequestWidgetGraphql;
|
||||
},
|
||||
|
@ -286,6 +285,10 @@ export default {
|
|||
return new MRWidgetService(this.getServiceEndpoints(store));
|
||||
},
|
||||
checkStatus(cb, isRebased) {
|
||||
if (window.gon?.features?.mergeRequestWidgetGraphql) {
|
||||
this.$apollo.queries.state.refetch();
|
||||
}
|
||||
|
||||
return this.service
|
||||
.checkStatus()
|
||||
.then(({ data }) => {
|
||||
|
|
|
@ -18,6 +18,7 @@ query getState($projectPath: ID!, $iid: String!) {
|
|||
}
|
||||
shouldBeRebased
|
||||
sourceBranchExists
|
||||
state
|
||||
targetBranchExists
|
||||
userPermissions {
|
||||
canMerge
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
fragment autoMergeEnabled on MergeRequest {
|
||||
autoMergeStrategy
|
||||
mergeUser {
|
||||
id
|
||||
name
|
||||
username
|
||||
webUrl
|
||||
|
|
|
@ -4,7 +4,6 @@ query autoMergeEnabledQuery($projectPath: ID!, $iid: String!) {
|
|||
project(fullPath: $projectPath) {
|
||||
mergeRequest(iid: $iid) {
|
||||
...autoMergeEnabled
|
||||
mergeTrainsCount
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -156,9 +156,9 @@ export default class MergeRequestStore {
|
|||
|
||||
this.setState();
|
||||
|
||||
mrEventHub.$emit('mr.state.updated', {
|
||||
state: this.mergeRequestState,
|
||||
});
|
||||
if (!window.gon?.features?.mergeRequestWidgetGraphql) {
|
||||
this.emitUpdatedState();
|
||||
}
|
||||
}
|
||||
|
||||
setGraphqlData(project) {
|
||||
|
@ -182,7 +182,9 @@ export default class MergeRequestStore {
|
|||
this.isSHAMismatch = this.sha !== mergeRequest.diffHeadSha;
|
||||
this.shouldBeRebased = mergeRequest.shouldBeRebased;
|
||||
this.workInProgress = mergeRequest.workInProgress;
|
||||
this.mergeRequestState = mergeRequest.state;
|
||||
|
||||
this.emitUpdatedState();
|
||||
this.setState();
|
||||
}
|
||||
|
||||
|
@ -208,6 +210,12 @@ export default class MergeRequestStore {
|
|||
}
|
||||
}
|
||||
|
||||
emitUpdatedState() {
|
||||
mrEventHub.$emit('mr.state.updated', {
|
||||
state: this.mergeRequestState,
|
||||
});
|
||||
}
|
||||
|
||||
setPaths(data) {
|
||||
// Paths are set on the first load of the page and not auto-refreshed
|
||||
this.squashBeforeMergeHelpPath = data.squash_before_merge_help_path;
|
||||
|
|
|
@ -992,6 +992,20 @@ pre.light-well {
|
|||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove once gitlab/ui solution is implemented:
|
||||
// https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1157
|
||||
// https://gitlab.com/gitlab-org/gitlab/-/issues/300405
|
||||
.gl-search-box-by-type-input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
// Remove once gitlab/ui solution is implemented
|
||||
// https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1158
|
||||
// https://gitlab.com/gitlab-org/gitlab/-/issues/300405
|
||||
.gl-new-dropdown-button-text {
|
||||
@include str-truncated;
|
||||
}
|
||||
}
|
||||
|
||||
.clearable-input {
|
||||
|
|
|
@ -31,9 +31,9 @@ module NotesActions
|
|||
# We know there's more data, so tell the frontend to poll again after 1ms
|
||||
set_polling_interval_header(interval: 1) if meta[:more]
|
||||
|
||||
# Only present an ETag for the empty response to ensure pagination works
|
||||
# as expected
|
||||
::Gitlab::EtagCaching::Middleware.skip!(response) if notes.present?
|
||||
# We might still want to investigate further adjusting ETag caching with paginated notes, but
|
||||
# let's avoid ETag caching for now until we confirm the viability of paginated notes.
|
||||
::Gitlab::EtagCaching::Middleware.skip!(response)
|
||||
|
||||
render json: meta.merge(notes: notes)
|
||||
end
|
||||
|
|
|
@ -44,6 +44,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
|
|||
push_frontend_feature_flag(:codequality_mr_diff, @project)
|
||||
push_frontend_feature_flag(:suggestions_custom_commit, @project)
|
||||
push_frontend_feature_flag(:local_file_reviews, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:paginated_notes, @project, default_enabled: :yaml)
|
||||
|
||||
record_experiment_user(:invite_members_version_a)
|
||||
record_experiment_user(:invite_members_version_b)
|
||||
|
|
|
@ -9,7 +9,7 @@ class SearchController < ApplicationController
|
|||
|
||||
around_action :allow_gitaly_ref_name_caching
|
||||
|
||||
before_action :block_anonymous_global_searches
|
||||
before_action :block_anonymous_global_searches, except: :opensearch
|
||||
skip_before_action :authenticate_user!
|
||||
requires_cross_project_access if: -> do
|
||||
search_term_present = params[:search].present? || params[:term].present?
|
||||
|
|
|
@ -5,6 +5,6 @@ module Types
|
|||
graphql_name 'MergeRequestState'
|
||||
description 'State of a GitLab merge request'
|
||||
|
||||
value 'merged'
|
||||
value 'merged', description: "Merge Request has been merged"
|
||||
end
|
||||
end
|
||||
|
|
|
@ -175,7 +175,9 @@ module NotesHelper
|
|||
end
|
||||
end
|
||||
|
||||
def notes_data(issuable)
|
||||
def notes_data(issuable, start_at_zero = false)
|
||||
initial_last_fetched_at = start_at_zero ? 0 : Time.current.to_i * ::Gitlab::UpdatedNotesPaginator::MICROSECOND
|
||||
|
||||
data = {
|
||||
discussionsPath: discussions_path(issuable),
|
||||
registerPath: new_session_path(:user, redirect_to_referer: 'yes', anchor: 'register-pane'),
|
||||
|
@ -186,7 +188,7 @@ module NotesHelper
|
|||
reopenPath: reopen_issuable_path(issuable),
|
||||
notesPath: notes_url,
|
||||
prerenderedNotesCount: issuable.capped_notes_count(MAX_PRERENDERED_NOTES),
|
||||
lastFetchedAt: Time.now.to_i * ::Gitlab::UpdatedNotesPaginator::MICROSECOND
|
||||
lastFetchedAt: initial_last_fetched_at
|
||||
}
|
||||
|
||||
if issuable.is_a?(MergeRequest)
|
||||
|
|
|
@ -6,28 +6,6 @@ module TreeHelper
|
|||
|
||||
FILE_LIMIT = 1_000
|
||||
|
||||
# Sorts a repository's tree so that folders are before files and renders
|
||||
# their corresponding partials
|
||||
#
|
||||
# tree - A `Tree` object for the current tree
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
def render_tree(tree)
|
||||
# Sort submodules and folders together by name ahead of files
|
||||
folders, files, submodules = tree.trees, tree.blobs, tree.submodules
|
||||
tree = []
|
||||
items = (folders + submodules).sort_by(&:name) + files
|
||||
|
||||
if items.size > FILE_LIMIT
|
||||
tree << render(partial: 'projects/tree/truncated_notice_tree_row',
|
||||
locals: { limit: FILE_LIMIT, total: items.size })
|
||||
items = items.take(FILE_LIMIT)
|
||||
end
|
||||
|
||||
tree << render(partial: 'projects/tree/tree_row', collection: items) if items.present?
|
||||
tree.join.html_safe
|
||||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
||||
# Return an image icon depending on the file type and mode
|
||||
#
|
||||
# type - String type of the tree item; either 'folder' or 'file'
|
||||
|
@ -37,20 +15,6 @@ module TreeHelper
|
|||
sprite_icon(file_type_icon_class(type, mode, name))
|
||||
end
|
||||
|
||||
# Using Rails `*_path` methods can be slow, especially when generating
|
||||
# many paths, as with a repository tree that has thousands of items.
|
||||
def fast_project_blob_path(project, blob_path)
|
||||
ActionDispatch::Journey::Router::Utils.escape_path(
|
||||
File.join(relative_url_root, project.path_with_namespace, '-', 'blob', blob_path)
|
||||
)
|
||||
end
|
||||
|
||||
def fast_project_tree_path(project, tree_path)
|
||||
ActionDispatch::Journey::Router::Utils.escape_path(
|
||||
File.join(relative_url_root, project.path_with_namespace, '-', 'tree', tree_path)
|
||||
)
|
||||
end
|
||||
|
||||
# Simple shortcut to File.join
|
||||
def tree_join(*args)
|
||||
File.join(*args)
|
||||
|
@ -167,13 +131,6 @@ module TreeHelper
|
|||
Gitlab.config.gitlab.relative_url_root.presence || '/'
|
||||
end
|
||||
|
||||
# project and path are used on the EE version
|
||||
def tree_content_data(logs_path, project, path)
|
||||
{
|
||||
"logs-path" => logs_path
|
||||
}
|
||||
end
|
||||
|
||||
def breadcrumb_data_attributes
|
||||
attrs = {
|
||||
can_collaborate: can_collaborate_with_project?(@project).to_s,
|
||||
|
|
|
@ -15,6 +15,7 @@ class BaseDiscussionEntity < Grape::Entity
|
|||
expose :for_commit?, as: :for_commit
|
||||
expose :individual_note?, as: :individual_note
|
||||
expose :resolvable?, as: :resolvable
|
||||
expose :resolved_by_push?, as: :resolved_by_push
|
||||
|
||||
expose :truncated_diff_lines, using: DiffLineEntity, if: -> (d, _) { d.diff_discussion? && d.on_text? && (d.expanded? || render_truncated_diff_lines?) }
|
||||
|
||||
|
|
|
@ -44,11 +44,7 @@ module Git
|
|||
def invalidated_file_types
|
||||
return super unless default_branch? && !creating_branch?
|
||||
|
||||
paths = limited_commits.each_with_object(Set.new) do |commit, set|
|
||||
commit.raw_deltas.each do |diff|
|
||||
set << diff.new_path
|
||||
end
|
||||
end
|
||||
paths = commit_paths.values.reduce(&:merge) || Set.new
|
||||
|
||||
Gitlab::FileDetector.types_in_paths(paths)
|
||||
end
|
||||
|
@ -77,6 +73,7 @@ module Git
|
|||
enqueue_process_commit_messages
|
||||
enqueue_jira_connect_sync_messages
|
||||
enqueue_metrics_dashboard_sync
|
||||
track_ci_config_change_event
|
||||
end
|
||||
|
||||
def branch_remove_hooks
|
||||
|
@ -89,6 +86,18 @@ module Git
|
|||
::Metrics::Dashboard::SyncDashboardsWorker.perform_async(project.id)
|
||||
end
|
||||
|
||||
def track_ci_config_change_event
|
||||
return unless Gitlab::CurrentSettings.usage_ping_enabled?
|
||||
return unless ::Feature.enabled?(:usage_data_unique_users_committing_ciconfigfile, project, default_enabled: :yaml)
|
||||
return unless default_branch?
|
||||
|
||||
commits_changing_ci_config.each do |commit|
|
||||
Gitlab::UsageDataCounters::HLLRedisCounter.track_event(
|
||||
'o_pipeline_authoring_unique_users_committing_ciconfigfile', values: commit.author&.id
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# Schedules processing of commit messages
|
||||
def enqueue_process_commit_messages
|
||||
referencing_commits = limited_commits.select(&:matches_cross_reference_regex?)
|
||||
|
@ -190,6 +199,23 @@ module Git
|
|||
|
||||
set
|
||||
end
|
||||
|
||||
def commits_changing_ci_config
|
||||
commit_paths.select do |commit, paths|
|
||||
next if commit.merge_commit?
|
||||
|
||||
paths.include?(project.ci_config_path_or_default)
|
||||
end.keys
|
||||
end
|
||||
|
||||
def commit_paths
|
||||
strong_memoize(:commit_paths) do
|
||||
limited_commits.map do |commit|
|
||||
paths = Set.new(commit.raw_deltas.map(&:new_path))
|
||||
[commit, paths]
|
||||
end.to_h
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -84,8 +84,7 @@
|
|||
= s_('Profiles|You must transfer ownership or delete these groups before you can delete your account.')
|
||||
- elsif !current_user.can_remove_self?
|
||||
%p
|
||||
- reset_link = reset_profile_password_path
|
||||
= s_('Profiles|GitLab is unable to verify your identity automatically. For security purposes, you must set a password by %{openingTag}resetting your password%{closingTag} to delete your account.').html_safe % { openingTag: "<a href='#{reset_link}'>".html_safe, closingTag: '</a>'.html_safe}
|
||||
= s_('Profiles|GitLab is unable to verify your identity automatically. For security purposes, you must set a password by %{openingTag}resetting your password%{closingTag} to delete your account.').html_safe % { openingTag: "<a href='#{reset_profile_password_path}' rel=\"nofollow\" data-method=\"put\">".html_safe, closingTag: '</a>'.html_safe}
|
||||
%p
|
||||
= s_('Profiles|If after setting a password, the option to delete your account is still not available, please email %{data_request} to begin the account deletion process.').html_safe % { data_request: mail_to('personal-data-request@gitlab.com') }
|
||||
- else
|
||||
|
|
|
@ -8,13 +8,13 @@
|
|||
= link_to project_tree_path(@project, branch.name), class: 'item-title str-truncated-100 ref-name gl-ml-3 qa-branch-name' do
|
||||
= branch.name
|
||||
- if branch.name == @repository.root_ref
|
||||
%span.badge.badge-primary.gl-ml-2 default
|
||||
%span.badge.gl-badge.sm.badge-pill.badge-primary.gl-ml-2 default
|
||||
- elsif merged
|
||||
%span.badge.badge-info.has-tooltip.gl-ml-2{ title: s_('Branches|Merged into %{default_branch}') % { default_branch: @repository.root_ref } }
|
||||
%span.badge.gl-badge.sm.badge-pill.badge-info.has-tooltip.gl-ml-2{ title: s_('Branches|Merged into %{default_branch}') % { default_branch: @repository.root_ref } }
|
||||
= s_('Branches|merged')
|
||||
|
||||
- if protected_branch?(@project, branch)
|
||||
%span.badge.badge-success.gl-ml-2
|
||||
%span.badge.gl-badge.sm.badge-pill.badge-success.gl-ml-2
|
||||
= s_('Branches|protected')
|
||||
|
||||
= render_if_exists 'projects/branches/diverged_from_upstream', branch: branch
|
||||
|
|
|
@ -2,5 +2,5 @@
|
|||
- tag = local_assigns.fetch(:tag, nil)
|
||||
- return unless project && tag
|
||||
|
||||
%button{ type: "button", class: "js-remove-tag js-confirm-modal-button gl-button btn btn-danger remove-row has-tooltip gl-ml-3 #{protected_tag?(project, tag) ? 'disabled' : ''}", title: s_('TagsPage|Delete tag'), data: { container: 'body', path: project_tag_path(@project, tag.name), modal_attributes: delete_tag_modal_attributes(tag.name) } }
|
||||
%button{ type: "button", class: "js-remove-tag js-confirm-modal-button gl-button btn btn-danger btn-icon remove-row has-tooltip gl-ml-3 #{protected_tag?(project, tag) ? 'disabled' : ''}", title: s_('TagsPage|Delete tag'), data: { container: 'body', path: project_tag_path(@project, tag.name), modal_attributes: delete_tag_modal_attributes(tag.name) } }
|
||||
= sprite_icon("remove")
|
||||
|
|
|
@ -13,4 +13,8 @@
|
|||
= html_escape(_("Changes are shown as if the %{b_open}source%{b_close} revision was being merged into the %{b_open}target%{b_close} revision.")) % { b_open: '<b>'.html_safe, b_close: '</b>'.html_safe }
|
||||
|
||||
.prepend-top-20
|
||||
= render "form"
|
||||
#js-compare-selector{ data: { project_compare_index_path: project_compare_index_path(@project),
|
||||
refs_project_path: refs_project_path(@project),
|
||||
params_from: params[:from], params_to: params[:to],
|
||||
project_merge_request_path: @merge_request.present? ? project_merge_request_path(@project, @merge_request) : '',
|
||||
create_mr_path: create_mr_button? ? create_mr_path : '' } }
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
.settings-header
|
||||
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only= _('Visibility, project features, permissions')
|
||||
%button.gl-button.btn.btn-default.js-settings-toggle{ type: 'button' }= expanded ? _('Collapse') : _('Expand')
|
||||
%p= _('Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions.')
|
||||
%p= _('Choose visibility level, enable/disable project features and their permissions, disable email notifications, and show default award emoji.')
|
||||
|
||||
.settings-content
|
||||
= form_for @project, remote: true, html: { multipart: true, class: "sharing-permissions-form" }, authenticity_token: true do |f|
|
||||
|
|
|
@ -56,10 +56,13 @@
|
|||
= render "projects/merge_requests/widget"
|
||||
= render "projects/merge_requests/awards_block"
|
||||
- if mr_action === "show"
|
||||
- add_page_startup_api_call discussions_path(@merge_request)
|
||||
- if Feature.enabled?(:paginated_notes, @project)
|
||||
- add_page_startup_api_call notes_url
|
||||
- else
|
||||
- add_page_startup_api_call discussions_path(@merge_request)
|
||||
- add_page_startup_api_call widget_project_json_merge_request_path(@project, @merge_request, format: :json)
|
||||
- add_page_startup_api_call cached_widget_project_json_merge_request_path(@project, @merge_request, format: :json)
|
||||
#js-vue-mr-discussions{ data: { notes_data: notes_data(@merge_request).to_json,
|
||||
#js-vue-mr-discussions{ data: { notes_data: notes_data(@merge_request, Feature.enabled?(:paginated_notes, @project)).to_json,
|
||||
noteable_data: serialize_issuable(@merge_request, serializer: 'noteable'),
|
||||
noteable_type: 'MergeRequest',
|
||||
target_type: 'merge_request',
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
.project-network.gl-border-1.gl-border-solid.gl-border-gray-300
|
||||
.controls.gl-bg-gray-50.gl-p-2.gl-font-base.gl-text-gray-400.gl-border-b-1.gl-border-b-solid.gl-border-b-gray-300
|
||||
= form_tag project_network_path(@project, @id), method: :get, class: 'form-inline network-form' do |f|
|
||||
= text_field_tag :extended_sha1, @options[:extended_sha1], placeholder: _("Git revision"), class: 'search-input form-control input-mx-250 search-sha'
|
||||
= button_tag class: 'btn btn-success' do
|
||||
= text_field_tag :extended_sha1, @options[:extended_sha1], placeholder: _("Git revision"), class: 'search-input form-control gl-form-input input-mx-250 search-sha gl-mr-2'
|
||||
= button_tag class: 'btn gl-button btn-success btn-icon' do
|
||||
= sprite_icon('search')
|
||||
.inline.gl-ml-5
|
||||
.form-check.light
|
||||
|
|
|
@ -41,6 +41,6 @@
|
|||
= render 'projects/buttons/download', project: @project, ref: tag.name, pipeline: @tags_pipelines[tag.name]
|
||||
|
||||
- if can?(current_user, :admin_tag, @project)
|
||||
= link_to edit_project_tag_release_path(@project, tag.name), class: 'btn btn-edit has-tooltip', title: s_('TagsPage|Edit release notes'), data: { container: "body" } do
|
||||
= link_to edit_project_tag_release_path(@project, tag.name), class: 'btn gl-button btn-default btn-icon btn-edit has-tooltip', title: s_('TagsPage|Edit release notes'), data: { container: "body" } do
|
||||
= sprite_icon("pencil")
|
||||
= render 'projects/buttons/remove_tag', project: @project, tag: tag
|
||||
|
|
|
@ -24,9 +24,9 @@
|
|||
%li
|
||||
= link_to title, filter_tags_path(sort: value), class: ("is-active" if @sort == value)
|
||||
- if can?(current_user, :admin_tag, @project)
|
||||
= link_to new_project_tag_path(@project), class: 'btn btn-success new-tag-btn', data: { qa_selector: "new_tag_button" } do
|
||||
= link_to new_project_tag_path(@project), class: 'btn gl-button btn-success', data: { qa_selector: "new_tag_button" } do
|
||||
= s_('TagsPage|New tag')
|
||||
= link_to project_tags_path(@project, rss_url_options), title: _("Tags feed"), class: 'btn btn-svg d-none d-sm-inline-block has-tooltip' do
|
||||
= link_to project_tags_path(@project, rss_url_options), title: _("Tags feed"), class: 'btn gl-button btn-default btn-icon d-none d-sm-inline-block has-tooltip' do
|
||||
= sprite_icon('rss', css_class: 'qa-rss-icon')
|
||||
|
||||
= render_if_exists 'projects/commits/mirror_status'
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
- if readme.rich_viewer
|
||||
%article.file-holder.readme-holder{ id: 'readme', class: [("limited-width-container" unless fluid_layout)] }
|
||||
.js-file-title.file-title-flex-parent
|
||||
.file-header-content
|
||||
= blob_icon readme.mode, readme.name
|
||||
= link_to project_blob_path(@project, tree_join(@ref, readme.path)) do
|
||||
%strong
|
||||
= readme.name
|
||||
|
||||
= render 'projects/blob/viewer', viewer: readme.rich_viewer, viewer_url: project_blob_path(@project, tree_join(@ref, readme.path), viewer: :rich, format: :json)
|
|
@ -1,24 +0,0 @@
|
|||
.tree-content-holder.js-tree-content{ data: tree_content_data(@logs_path, @project, @path) }
|
||||
.table-holder.bordered-box
|
||||
%table.table#tree-slider{ class: "table_#{@hex_path} tree-table" }
|
||||
%thead
|
||||
%tr
|
||||
%th= s_('ProjectFileTree|Name')
|
||||
%th.d-none.d-sm-table-cell
|
||||
.float-left= _('Last commit')
|
||||
%th.text-right= _('Last update')
|
||||
- if @path.present?
|
||||
%tr.tree-item
|
||||
%td.tree-item-file-name
|
||||
= link_to "..", project_tree_path(@project, up_dir_path), class: 'gl-ml-3'
|
||||
%td
|
||||
%td.d-none.d-sm-table-cell
|
||||
|
||||
= render_tree(tree)
|
||||
|
||||
- if tree.readme
|
||||
= render "projects/tree/readme", readme: tree.readme
|
||||
|
||||
- if can_edit_tree?
|
||||
= render 'projects/blob/upload', title: _('Upload New File'), placeholder: _('Upload New File'), button_title: _('Upload file'), form_path: project_create_blob_path(@project, @id), method: :post
|
||||
= render 'projects/blob/new_dir'
|
|
@ -1,27 +0,0 @@
|
|||
- tree_row_name = tree_row.name
|
||||
- tree_row_type = tree_row.type
|
||||
|
||||
%tr{ class: "tree-item file_#{hexdigest(tree_row_name)}" }
|
||||
%td.tree-item-file-name
|
||||
- if tree_row_type == :tree
|
||||
= tree_icon('folder', tree_row.mode, tree_row.name)
|
||||
- path = flatten_tree(@path, tree_row)
|
||||
%a.str-truncated{ href: fast_project_tree_path(@project, tree_join(@id || @commit.id, path)), title: path }
|
||||
%span= path
|
||||
|
||||
- elsif tree_row_type == :blob
|
||||
= tree_icon('file', tree_row.mode, tree_row_name)
|
||||
%a.str-truncated{ href: fast_project_blob_path(@project, tree_join(@id || @commit.id, tree_row_name)), title: tree_row_name }
|
||||
%span= tree_row_name
|
||||
- if @lfs_blob_ids.include?(tree_row.id)
|
||||
%span.badge.label-lfs.gl-ml-2 LFS
|
||||
|
||||
- elsif tree_row_type == :commit
|
||||
= tree_icon('archive', tree_row.mode, tree_row.name)
|
||||
= submodule_link(tree_row, @ref)
|
||||
|
||||
%td.d-none.d-sm-table-cell.tree-commit
|
||||
%td.tree-time-ago.text-right
|
||||
%span.log_loading.hide
|
||||
= loading_icon
|
||||
Loading commit data...
|
|
@ -1,7 +0,0 @@
|
|||
%tr.tree-truncated-warning
|
||||
%td{ colspan: '3' }
|
||||
= sprite_icon('warning-solid')
|
||||
%span
|
||||
Too many items to show. To preserve performance only
|
||||
%strong #{number_with_delimiter(limit)} of #{number_with_delimiter(total)}
|
||||
items are displayed.
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fixes broken password reset link in account deletion message
|
||||
merge_request: 53274
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Update question mark icon while checking pipeline status
|
||||
merge_request: 52760
|
||||
author: Yogi (@yo)
|
||||
type: other
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix opensearch for anonymous users
|
||||
merge_request: 53056
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Apply new GitLab UI for badges in the project branch list
|
||||
merge_request: 52868
|
||||
author: Yogi (@yo)
|
||||
type: other
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Apply new GitLab UI for buttons in tags page
|
||||
merge_request: 52862
|
||||
author: Yogi (@yo)
|
||||
type: other
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Apply new GitLab UI for buttons and input in the project graph page
|
||||
merge_request: 52864
|
||||
author: Yogi (@yo)
|
||||
type: other
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Edited UI copy wording to comply with GitLab style
|
||||
merge_request: 50676
|
||||
author:
|
||||
type: other
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: usage_data_unique_users_committing_ciconfigfile
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/52172
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/299403
|
||||
milestone: '13.9'
|
||||
type: development
|
||||
group: group::pipeline authoring
|
||||
default_enabled: false
|
Binary file not shown.
After Width: | Height: | Size: 41 KiB |
|
@ -41,3 +41,18 @@ After adding the configuration parameter, reconfigure and restart your GitLab in
|
|||
gitlab-ctl reconfigure
|
||||
gitlab-ctl restart
|
||||
```
|
||||
|
||||
## Changing time zone per user
|
||||
|
||||
To allow users to change the time zone in their profile, the feature flag `user_time_settings` should be enabled:
|
||||
|
||||
1. [Start a Rails console session](operations/rails_console.md).
|
||||
1. Enable the feature flag:
|
||||
|
||||
```ruby
|
||||
Feature.enable(:user_time_settings)
|
||||
```
|
||||
|
||||
1. You should now be able to see the timezone dropdown in the users' **Settings > Profile** page.
|
||||
|
||||
![User Time Zone Settings](img/time_zone_settings.png)
|
||||
|
|
|
@ -742,6 +742,26 @@ enum AlertManagementIntegrationType {
|
|||
PROMETHEUS
|
||||
}
|
||||
|
||||
"""
|
||||
Parsed field from an alert used for custom mappings
|
||||
"""
|
||||
type AlertManagementPayloadAlertField {
|
||||
"""
|
||||
Human-readable label of the payload path.
|
||||
"""
|
||||
label: String
|
||||
|
||||
"""
|
||||
Path to value inside payload JSON.
|
||||
"""
|
||||
path: [String!]
|
||||
|
||||
"""
|
||||
Type of the parsed value.
|
||||
"""
|
||||
type: AlertManagementPayloadAlertFieldType
|
||||
}
|
||||
|
||||
"""
|
||||
Field that are available while modifying the custom mapping attributes for an HTTP integration
|
||||
"""
|
||||
|
@ -1571,12 +1591,12 @@ Represents an epic on an issue board
|
|||
"""
|
||||
type BoardEpic implements CurrentUserTodos & Noteable {
|
||||
"""
|
||||
Author of the epic
|
||||
Author of the epic.
|
||||
"""
|
||||
author: User!
|
||||
|
||||
"""
|
||||
A list of award emojis associated with the epic
|
||||
A list of award emojis associated with the epic.
|
||||
"""
|
||||
awardEmoji(
|
||||
"""
|
||||
|
@ -1601,7 +1621,7 @@ type BoardEpic implements CurrentUserTodos & Noteable {
|
|||
): AwardEmojiConnection
|
||||
|
||||
"""
|
||||
Children (sub-epics) of the epic
|
||||
Children (sub-epics) of the epic.
|
||||
"""
|
||||
children(
|
||||
"""
|
||||
|
@ -1699,17 +1719,17 @@ type BoardEpic implements CurrentUserTodos & Noteable {
|
|||
): EpicConnection
|
||||
|
||||
"""
|
||||
Timestamp of when the epic was closed
|
||||
Timestamp of when the epic was closed.
|
||||
"""
|
||||
closedAt: Time
|
||||
|
||||
"""
|
||||
Indicates if the epic is confidential
|
||||
Indicates if the epic is confidential.
|
||||
"""
|
||||
confidential: Boolean
|
||||
|
||||
"""
|
||||
Timestamp of when the epic was created
|
||||
Timestamp of when the epic was created.
|
||||
"""
|
||||
createdAt: Time
|
||||
|
||||
|
@ -1744,17 +1764,17 @@ type BoardEpic implements CurrentUserTodos & Noteable {
|
|||
): TodoConnection!
|
||||
|
||||
"""
|
||||
Number of open and closed descendant epics and issues
|
||||
Number of open and closed descendant epics and issues.
|
||||
"""
|
||||
descendantCounts: EpicDescendantCount
|
||||
|
||||
"""
|
||||
Total weight of open and closed issues in the epic and its descendants
|
||||
Total weight of open and closed issues in the epic and its descendants.
|
||||
"""
|
||||
descendantWeightSum: EpicDescendantWeights
|
||||
|
||||
"""
|
||||
Description of the epic
|
||||
Description of the epic.
|
||||
"""
|
||||
description: String
|
||||
|
||||
|
@ -1784,67 +1804,67 @@ type BoardEpic implements CurrentUserTodos & Noteable {
|
|||
): DiscussionConnection!
|
||||
|
||||
"""
|
||||
Number of downvotes the epic has received
|
||||
Number of downvotes the epic has received.
|
||||
"""
|
||||
downvotes: Int!
|
||||
|
||||
"""
|
||||
Due date of the epic
|
||||
Due date of the epic.
|
||||
"""
|
||||
dueDate: Time
|
||||
|
||||
"""
|
||||
Fixed due date of the epic
|
||||
Fixed due date of the epic.
|
||||
"""
|
||||
dueDateFixed: Time
|
||||
|
||||
"""
|
||||
Inherited due date of the epic from milestones
|
||||
Inherited due date of the epic from milestones.
|
||||
"""
|
||||
dueDateFromMilestones: Time
|
||||
|
||||
"""
|
||||
Indicates if the due date has been manually set
|
||||
Indicates if the due date has been manually set.
|
||||
"""
|
||||
dueDateIsFixed: Boolean
|
||||
|
||||
"""
|
||||
Group to which the epic belongs
|
||||
Group to which the epic belongs.
|
||||
"""
|
||||
group: Group!
|
||||
|
||||
"""
|
||||
Indicates if the epic has children
|
||||
Indicates if the epic has children.
|
||||
"""
|
||||
hasChildren: Boolean!
|
||||
|
||||
"""
|
||||
Indicates if the epic has direct issues
|
||||
Indicates if the epic has direct issues.
|
||||
"""
|
||||
hasIssues: Boolean!
|
||||
|
||||
"""
|
||||
Indicates if the epic has a parent epic
|
||||
Indicates if the epic has a parent epic.
|
||||
"""
|
||||
hasParent: Boolean!
|
||||
|
||||
"""
|
||||
Current health status of the epic
|
||||
Current health status of the epic.
|
||||
"""
|
||||
healthStatus: EpicHealthStatus
|
||||
|
||||
"""
|
||||
ID of the epic
|
||||
ID of the epic.
|
||||
"""
|
||||
id: ID!
|
||||
|
||||
"""
|
||||
Internal ID of the epic
|
||||
Internal ID of the epic.
|
||||
"""
|
||||
iid: ID!
|
||||
|
||||
"""
|
||||
A list of issues associated with the epic
|
||||
A list of issues associated with the epic.
|
||||
"""
|
||||
issues(
|
||||
"""
|
||||
|
@ -1869,7 +1889,7 @@ type BoardEpic implements CurrentUserTodos & Noteable {
|
|||
): EpicIssueConnection
|
||||
|
||||
"""
|
||||
Labels assigned to the epic
|
||||
Labels assigned to the epic.
|
||||
"""
|
||||
labels(
|
||||
"""
|
||||
|
@ -1919,12 +1939,12 @@ type BoardEpic implements CurrentUserTodos & Noteable {
|
|||
): NoteConnection!
|
||||
|
||||
"""
|
||||
Parent epic of the epic
|
||||
Parent epic of the epic.
|
||||
"""
|
||||
parent: Epic
|
||||
|
||||
"""
|
||||
List of participants for the epic
|
||||
List of participants for the epic.
|
||||
"""
|
||||
participants(
|
||||
"""
|
||||
|
@ -1949,77 +1969,77 @@ type BoardEpic implements CurrentUserTodos & Noteable {
|
|||
): UserConnection
|
||||
|
||||
"""
|
||||
Internal reference of the epic. Returned in shortened format by default
|
||||
Internal reference of the epic. Returned in shortened format by default.
|
||||
"""
|
||||
reference(
|
||||
"""
|
||||
Indicates if the reference should be returned in full
|
||||
Indicates if the reference should be returned in full.
|
||||
"""
|
||||
full: Boolean = false
|
||||
): String!
|
||||
|
||||
"""
|
||||
URI path of the epic-issue relationship
|
||||
URI path of the epic-issue relationship.
|
||||
"""
|
||||
relationPath: String
|
||||
|
||||
"""
|
||||
The relative position of the epic in the epic tree
|
||||
The relative position of the epic in the epic tree.
|
||||
"""
|
||||
relativePosition: Int
|
||||
|
||||
"""
|
||||
Start date of the epic
|
||||
Start date of the epic.
|
||||
"""
|
||||
startDate: Time
|
||||
|
||||
"""
|
||||
Fixed start date of the epic
|
||||
Fixed start date of the epic.
|
||||
"""
|
||||
startDateFixed: Time
|
||||
|
||||
"""
|
||||
Inherited start date of the epic from milestones
|
||||
Inherited start date of the epic from milestones.
|
||||
"""
|
||||
startDateFromMilestones: Time
|
||||
|
||||
"""
|
||||
Indicates if the start date has been manually set
|
||||
Indicates if the start date has been manually set.
|
||||
"""
|
||||
startDateIsFixed: Boolean
|
||||
|
||||
"""
|
||||
State of the epic
|
||||
State of the epic.
|
||||
"""
|
||||
state: EpicState!
|
||||
|
||||
"""
|
||||
Indicates the currently logged in user is subscribed to the epic
|
||||
Indicates the currently logged in user is subscribed to the epic.
|
||||
"""
|
||||
subscribed: Boolean!
|
||||
|
||||
"""
|
||||
Title of the epic
|
||||
Title of the epic.
|
||||
"""
|
||||
title: String
|
||||
|
||||
"""
|
||||
Timestamp of when the epic was updated
|
||||
Timestamp of when the epic was updated.
|
||||
"""
|
||||
updatedAt: Time
|
||||
|
||||
"""
|
||||
Number of upvotes the epic has received
|
||||
Number of upvotes the epic has received.
|
||||
"""
|
||||
upvotes: Int!
|
||||
|
||||
"""
|
||||
Number of user discussions in the epic
|
||||
Number of user discussions in the epic.
|
||||
"""
|
||||
userDiscussionsCount: Int!
|
||||
|
||||
"""
|
||||
Number of user notes of the epic
|
||||
Number of user notes of the epic.
|
||||
"""
|
||||
userNotesCount: Int!
|
||||
|
||||
|
@ -2034,12 +2054,12 @@ type BoardEpic implements CurrentUserTodos & Noteable {
|
|||
userPreferences: BoardEpicUserPreferences
|
||||
|
||||
"""
|
||||
Web path of the epic
|
||||
Web path of the epic.
|
||||
"""
|
||||
webPath: String!
|
||||
|
||||
"""
|
||||
Web URL of the epic
|
||||
Web URL of the epic.
|
||||
"""
|
||||
webUrl: String!
|
||||
}
|
||||
|
@ -8417,12 +8437,12 @@ Represents an epic
|
|||
"""
|
||||
type Epic implements CurrentUserTodos & Noteable {
|
||||
"""
|
||||
Author of the epic
|
||||
Author of the epic.
|
||||
"""
|
||||
author: User!
|
||||
|
||||
"""
|
||||
A list of award emojis associated with the epic
|
||||
A list of award emojis associated with the epic.
|
||||
"""
|
||||
awardEmoji(
|
||||
"""
|
||||
|
@ -8447,7 +8467,7 @@ type Epic implements CurrentUserTodos & Noteable {
|
|||
): AwardEmojiConnection
|
||||
|
||||
"""
|
||||
Children (sub-epics) of the epic
|
||||
Children (sub-epics) of the epic.
|
||||
"""
|
||||
children(
|
||||
"""
|
||||
|
@ -8545,17 +8565,17 @@ type Epic implements CurrentUserTodos & Noteable {
|
|||
): EpicConnection
|
||||
|
||||
"""
|
||||
Timestamp of when the epic was closed
|
||||
Timestamp of when the epic was closed.
|
||||
"""
|
||||
closedAt: Time
|
||||
|
||||
"""
|
||||
Indicates if the epic is confidential
|
||||
Indicates if the epic is confidential.
|
||||
"""
|
||||
confidential: Boolean
|
||||
|
||||
"""
|
||||
Timestamp of when the epic was created
|
||||
Timestamp of when the epic was created.
|
||||
"""
|
||||
createdAt: Time
|
||||
|
||||
|
@ -8590,17 +8610,17 @@ type Epic implements CurrentUserTodos & Noteable {
|
|||
): TodoConnection!
|
||||
|
||||
"""
|
||||
Number of open and closed descendant epics and issues
|
||||
Number of open and closed descendant epics and issues.
|
||||
"""
|
||||
descendantCounts: EpicDescendantCount
|
||||
|
||||
"""
|
||||
Total weight of open and closed issues in the epic and its descendants
|
||||
Total weight of open and closed issues in the epic and its descendants.
|
||||
"""
|
||||
descendantWeightSum: EpicDescendantWeights
|
||||
|
||||
"""
|
||||
Description of the epic
|
||||
Description of the epic.
|
||||
"""
|
||||
description: String
|
||||
|
||||
|
@ -8630,67 +8650,67 @@ type Epic implements CurrentUserTodos & Noteable {
|
|||
): DiscussionConnection!
|
||||
|
||||
"""
|
||||
Number of downvotes the epic has received
|
||||
Number of downvotes the epic has received.
|
||||
"""
|
||||
downvotes: Int!
|
||||
|
||||
"""
|
||||
Due date of the epic
|
||||
Due date of the epic.
|
||||
"""
|
||||
dueDate: Time
|
||||
|
||||
"""
|
||||
Fixed due date of the epic
|
||||
Fixed due date of the epic.
|
||||
"""
|
||||
dueDateFixed: Time
|
||||
|
||||
"""
|
||||
Inherited due date of the epic from milestones
|
||||
Inherited due date of the epic from milestones.
|
||||
"""
|
||||
dueDateFromMilestones: Time
|
||||
|
||||
"""
|
||||
Indicates if the due date has been manually set
|
||||
Indicates if the due date has been manually set.
|
||||
"""
|
||||
dueDateIsFixed: Boolean
|
||||
|
||||
"""
|
||||
Group to which the epic belongs
|
||||
Group to which the epic belongs.
|
||||
"""
|
||||
group: Group!
|
||||
|
||||
"""
|
||||
Indicates if the epic has children
|
||||
Indicates if the epic has children.
|
||||
"""
|
||||
hasChildren: Boolean!
|
||||
|
||||
"""
|
||||
Indicates if the epic has direct issues
|
||||
Indicates if the epic has direct issues.
|
||||
"""
|
||||
hasIssues: Boolean!
|
||||
|
||||
"""
|
||||
Indicates if the epic has a parent epic
|
||||
Indicates if the epic has a parent epic.
|
||||
"""
|
||||
hasParent: Boolean!
|
||||
|
||||
"""
|
||||
Current health status of the epic
|
||||
Current health status of the epic.
|
||||
"""
|
||||
healthStatus: EpicHealthStatus
|
||||
|
||||
"""
|
||||
ID of the epic
|
||||
ID of the epic.
|
||||
"""
|
||||
id: ID!
|
||||
|
||||
"""
|
||||
Internal ID of the epic
|
||||
Internal ID of the epic.
|
||||
"""
|
||||
iid: ID!
|
||||
|
||||
"""
|
||||
A list of issues associated with the epic
|
||||
A list of issues associated with the epic.
|
||||
"""
|
||||
issues(
|
||||
"""
|
||||
|
@ -8715,7 +8735,7 @@ type Epic implements CurrentUserTodos & Noteable {
|
|||
): EpicIssueConnection
|
||||
|
||||
"""
|
||||
Labels assigned to the epic
|
||||
Labels assigned to the epic.
|
||||
"""
|
||||
labels(
|
||||
"""
|
||||
|
@ -8765,12 +8785,12 @@ type Epic implements CurrentUserTodos & Noteable {
|
|||
): NoteConnection!
|
||||
|
||||
"""
|
||||
Parent epic of the epic
|
||||
Parent epic of the epic.
|
||||
"""
|
||||
parent: Epic
|
||||
|
||||
"""
|
||||
List of participants for the epic
|
||||
List of participants for the epic.
|
||||
"""
|
||||
participants(
|
||||
"""
|
||||
|
@ -8795,77 +8815,77 @@ type Epic implements CurrentUserTodos & Noteable {
|
|||
): UserConnection
|
||||
|
||||
"""
|
||||
Internal reference of the epic. Returned in shortened format by default
|
||||
Internal reference of the epic. Returned in shortened format by default.
|
||||
"""
|
||||
reference(
|
||||
"""
|
||||
Indicates if the reference should be returned in full
|
||||
Indicates if the reference should be returned in full.
|
||||
"""
|
||||
full: Boolean = false
|
||||
): String!
|
||||
|
||||
"""
|
||||
URI path of the epic-issue relationship
|
||||
URI path of the epic-issue relationship.
|
||||
"""
|
||||
relationPath: String
|
||||
|
||||
"""
|
||||
The relative position of the epic in the epic tree
|
||||
The relative position of the epic in the epic tree.
|
||||
"""
|
||||
relativePosition: Int
|
||||
|
||||
"""
|
||||
Start date of the epic
|
||||
Start date of the epic.
|
||||
"""
|
||||
startDate: Time
|
||||
|
||||
"""
|
||||
Fixed start date of the epic
|
||||
Fixed start date of the epic.
|
||||
"""
|
||||
startDateFixed: Time
|
||||
|
||||
"""
|
||||
Inherited start date of the epic from milestones
|
||||
Inherited start date of the epic from milestones.
|
||||
"""
|
||||
startDateFromMilestones: Time
|
||||
|
||||
"""
|
||||
Indicates if the start date has been manually set
|
||||
Indicates if the start date has been manually set.
|
||||
"""
|
||||
startDateIsFixed: Boolean
|
||||
|
||||
"""
|
||||
State of the epic
|
||||
State of the epic.
|
||||
"""
|
||||
state: EpicState!
|
||||
|
||||
"""
|
||||
Indicates the currently logged in user is subscribed to the epic
|
||||
Indicates the currently logged in user is subscribed to the epic.
|
||||
"""
|
||||
subscribed: Boolean!
|
||||
|
||||
"""
|
||||
Title of the epic
|
||||
Title of the epic.
|
||||
"""
|
||||
title: String
|
||||
|
||||
"""
|
||||
Timestamp of when the epic was updated
|
||||
Timestamp of when the epic was updated.
|
||||
"""
|
||||
updatedAt: Time
|
||||
|
||||
"""
|
||||
Number of upvotes the epic has received
|
||||
Number of upvotes the epic has received.
|
||||
"""
|
||||
upvotes: Int!
|
||||
|
||||
"""
|
||||
Number of user discussions in the epic
|
||||
Number of user discussions in the epic.
|
||||
"""
|
||||
userDiscussionsCount: Int!
|
||||
|
||||
"""
|
||||
Number of user notes of the epic
|
||||
Number of user notes of the epic.
|
||||
"""
|
||||
userNotesCount: Int!
|
||||
|
||||
|
@ -8875,12 +8895,12 @@ type Epic implements CurrentUserTodos & Noteable {
|
|||
userPermissions: EpicPermissions!
|
||||
|
||||
"""
|
||||
Web path of the epic
|
||||
Web path of the epic.
|
||||
"""
|
||||
webPath: String!
|
||||
|
||||
"""
|
||||
Web URL of the epic
|
||||
Web URL of the epic.
|
||||
"""
|
||||
webUrl: String!
|
||||
}
|
||||
|
@ -15696,6 +15716,10 @@ enum MergeRequestState {
|
|||
all
|
||||
closed
|
||||
locked
|
||||
|
||||
"""
|
||||
Merge Request has been merged
|
||||
"""
|
||||
merged
|
||||
opened
|
||||
}
|
||||
|
@ -18308,6 +18332,16 @@ type Project {
|
|||
last: Int
|
||||
): AlertManagementIntegrationConnection
|
||||
|
||||
"""
|
||||
Extract alert fields from payload for custom mapping
|
||||
"""
|
||||
alertManagementPayloadFields(
|
||||
"""
|
||||
Sample payload for extracting alert fields for custom mappings.
|
||||
"""
|
||||
payloadExample: String!
|
||||
): [AlertManagementPayloadAlertField!]
|
||||
|
||||
"""
|
||||
If `only_allow_merge_if_pipeline_succeeds` is true, indicates if merge
|
||||
requests of the project can also be merged with skipped jobs
|
||||
|
|
|
@ -1909,6 +1909,69 @@
|
|||
],
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "AlertManagementPayloadAlertField",
|
||||
"description": "Parsed field from an alert used for custom mappings",
|
||||
"fields": [
|
||||
{
|
||||
"name": "label",
|
||||
"description": "Human-readable label of the payload path.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "path",
|
||||
"description": "Path to value inside payload JSON.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "type",
|
||||
"description": "Type of the parsed value.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "ENUM",
|
||||
"name": "AlertManagementPayloadAlertFieldType",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [
|
||||
|
||||
],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "AlertManagementPayloadAlertFieldInput",
|
||||
|
@ -4097,7 +4160,7 @@
|
|||
"fields": [
|
||||
{
|
||||
"name": "author",
|
||||
"description": "Author of the epic",
|
||||
"description": "Author of the epic.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -4115,7 +4178,7 @@
|
|||
},
|
||||
{
|
||||
"name": "awardEmoji",
|
||||
"description": "A list of award emojis associated with the epic",
|
||||
"description": "A list of award emojis associated with the epic.",
|
||||
"args": [
|
||||
{
|
||||
"name": "after",
|
||||
|
@ -4168,7 +4231,7 @@
|
|||
},
|
||||
{
|
||||
"name": "children",
|
||||
"description": "Children (sub-epics) of the epic",
|
||||
"description": "Children (sub-epics) of the epic.",
|
||||
"args": [
|
||||
{
|
||||
"name": "startDate",
|
||||
|
@ -4377,7 +4440,7 @@
|
|||
},
|
||||
{
|
||||
"name": "closedAt",
|
||||
"description": "Timestamp of when the epic was closed",
|
||||
"description": "Timestamp of when the epic was closed.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -4391,7 +4454,7 @@
|
|||
},
|
||||
{
|
||||
"name": "confidential",
|
||||
"description": "Indicates if the epic is confidential",
|
||||
"description": "Indicates if the epic is confidential.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -4405,7 +4468,7 @@
|
|||
},
|
||||
{
|
||||
"name": "createdAt",
|
||||
"description": "Timestamp of when the epic was created",
|
||||
"description": "Timestamp of when the epic was created.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -4486,7 +4549,7 @@
|
|||
},
|
||||
{
|
||||
"name": "descendantCounts",
|
||||
"description": "Number of open and closed descendant epics and issues",
|
||||
"description": "Number of open and closed descendant epics and issues.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -4500,7 +4563,7 @@
|
|||
},
|
||||
{
|
||||
"name": "descendantWeightSum",
|
||||
"description": "Total weight of open and closed issues in the epic and its descendants",
|
||||
"description": "Total weight of open and closed issues in the epic and its descendants.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -4514,7 +4577,7 @@
|
|||
},
|
||||
{
|
||||
"name": "description",
|
||||
"description": "Description of the epic",
|
||||
"description": "Description of the epic.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -4585,7 +4648,7 @@
|
|||
},
|
||||
{
|
||||
"name": "downvotes",
|
||||
"description": "Number of downvotes the epic has received",
|
||||
"description": "Number of downvotes the epic has received.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -4603,7 +4666,7 @@
|
|||
},
|
||||
{
|
||||
"name": "dueDate",
|
||||
"description": "Due date of the epic",
|
||||
"description": "Due date of the epic.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -4617,7 +4680,7 @@
|
|||
},
|
||||
{
|
||||
"name": "dueDateFixed",
|
||||
"description": "Fixed due date of the epic",
|
||||
"description": "Fixed due date of the epic.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -4631,7 +4694,7 @@
|
|||
},
|
||||
{
|
||||
"name": "dueDateFromMilestones",
|
||||
"description": "Inherited due date of the epic from milestones",
|
||||
"description": "Inherited due date of the epic from milestones.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -4645,7 +4708,7 @@
|
|||
},
|
||||
{
|
||||
"name": "dueDateIsFixed",
|
||||
"description": "Indicates if the due date has been manually set",
|
||||
"description": "Indicates if the due date has been manually set.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -4659,7 +4722,7 @@
|
|||
},
|
||||
{
|
||||
"name": "group",
|
||||
"description": "Group to which the epic belongs",
|
||||
"description": "Group to which the epic belongs.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -4677,7 +4740,7 @@
|
|||
},
|
||||
{
|
||||
"name": "hasChildren",
|
||||
"description": "Indicates if the epic has children",
|
||||
"description": "Indicates if the epic has children.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -4695,7 +4758,7 @@
|
|||
},
|
||||
{
|
||||
"name": "hasIssues",
|
||||
"description": "Indicates if the epic has direct issues",
|
||||
"description": "Indicates if the epic has direct issues.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -4713,7 +4776,7 @@
|
|||
},
|
||||
{
|
||||
"name": "hasParent",
|
||||
"description": "Indicates if the epic has a parent epic",
|
||||
"description": "Indicates if the epic has a parent epic.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -4731,7 +4794,7 @@
|
|||
},
|
||||
{
|
||||
"name": "healthStatus",
|
||||
"description": "Current health status of the epic",
|
||||
"description": "Current health status of the epic.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -4745,7 +4808,7 @@
|
|||
},
|
||||
{
|
||||
"name": "id",
|
||||
"description": "ID of the epic",
|
||||
"description": "ID of the epic.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -4763,7 +4826,7 @@
|
|||
},
|
||||
{
|
||||
"name": "iid",
|
||||
"description": "Internal ID of the epic",
|
||||
"description": "Internal ID of the epic.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -4781,7 +4844,7 @@
|
|||
},
|
||||
{
|
||||
"name": "issues",
|
||||
"description": "A list of issues associated with the epic",
|
||||
"description": "A list of issues associated with the epic.",
|
||||
"args": [
|
||||
{
|
||||
"name": "after",
|
||||
|
@ -4834,7 +4897,7 @@
|
|||
},
|
||||
{
|
||||
"name": "labels",
|
||||
"description": "Labels assigned to the epic",
|
||||
"description": "Labels assigned to the epic.",
|
||||
"args": [
|
||||
{
|
||||
"name": "after",
|
||||
|
@ -4944,7 +5007,7 @@
|
|||
},
|
||||
{
|
||||
"name": "parent",
|
||||
"description": "Parent epic of the epic",
|
||||
"description": "Parent epic of the epic.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -4958,7 +5021,7 @@
|
|||
},
|
||||
{
|
||||
"name": "participants",
|
||||
"description": "List of participants for the epic",
|
||||
"description": "List of participants for the epic.",
|
||||
"args": [
|
||||
{
|
||||
"name": "after",
|
||||
|
@ -5011,11 +5074,11 @@
|
|||
},
|
||||
{
|
||||
"name": "reference",
|
||||
"description": "Internal reference of the epic. Returned in shortened format by default",
|
||||
"description": "Internal reference of the epic. Returned in shortened format by default.",
|
||||
"args": [
|
||||
{
|
||||
"name": "full",
|
||||
"description": "Indicates if the reference should be returned in full",
|
||||
"description": "Indicates if the reference should be returned in full.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Boolean",
|
||||
|
@ -5038,7 +5101,7 @@
|
|||
},
|
||||
{
|
||||
"name": "relationPath",
|
||||
"description": "URI path of the epic-issue relationship",
|
||||
"description": "URI path of the epic-issue relationship.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -5052,7 +5115,7 @@
|
|||
},
|
||||
{
|
||||
"name": "relativePosition",
|
||||
"description": "The relative position of the epic in the epic tree",
|
||||
"description": "The relative position of the epic in the epic tree.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -5066,7 +5129,7 @@
|
|||
},
|
||||
{
|
||||
"name": "startDate",
|
||||
"description": "Start date of the epic",
|
||||
"description": "Start date of the epic.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -5080,7 +5143,7 @@
|
|||
},
|
||||
{
|
||||
"name": "startDateFixed",
|
||||
"description": "Fixed start date of the epic",
|
||||
"description": "Fixed start date of the epic.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -5094,7 +5157,7 @@
|
|||
},
|
||||
{
|
||||
"name": "startDateFromMilestones",
|
||||
"description": "Inherited start date of the epic from milestones",
|
||||
"description": "Inherited start date of the epic from milestones.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -5108,7 +5171,7 @@
|
|||
},
|
||||
{
|
||||
"name": "startDateIsFixed",
|
||||
"description": "Indicates if the start date has been manually set",
|
||||
"description": "Indicates if the start date has been manually set.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -5122,7 +5185,7 @@
|
|||
},
|
||||
{
|
||||
"name": "state",
|
||||
"description": "State of the epic",
|
||||
"description": "State of the epic.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -5140,7 +5203,7 @@
|
|||
},
|
||||
{
|
||||
"name": "subscribed",
|
||||
"description": "Indicates the currently logged in user is subscribed to the epic",
|
||||
"description": "Indicates the currently logged in user is subscribed to the epic.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -5158,7 +5221,7 @@
|
|||
},
|
||||
{
|
||||
"name": "title",
|
||||
"description": "Title of the epic",
|
||||
"description": "Title of the epic.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -5172,7 +5235,7 @@
|
|||
},
|
||||
{
|
||||
"name": "updatedAt",
|
||||
"description": "Timestamp of when the epic was updated",
|
||||
"description": "Timestamp of when the epic was updated.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -5186,7 +5249,7 @@
|
|||
},
|
||||
{
|
||||
"name": "upvotes",
|
||||
"description": "Number of upvotes the epic has received",
|
||||
"description": "Number of upvotes the epic has received.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -5204,7 +5267,7 @@
|
|||
},
|
||||
{
|
||||
"name": "userDiscussionsCount",
|
||||
"description": "Number of user discussions in the epic",
|
||||
"description": "Number of user discussions in the epic.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -5222,7 +5285,7 @@
|
|||
},
|
||||
{
|
||||
"name": "userNotesCount",
|
||||
"description": "Number of user notes of the epic",
|
||||
"description": "Number of user notes of the epic.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -5272,7 +5335,7 @@
|
|||
},
|
||||
{
|
||||
"name": "webPath",
|
||||
"description": "Web path of the epic",
|
||||
"description": "Web path of the epic.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -5290,7 +5353,7 @@
|
|||
},
|
||||
{
|
||||
"name": "webUrl",
|
||||
"description": "Web URL of the epic",
|
||||
"description": "Web URL of the epic.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -23328,7 +23391,7 @@
|
|||
"fields": [
|
||||
{
|
||||
"name": "author",
|
||||
"description": "Author of the epic",
|
||||
"description": "Author of the epic.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -23346,7 +23409,7 @@
|
|||
},
|
||||
{
|
||||
"name": "awardEmoji",
|
||||
"description": "A list of award emojis associated with the epic",
|
||||
"description": "A list of award emojis associated with the epic.",
|
||||
"args": [
|
||||
{
|
||||
"name": "after",
|
||||
|
@ -23399,7 +23462,7 @@
|
|||
},
|
||||
{
|
||||
"name": "children",
|
||||
"description": "Children (sub-epics) of the epic",
|
||||
"description": "Children (sub-epics) of the epic.",
|
||||
"args": [
|
||||
{
|
||||
"name": "startDate",
|
||||
|
@ -23608,7 +23671,7 @@
|
|||
},
|
||||
{
|
||||
"name": "closedAt",
|
||||
"description": "Timestamp of when the epic was closed",
|
||||
"description": "Timestamp of when the epic was closed.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -23622,7 +23685,7 @@
|
|||
},
|
||||
{
|
||||
"name": "confidential",
|
||||
"description": "Indicates if the epic is confidential",
|
||||
"description": "Indicates if the epic is confidential.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -23636,7 +23699,7 @@
|
|||
},
|
||||
{
|
||||
"name": "createdAt",
|
||||
"description": "Timestamp of when the epic was created",
|
||||
"description": "Timestamp of when the epic was created.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -23717,7 +23780,7 @@
|
|||
},
|
||||
{
|
||||
"name": "descendantCounts",
|
||||
"description": "Number of open and closed descendant epics and issues",
|
||||
"description": "Number of open and closed descendant epics and issues.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -23731,7 +23794,7 @@
|
|||
},
|
||||
{
|
||||
"name": "descendantWeightSum",
|
||||
"description": "Total weight of open and closed issues in the epic and its descendants",
|
||||
"description": "Total weight of open and closed issues in the epic and its descendants.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -23745,7 +23808,7 @@
|
|||
},
|
||||
{
|
||||
"name": "description",
|
||||
"description": "Description of the epic",
|
||||
"description": "Description of the epic.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -23816,7 +23879,7 @@
|
|||
},
|
||||
{
|
||||
"name": "downvotes",
|
||||
"description": "Number of downvotes the epic has received",
|
||||
"description": "Number of downvotes the epic has received.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -23834,7 +23897,7 @@
|
|||
},
|
||||
{
|
||||
"name": "dueDate",
|
||||
"description": "Due date of the epic",
|
||||
"description": "Due date of the epic.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -23848,7 +23911,7 @@
|
|||
},
|
||||
{
|
||||
"name": "dueDateFixed",
|
||||
"description": "Fixed due date of the epic",
|
||||
"description": "Fixed due date of the epic.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -23862,7 +23925,7 @@
|
|||
},
|
||||
{
|
||||
"name": "dueDateFromMilestones",
|
||||
"description": "Inherited due date of the epic from milestones",
|
||||
"description": "Inherited due date of the epic from milestones.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -23876,7 +23939,7 @@
|
|||
},
|
||||
{
|
||||
"name": "dueDateIsFixed",
|
||||
"description": "Indicates if the due date has been manually set",
|
||||
"description": "Indicates if the due date has been manually set.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -23890,7 +23953,7 @@
|
|||
},
|
||||
{
|
||||
"name": "group",
|
||||
"description": "Group to which the epic belongs",
|
||||
"description": "Group to which the epic belongs.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -23908,7 +23971,7 @@
|
|||
},
|
||||
{
|
||||
"name": "hasChildren",
|
||||
"description": "Indicates if the epic has children",
|
||||
"description": "Indicates if the epic has children.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -23926,7 +23989,7 @@
|
|||
},
|
||||
{
|
||||
"name": "hasIssues",
|
||||
"description": "Indicates if the epic has direct issues",
|
||||
"description": "Indicates if the epic has direct issues.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -23944,7 +24007,7 @@
|
|||
},
|
||||
{
|
||||
"name": "hasParent",
|
||||
"description": "Indicates if the epic has a parent epic",
|
||||
"description": "Indicates if the epic has a parent epic.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -23962,7 +24025,7 @@
|
|||
},
|
||||
{
|
||||
"name": "healthStatus",
|
||||
"description": "Current health status of the epic",
|
||||
"description": "Current health status of the epic.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -23976,7 +24039,7 @@
|
|||
},
|
||||
{
|
||||
"name": "id",
|
||||
"description": "ID of the epic",
|
||||
"description": "ID of the epic.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -23994,7 +24057,7 @@
|
|||
},
|
||||
{
|
||||
"name": "iid",
|
||||
"description": "Internal ID of the epic",
|
||||
"description": "Internal ID of the epic.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -24012,7 +24075,7 @@
|
|||
},
|
||||
{
|
||||
"name": "issues",
|
||||
"description": "A list of issues associated with the epic",
|
||||
"description": "A list of issues associated with the epic.",
|
||||
"args": [
|
||||
{
|
||||
"name": "after",
|
||||
|
@ -24065,7 +24128,7 @@
|
|||
},
|
||||
{
|
||||
"name": "labels",
|
||||
"description": "Labels assigned to the epic",
|
||||
"description": "Labels assigned to the epic.",
|
||||
"args": [
|
||||
{
|
||||
"name": "after",
|
||||
|
@ -24175,7 +24238,7 @@
|
|||
},
|
||||
{
|
||||
"name": "parent",
|
||||
"description": "Parent epic of the epic",
|
||||
"description": "Parent epic of the epic.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -24189,7 +24252,7 @@
|
|||
},
|
||||
{
|
||||
"name": "participants",
|
||||
"description": "List of participants for the epic",
|
||||
"description": "List of participants for the epic.",
|
||||
"args": [
|
||||
{
|
||||
"name": "after",
|
||||
|
@ -24242,11 +24305,11 @@
|
|||
},
|
||||
{
|
||||
"name": "reference",
|
||||
"description": "Internal reference of the epic. Returned in shortened format by default",
|
||||
"description": "Internal reference of the epic. Returned in shortened format by default.",
|
||||
"args": [
|
||||
{
|
||||
"name": "full",
|
||||
"description": "Indicates if the reference should be returned in full",
|
||||
"description": "Indicates if the reference should be returned in full.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Boolean",
|
||||
|
@ -24269,7 +24332,7 @@
|
|||
},
|
||||
{
|
||||
"name": "relationPath",
|
||||
"description": "URI path of the epic-issue relationship",
|
||||
"description": "URI path of the epic-issue relationship.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -24283,7 +24346,7 @@
|
|||
},
|
||||
{
|
||||
"name": "relativePosition",
|
||||
"description": "The relative position of the epic in the epic tree",
|
||||
"description": "The relative position of the epic in the epic tree.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -24297,7 +24360,7 @@
|
|||
},
|
||||
{
|
||||
"name": "startDate",
|
||||
"description": "Start date of the epic",
|
||||
"description": "Start date of the epic.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -24311,7 +24374,7 @@
|
|||
},
|
||||
{
|
||||
"name": "startDateFixed",
|
||||
"description": "Fixed start date of the epic",
|
||||
"description": "Fixed start date of the epic.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -24325,7 +24388,7 @@
|
|||
},
|
||||
{
|
||||
"name": "startDateFromMilestones",
|
||||
"description": "Inherited start date of the epic from milestones",
|
||||
"description": "Inherited start date of the epic from milestones.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -24339,7 +24402,7 @@
|
|||
},
|
||||
{
|
||||
"name": "startDateIsFixed",
|
||||
"description": "Indicates if the start date has been manually set",
|
||||
"description": "Indicates if the start date has been manually set.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -24353,7 +24416,7 @@
|
|||
},
|
||||
{
|
||||
"name": "state",
|
||||
"description": "State of the epic",
|
||||
"description": "State of the epic.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -24371,7 +24434,7 @@
|
|||
},
|
||||
{
|
||||
"name": "subscribed",
|
||||
"description": "Indicates the currently logged in user is subscribed to the epic",
|
||||
"description": "Indicates the currently logged in user is subscribed to the epic.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -24389,7 +24452,7 @@
|
|||
},
|
||||
{
|
||||
"name": "title",
|
||||
"description": "Title of the epic",
|
||||
"description": "Title of the epic.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -24403,7 +24466,7 @@
|
|||
},
|
||||
{
|
||||
"name": "updatedAt",
|
||||
"description": "Timestamp of when the epic was updated",
|
||||
"description": "Timestamp of when the epic was updated.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -24417,7 +24480,7 @@
|
|||
},
|
||||
{
|
||||
"name": "upvotes",
|
||||
"description": "Number of upvotes the epic has received",
|
||||
"description": "Number of upvotes the epic has received.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -24435,7 +24498,7 @@
|
|||
},
|
||||
{
|
||||
"name": "userDiscussionsCount",
|
||||
"description": "Number of user discussions in the epic",
|
||||
"description": "Number of user discussions in the epic.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -24453,7 +24516,7 @@
|
|||
},
|
||||
{
|
||||
"name": "userNotesCount",
|
||||
"description": "Number of user notes of the epic",
|
||||
"description": "Number of user notes of the epic.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -24489,7 +24552,7 @@
|
|||
},
|
||||
{
|
||||
"name": "webPath",
|
||||
"description": "Web path of the epic",
|
||||
"description": "Web path of the epic.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -24507,7 +24570,7 @@
|
|||
},
|
||||
{
|
||||
"name": "webUrl",
|
||||
"description": "Web URL of the epic",
|
||||
"description": "Web URL of the epic.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -43076,7 +43139,7 @@
|
|||
},
|
||||
{
|
||||
"name": "merged",
|
||||
"description": null,
|
||||
"description": "Merge Request has been merged",
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
|
@ -53968,6 +54031,41 @@
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "alertManagementPayloadFields",
|
||||
"description": "Extract alert fields from payload for custom mapping",
|
||||
"args": [
|
||||
{
|
||||
"name": "payloadExample",
|
||||
"description": "Sample payload for extracting alert fields for custom mappings.",
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "AlertManagementPayloadAlertField",
|
||||
"ofType": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "allowMergeOnSkippedPipeline",
|
||||
"description": "If `only_allow_merge_if_pipeline_succeeds` is true, indicates if merge requests of the project can also be merged with skipped jobs",
|
||||
|
|
|
@ -132,6 +132,16 @@ An endpoint and credentials used to accept alerts for a project.
|
|||
| `type` | AlertManagementIntegrationType! | Type of integration. |
|
||||
| `url` | String | Endpoint which accepts alert notifications. |
|
||||
|
||||
### AlertManagementPayloadAlertField
|
||||
|
||||
Parsed field from an alert used for custom mappings.
|
||||
|
||||
| Field | Type | Description |
|
||||
| ----- | ---- | ----------- |
|
||||
| `label` | String | Human-readable label of the payload path. |
|
||||
| `path` | String! => Array | Path to value inside payload JSON. |
|
||||
| `type` | AlertManagementPayloadAlertFieldType | Type of the parsed value. |
|
||||
|
||||
### AlertManagementPrometheusIntegration
|
||||
|
||||
An endpoint and credentials used to accept Prometheus alerts for a project.
|
||||
|
@ -262,52 +272,52 @@ Represents an epic on an issue board.
|
|||
|
||||
| Field | Type | Description |
|
||||
| ----- | ---- | ----------- |
|
||||
| `author` | User! | Author of the epic |
|
||||
| `awardEmoji` | AwardEmojiConnection | A list of award emojis associated with the epic |
|
||||
| `children` | EpicConnection | Children (sub-epics) of the epic |
|
||||
| `closedAt` | Time | Timestamp of when the epic was closed |
|
||||
| `confidential` | Boolean | Indicates if the epic is confidential |
|
||||
| `createdAt` | Time | Timestamp of when the epic was created |
|
||||
| `author` | User! | Author of the epic. |
|
||||
| `awardEmoji` | AwardEmojiConnection | A list of award emojis associated with the epic. |
|
||||
| `children` | EpicConnection | Children (sub-epics) of the epic. |
|
||||
| `closedAt` | Time | Timestamp of when the epic was closed. |
|
||||
| `confidential` | Boolean | Indicates if the epic is confidential. |
|
||||
| `createdAt` | Time | Timestamp of when the epic was created. |
|
||||
| `currentUserTodos` | TodoConnection! | Todos for the current user. |
|
||||
| `descendantCounts` | EpicDescendantCount | Number of open and closed descendant epics and issues |
|
||||
| `descendantWeightSum` | EpicDescendantWeights | Total weight of open and closed issues in the epic and its descendants |
|
||||
| `description` | String | Description of the epic |
|
||||
| `descendantCounts` | EpicDescendantCount | Number of open and closed descendant epics and issues. |
|
||||
| `descendantWeightSum` | EpicDescendantWeights | Total weight of open and closed issues in the epic and its descendants. |
|
||||
| `description` | String | Description of the epic. |
|
||||
| `discussions` | DiscussionConnection! | All discussions on this noteable |
|
||||
| `downvotes` | Int! | Number of downvotes the epic has received |
|
||||
| `dueDate` | Time | Due date of the epic |
|
||||
| `dueDateFixed` | Time | Fixed due date of the epic |
|
||||
| `dueDateFromMilestones` | Time | Inherited due date of the epic from milestones |
|
||||
| `dueDateIsFixed` | Boolean | Indicates if the due date has been manually set |
|
||||
| `group` | Group! | Group to which the epic belongs |
|
||||
| `hasChildren` | Boolean! | Indicates if the epic has children |
|
||||
| `hasIssues` | Boolean! | Indicates if the epic has direct issues |
|
||||
| `hasParent` | Boolean! | Indicates if the epic has a parent epic |
|
||||
| `healthStatus` | EpicHealthStatus | Current health status of the epic |
|
||||
| `id` | ID! | ID of the epic |
|
||||
| `iid` | ID! | Internal ID of the epic |
|
||||
| `issues` | EpicIssueConnection | A list of issues associated with the epic |
|
||||
| `labels` | LabelConnection | Labels assigned to the epic |
|
||||
| `downvotes` | Int! | Number of downvotes the epic has received. |
|
||||
| `dueDate` | Time | Due date of the epic. |
|
||||
| `dueDateFixed` | Time | Fixed due date of the epic. |
|
||||
| `dueDateFromMilestones` | Time | Inherited due date of the epic from milestones. |
|
||||
| `dueDateIsFixed` | Boolean | Indicates if the due date has been manually set. |
|
||||
| `group` | Group! | Group to which the epic belongs. |
|
||||
| `hasChildren` | Boolean! | Indicates if the epic has children. |
|
||||
| `hasIssues` | Boolean! | Indicates if the epic has direct issues. |
|
||||
| `hasParent` | Boolean! | Indicates if the epic has a parent epic. |
|
||||
| `healthStatus` | EpicHealthStatus | Current health status of the epic. |
|
||||
| `id` | ID! | ID of the epic. |
|
||||
| `iid` | ID! | Internal ID of the epic. |
|
||||
| `issues` | EpicIssueConnection | A list of issues associated with the epic. |
|
||||
| `labels` | LabelConnection | Labels assigned to the epic. |
|
||||
| `notes` | NoteConnection! | All notes on this noteable |
|
||||
| `parent` | Epic | Parent epic of the epic |
|
||||
| `participants` | UserConnection | List of participants for the epic |
|
||||
| `reference` | String! | Internal reference of the epic. Returned in shortened format by default |
|
||||
| `relationPath` | String | URI path of the epic-issue relationship |
|
||||
| `relativePosition` | Int | The relative position of the epic in the epic tree |
|
||||
| `startDate` | Time | Start date of the epic |
|
||||
| `startDateFixed` | Time | Fixed start date of the epic |
|
||||
| `startDateFromMilestones` | Time | Inherited start date of the epic from milestones |
|
||||
| `startDateIsFixed` | Boolean | Indicates if the start date has been manually set |
|
||||
| `state` | EpicState! | State of the epic |
|
||||
| `subscribed` | Boolean! | Indicates the currently logged in user is subscribed to the epic |
|
||||
| `title` | String | Title of the epic |
|
||||
| `updatedAt` | Time | Timestamp of when the epic was updated |
|
||||
| `upvotes` | Int! | Number of upvotes the epic has received |
|
||||
| `userDiscussionsCount` | Int! | Number of user discussions in the epic |
|
||||
| `userNotesCount` | Int! | Number of user notes of the epic |
|
||||
| `parent` | Epic | Parent epic of the epic. |
|
||||
| `participants` | UserConnection | List of participants for the epic. |
|
||||
| `reference` | String! | Internal reference of the epic. Returned in shortened format by default. |
|
||||
| `relationPath` | String | URI path of the epic-issue relationship. |
|
||||
| `relativePosition` | Int | The relative position of the epic in the epic tree. |
|
||||
| `startDate` | Time | Start date of the epic. |
|
||||
| `startDateFixed` | Time | Fixed start date of the epic. |
|
||||
| `startDateFromMilestones` | Time | Inherited start date of the epic from milestones. |
|
||||
| `startDateIsFixed` | Boolean | Indicates if the start date has been manually set. |
|
||||
| `state` | EpicState! | State of the epic. |
|
||||
| `subscribed` | Boolean! | Indicates the currently logged in user is subscribed to the epic. |
|
||||
| `title` | String | Title of the epic. |
|
||||
| `updatedAt` | Time | Timestamp of when the epic was updated. |
|
||||
| `upvotes` | Int! | Number of upvotes the epic has received. |
|
||||
| `userDiscussionsCount` | Int! | Number of user discussions in the epic. |
|
||||
| `userNotesCount` | Int! | Number of user notes of the epic. |
|
||||
| `userPermissions` | EpicPermissions! | Permissions for the current user on the resource |
|
||||
| `userPreferences` | BoardEpicUserPreferences | User preferences for the epic on the issue board |
|
||||
| `webPath` | String! | Web path of the epic |
|
||||
| `webUrl` | String! | Web URL of the epic |
|
||||
| `webPath` | String! | Web path of the epic. |
|
||||
| `webUrl` | String! | Web URL of the epic. |
|
||||
|
||||
### BoardEpicUserPreferences
|
||||
|
||||
|
@ -1384,51 +1394,51 @@ Represents an epic.
|
|||
|
||||
| Field | Type | Description |
|
||||
| ----- | ---- | ----------- |
|
||||
| `author` | User! | Author of the epic |
|
||||
| `awardEmoji` | AwardEmojiConnection | A list of award emojis associated with the epic |
|
||||
| `children` | EpicConnection | Children (sub-epics) of the epic |
|
||||
| `closedAt` | Time | Timestamp of when the epic was closed |
|
||||
| `confidential` | Boolean | Indicates if the epic is confidential |
|
||||
| `createdAt` | Time | Timestamp of when the epic was created |
|
||||
| `author` | User! | Author of the epic. |
|
||||
| `awardEmoji` | AwardEmojiConnection | A list of award emojis associated with the epic. |
|
||||
| `children` | EpicConnection | Children (sub-epics) of the epic. |
|
||||
| `closedAt` | Time | Timestamp of when the epic was closed. |
|
||||
| `confidential` | Boolean | Indicates if the epic is confidential. |
|
||||
| `createdAt` | Time | Timestamp of when the epic was created. |
|
||||
| `currentUserTodos` | TodoConnection! | Todos for the current user. |
|
||||
| `descendantCounts` | EpicDescendantCount | Number of open and closed descendant epics and issues |
|
||||
| `descendantWeightSum` | EpicDescendantWeights | Total weight of open and closed issues in the epic and its descendants |
|
||||
| `description` | String | Description of the epic |
|
||||
| `descendantCounts` | EpicDescendantCount | Number of open and closed descendant epics and issues. |
|
||||
| `descendantWeightSum` | EpicDescendantWeights | Total weight of open and closed issues in the epic and its descendants. |
|
||||
| `description` | String | Description of the epic. |
|
||||
| `discussions` | DiscussionConnection! | All discussions on this noteable |
|
||||
| `downvotes` | Int! | Number of downvotes the epic has received |
|
||||
| `dueDate` | Time | Due date of the epic |
|
||||
| `dueDateFixed` | Time | Fixed due date of the epic |
|
||||
| `dueDateFromMilestones` | Time | Inherited due date of the epic from milestones |
|
||||
| `dueDateIsFixed` | Boolean | Indicates if the due date has been manually set |
|
||||
| `group` | Group! | Group to which the epic belongs |
|
||||
| `hasChildren` | Boolean! | Indicates if the epic has children |
|
||||
| `hasIssues` | Boolean! | Indicates if the epic has direct issues |
|
||||
| `hasParent` | Boolean! | Indicates if the epic has a parent epic |
|
||||
| `healthStatus` | EpicHealthStatus | Current health status of the epic |
|
||||
| `id` | ID! | ID of the epic |
|
||||
| `iid` | ID! | Internal ID of the epic |
|
||||
| `issues` | EpicIssueConnection | A list of issues associated with the epic |
|
||||
| `labels` | LabelConnection | Labels assigned to the epic |
|
||||
| `downvotes` | Int! | Number of downvotes the epic has received. |
|
||||
| `dueDate` | Time | Due date of the epic. |
|
||||
| `dueDateFixed` | Time | Fixed due date of the epic. |
|
||||
| `dueDateFromMilestones` | Time | Inherited due date of the epic from milestones. |
|
||||
| `dueDateIsFixed` | Boolean | Indicates if the due date has been manually set. |
|
||||
| `group` | Group! | Group to which the epic belongs. |
|
||||
| `hasChildren` | Boolean! | Indicates if the epic has children. |
|
||||
| `hasIssues` | Boolean! | Indicates if the epic has direct issues. |
|
||||
| `hasParent` | Boolean! | Indicates if the epic has a parent epic. |
|
||||
| `healthStatus` | EpicHealthStatus | Current health status of the epic. |
|
||||
| `id` | ID! | ID of the epic. |
|
||||
| `iid` | ID! | Internal ID of the epic. |
|
||||
| `issues` | EpicIssueConnection | A list of issues associated with the epic. |
|
||||
| `labels` | LabelConnection | Labels assigned to the epic. |
|
||||
| `notes` | NoteConnection! | All notes on this noteable |
|
||||
| `parent` | Epic | Parent epic of the epic |
|
||||
| `participants` | UserConnection | List of participants for the epic |
|
||||
| `reference` | String! | Internal reference of the epic. Returned in shortened format by default |
|
||||
| `relationPath` | String | URI path of the epic-issue relationship |
|
||||
| `relativePosition` | Int | The relative position of the epic in the epic tree |
|
||||
| `startDate` | Time | Start date of the epic |
|
||||
| `startDateFixed` | Time | Fixed start date of the epic |
|
||||
| `startDateFromMilestones` | Time | Inherited start date of the epic from milestones |
|
||||
| `startDateIsFixed` | Boolean | Indicates if the start date has been manually set |
|
||||
| `state` | EpicState! | State of the epic |
|
||||
| `subscribed` | Boolean! | Indicates the currently logged in user is subscribed to the epic |
|
||||
| `title` | String | Title of the epic |
|
||||
| `updatedAt` | Time | Timestamp of when the epic was updated |
|
||||
| `upvotes` | Int! | Number of upvotes the epic has received |
|
||||
| `userDiscussionsCount` | Int! | Number of user discussions in the epic |
|
||||
| `userNotesCount` | Int! | Number of user notes of the epic |
|
||||
| `parent` | Epic | Parent epic of the epic. |
|
||||
| `participants` | UserConnection | List of participants for the epic. |
|
||||
| `reference` | String! | Internal reference of the epic. Returned in shortened format by default. |
|
||||
| `relationPath` | String | URI path of the epic-issue relationship. |
|
||||
| `relativePosition` | Int | The relative position of the epic in the epic tree. |
|
||||
| `startDate` | Time | Start date of the epic. |
|
||||
| `startDateFixed` | Time | Fixed start date of the epic. |
|
||||
| `startDateFromMilestones` | Time | Inherited start date of the epic from milestones. |
|
||||
| `startDateIsFixed` | Boolean | Indicates if the start date has been manually set. |
|
||||
| `state` | EpicState! | State of the epic. |
|
||||
| `subscribed` | Boolean! | Indicates the currently logged in user is subscribed to the epic. |
|
||||
| `title` | String | Title of the epic. |
|
||||
| `updatedAt` | Time | Timestamp of when the epic was updated. |
|
||||
| `upvotes` | Int! | Number of upvotes the epic has received. |
|
||||
| `userDiscussionsCount` | Int! | Number of user discussions in the epic. |
|
||||
| `userNotesCount` | Int! | Number of user notes of the epic. |
|
||||
| `userPermissions` | EpicPermissions! | Permissions for the current user on the resource |
|
||||
| `webPath` | String! | Web path of the epic |
|
||||
| `webUrl` | String! | Web URL of the epic |
|
||||
| `webPath` | String! | Web path of the epic. |
|
||||
| `webUrl` | String! | Web URL of the epic. |
|
||||
|
||||
### EpicAddIssuePayload
|
||||
|
||||
|
@ -2767,6 +2777,7 @@ Autogenerated return type of PipelineRetry.
|
|||
| `alertManagementAlertStatusCounts` | AlertManagementAlertStatusCountsType | Counts of alerts by status for the project |
|
||||
| `alertManagementAlerts` | AlertManagementAlertConnection | Alert Management alerts of the project |
|
||||
| `alertManagementIntegrations` | AlertManagementIntegrationConnection | Integrations which can receive alerts for the project |
|
||||
| `alertManagementPayloadFields` | AlertManagementPayloadAlertField! => Array | Extract alert fields from payload for custom mapping |
|
||||
| `allowMergeOnSkippedPipeline` | Boolean | If `only_allow_merge_if_pipeline_succeeds` is true, indicates if merge requests of the project can also be merged with skipped jobs |
|
||||
| `archived` | Boolean | Indicates the archived status of the project |
|
||||
| `autocloseReferencedIssues` | Boolean | Indicates if issues referenced by merge requests and commits within the default branch are closed automatically |
|
||||
|
@ -4991,7 +5002,7 @@ State of a GitLab merge request.
|
|||
| `all` | |
|
||||
| `closed` | |
|
||||
| `locked` | |
|
||||
| `merged` | |
|
||||
| `merged` | Merge Request has been merged |
|
||||
| `opened` | |
|
||||
|
||||
### MilestoneStateEnum
|
||||
|
|
|
@ -34,6 +34,12 @@ but only for updating the declaration of the columns. We can then validate it at
|
|||
`VALIDATE CONSTRAINT`, which requires only a `SHARE UPDATE EXCLUSIVE LOCK` (only conflicts with other
|
||||
validations and index creation while it allows reads and writes).
|
||||
|
||||
### Exceptions
|
||||
|
||||
Text columns used by `attr_encrypted` are not required to have a limit, becuase the length of the
|
||||
text after encryption may be longer than the text itself. Instead, you can use an Active Record
|
||||
length validation on the attribute.
|
||||
|
||||
## Create a new table with text columns
|
||||
|
||||
When adding a new table, the limits for all text columns should be added in the same migration as
|
||||
|
|
|
@ -163,13 +163,7 @@ Once a lifetime for personal access tokens is set, GitLab will:
|
|||
allowed lifetime. Three hours is given to allow administrators to change the allowed lifetime,
|
||||
or remove it, before revocation takes place.
|
||||
|
||||
## Enforcement of SSH key expiration **(ULTIMATE ONLY)**
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/276221) in GitLab Ultimate 13.9.
|
||||
> - It is deployed behind a feature flag, disabled by default.
|
||||
> - It is disabled on GitLab.com.
|
||||
> - It is not recommended for production use.
|
||||
> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-enforcement-of-ssh-key-expiration-feature). **(CORE ONLY)**
|
||||
## Enforcement of SSH key expiration **(ULTIMATE SELF)**
|
||||
|
||||
GitLab administrators can choose to enforce the expiration of SSH keys after their expiration dates.
|
||||
If you enable this feature, this disables all _expired_ SSH keys.
|
||||
|
@ -180,23 +174,6 @@ To do this:
|
|||
1. Expand the **Account and limit** section.
|
||||
1. Select the **Enforce SSH key expiration** checkbox.
|
||||
|
||||
### Enable or disable enforcement of SSH key expiration Feature **(CORE ONLY)**
|
||||
|
||||
Enforcement of SSH key expiry is deployed behind a feature flag and is **disabled by default**.
|
||||
[GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md) can enable it for your instance from the [rails console](../../../administration/feature_flags.md#start-the-gitlab-rails-console).
|
||||
|
||||
To enable it:
|
||||
|
||||
```ruby
|
||||
Feature.enable(:ff_enforce_ssh_key_expiration)
|
||||
```
|
||||
|
||||
To disable it:
|
||||
|
||||
```ruby
|
||||
Feature.disable(:ff_enforce_ssh_key_expiration)
|
||||
```
|
||||
|
||||
## Optional enforcement of Personal Access Token expiry **(ULTIMATE SELF)**
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/214723) in GitLab Ultimate 13.1.
|
||||
|
|
|
@ -10,7 +10,7 @@ module BulkImports
|
|||
def run(context)
|
||||
raise MarkedAsFailedError if marked_as_failed?(context)
|
||||
|
||||
info(context, message: 'Pipeline started', pipeline_class: pipeline)
|
||||
info(context, message: 'Pipeline started')
|
||||
|
||||
extracted_data = extracted_data_from(context)
|
||||
|
||||
|
@ -27,6 +27,8 @@ module BulkImports
|
|||
end
|
||||
|
||||
after_run(context, extracted_data) if respond_to?(:after_run)
|
||||
|
||||
info(context, message: 'Pipeline finished')
|
||||
rescue MarkedAsFailedError
|
||||
log_skip(context)
|
||||
end
|
||||
|
@ -36,7 +38,7 @@ module BulkImports
|
|||
def run_pipeline_step(step, class_name, context)
|
||||
raise MarkedAsFailedError if marked_as_failed?(context)
|
||||
|
||||
info(context, step => class_name)
|
||||
info(context, pipeline_step: step, step_class: class_name)
|
||||
|
||||
yield
|
||||
rescue MarkedAsFailedError
|
||||
|
@ -100,7 +102,8 @@ module BulkImports
|
|||
def log_base_params(context)
|
||||
{
|
||||
bulk_import_entity_id: context.entity.id,
|
||||
bulk_import_entity_type: context.entity.source_type
|
||||
bulk_import_entity_type: context.entity.source_type,
|
||||
pipeline_class: pipeline
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ module Gitlab
|
|||
base_payload = parse_job(job)
|
||||
ActiveRecord::LogSubscriber.reset_runtime
|
||||
|
||||
Sidekiq.logger.info log_job_start(base_payload)
|
||||
Sidekiq.logger.info log_job_start(job, base_payload)
|
||||
|
||||
yield
|
||||
|
||||
|
@ -40,13 +40,15 @@ module Gitlab
|
|||
output_payload.merge!(job.slice(*::Gitlab::Metrics::Subscribers::ActiveRecord::DB_COUNTERS))
|
||||
end
|
||||
|
||||
def log_job_start(payload)
|
||||
def log_job_start(job, payload)
|
||||
payload['message'] = "#{base_message(payload)}: start"
|
||||
payload['job_status'] = 'start'
|
||||
|
||||
scheduling_latency_s = ::Gitlab::InstrumentationHelper.queue_duration_for_job(payload)
|
||||
payload['scheduling_latency_s'] = scheduling_latency_s if scheduling_latency_s
|
||||
|
||||
payload['job_size_bytes'] = Sidekiq.dump_json(job).bytesize
|
||||
|
||||
payload
|
||||
end
|
||||
|
||||
|
|
|
@ -608,3 +608,9 @@
|
|||
redis_slot: ci_templates
|
||||
aggregation: weekly
|
||||
feature_flag: usage_data_track_ci_templates_unique_projects
|
||||
# Pipeline Authoring
|
||||
- name: o_pipeline_authoring_unique_users_committing_ciconfigfile
|
||||
category: pipeline_authoring
|
||||
redis_slot: pipeline_authoring
|
||||
aggregation: weekly
|
||||
feature_flag: usage_data_unique_users_committing_ciconfigfile
|
||||
|
|
|
@ -5694,7 +5694,7 @@ msgstr ""
|
|||
msgid "Choose the top-level group for your repository imports."
|
||||
msgstr ""
|
||||
|
||||
msgid "Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions."
|
||||
msgid "Choose visibility level, enable/disable project features and their permissions, disable email notifications, and show default award emoji."
|
||||
msgstr ""
|
||||
|
||||
msgid "Choose what content you want to see on a group’s overview page."
|
||||
|
@ -7343,6 +7343,30 @@ msgstr ""
|
|||
msgid "CompareBranches|There isn't anything to compare."
|
||||
msgstr ""
|
||||
|
||||
msgid "CompareRevisions|Branches"
|
||||
msgstr ""
|
||||
|
||||
msgid "CompareRevisions|Compare"
|
||||
msgstr ""
|
||||
|
||||
msgid "CompareRevisions|Create merge request"
|
||||
msgstr ""
|
||||
|
||||
msgid "CompareRevisions|Filter by Git revision"
|
||||
msgstr ""
|
||||
|
||||
msgid "CompareRevisions|Select branch/tag"
|
||||
msgstr ""
|
||||
|
||||
msgid "CompareRevisions|Tags"
|
||||
msgstr ""
|
||||
|
||||
msgid "CompareRevisions|There was an error while updating the branch/tag list. Please try again."
|
||||
msgstr ""
|
||||
|
||||
msgid "CompareRevisions|View open merge request"
|
||||
msgstr ""
|
||||
|
||||
msgid "Complete"
|
||||
msgstr ""
|
||||
|
||||
|
@ -22799,10 +22823,7 @@ msgstr ""
|
|||
msgid "ProjectSettings|Allow editing commit messages"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectSettings|Allow users to make copies of your repository to a new project"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectSettings|Allow users to request access"
|
||||
msgid "ProjectSettings|Always show thumbs-up and thumbs-down award emoji buttons on issues, merge requests, and snippets."
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectSettings|Analytics"
|
||||
|
@ -22814,7 +22835,7 @@ msgstr ""
|
|||
msgid "ProjectSettings|Badges"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectSettings|Build, test, and deploy your changes"
|
||||
msgid "ProjectSettings|Build, test, and deploy your changes."
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectSettings|Checkbox is visible and selected by default."
|
||||
|
@ -22829,6 +22850,9 @@ msgstr ""
|
|||
msgid "ProjectSettings|Choose your merge method, merge options, merge checks, merge suggestions, and set up a default description template for merge requests."
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectSettings|Commit authors can edit commit messages on unprotected branches."
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectSettings|Contact an admin to change this setting."
|
||||
msgstr ""
|
||||
|
||||
|
@ -22856,7 +22880,7 @@ msgstr ""
|
|||
msgid "ProjectSettings|Encourage"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectSettings|Environments, logs, cluster management, and more"
|
||||
msgid "ProjectSettings|Environments, logs, cluster management, and more."
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectSettings|Every merge creates a merge commit"
|
||||
|
@ -22865,7 +22889,7 @@ msgstr ""
|
|||
msgid "ProjectSettings|Every project can have its own space to store its Docker images"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectSettings|Every project can have its own space to store its packages"
|
||||
msgid "ProjectSettings|Every project can have its own space to store its packages."
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectSettings|Everyone"
|
||||
|
@ -22904,13 +22928,13 @@ msgstr ""
|
|||
msgid "ProjectSettings|Issues"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectSettings|LFS objects from this repository are still available to forks. %{linkStart}How do I remove them?%{linkEnd}"
|
||||
msgid "ProjectSettings|LFS objects from this repository are available to forks. %{linkStart}How do I remove them?%{linkEnd}"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectSettings|Lightweight issue tracking system for this project"
|
||||
msgid "ProjectSettings|Lightweight issue tracking system."
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectSettings|Manages large files such as audio, video, and graphics files"
|
||||
msgid "ProjectSettings|Manages large files such as audio, video, and graphics files."
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectSettings|Merge checks"
|
||||
|
@ -22946,13 +22970,16 @@ msgstr ""
|
|||
msgid "ProjectSettings|Operations"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectSettings|Override user notification preferences for all project members."
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectSettings|Packages"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectSettings|Pages"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectSettings|Pages for project documentation"
|
||||
msgid "ProjectSettings|Pages for project documentation."
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectSettings|Pipelines"
|
||||
|
@ -22982,7 +23009,7 @@ msgstr ""
|
|||
msgid "ProjectSettings|Requirements"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectSettings|Requirements management system for this project"
|
||||
msgid "ProjectSettings|Requirements management system."
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectSettings|Security & Compliance"
|
||||
|
@ -22994,7 +23021,7 @@ msgstr ""
|
|||
msgid "ProjectSettings|Set the default behavior and availability of this option in merge requests. Changes made are also applied to existing merge requests."
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectSettings|Share code pastes with others out of Git repository"
|
||||
msgid "ProjectSettings|Share code with others outside the project."
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectSettings|Show default award emojis"
|
||||
|
@ -23018,7 +23045,7 @@ msgstr ""
|
|||
msgid "ProjectSettings|Squashing is never performed and the checkbox is hidden."
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectSettings|Submit changes to be merged upstream"
|
||||
msgid "ProjectSettings|Submit changes to be merged upstream."
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectSettings|The commit message used to apply merge request suggestions"
|
||||
|
@ -23042,30 +23069,36 @@ msgstr ""
|
|||
msgid "ProjectSettings|This setting will be applied to all projects unless overridden by an admin."
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectSettings|This setting will override user notification preferences for all project members."
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectSettings|This will dictate the commit history when you merge a merge request"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectSettings|Transfer project"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectSettings|Users can copy the repository to a new project."
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectSettings|Users can only push commits to this repository that were committed with one of their own verified emails."
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectSettings|View and edit files in this project"
|
||||
msgid "ProjectSettings|Users can request access"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectSettings|View and edit files in this project. Non-project members will only have read access"
|
||||
msgid "ProjectSettings|View and edit files in this project."
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectSettings|View project analytics"
|
||||
msgid "ProjectSettings|View and edit files in this project. Non-project members will only have read access."
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectSettings|View project analytics."
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectSettings|Visibility options for this fork are limited by the current visibility of the source project."
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectSettings|Visualize the project's performance metrics."
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectSettings|What are badges?"
|
||||
msgstr ""
|
||||
|
||||
|
@ -23075,19 +23108,10 @@ msgstr ""
|
|||
msgid "ProjectSettings|When conflicts arise the user is given the option to rebase"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectSettings|When enabled, commit authors will be able to edit commit messages on unprotected branches."
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectSettings|When enabled, issues, merge requests, and snippets will always show thumbs-up and thumbs-down award emoji buttons."
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectSettings|Wiki"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectSettings|With GitLab Pages you can host your static websites on GitLab"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectSettings|With Metrics Dashboard you can visualize this project performance metrics"
|
||||
msgid "ProjectSettings|With GitLab Pages you can host your static websites on GitLab."
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectTemplates|.NET Core"
|
||||
|
|
|
@ -39,27 +39,22 @@ module QA
|
|||
private
|
||||
|
||||
def set_up_jira_integration
|
||||
# Retry is required because allow_local_requests_from_web_hooks_and_services
|
||||
# takes some time to get enabled.
|
||||
# Bug issue: https://gitlab.com/gitlab-org/gitlab/-/issues/217010
|
||||
QA::Support::Retrier.retry_on_exception(max_attempts: 5, sleep_interval: 3) do
|
||||
Runtime::ApplicationSettings.set_application_settings(allow_local_requests_from_web_hooks_and_services: true)
|
||||
Runtime::ApplicationSettings.set_application_settings(allow_local_requests_from_web_hooks_and_services: true)
|
||||
|
||||
page.visit Runtime::Scenario.gitlab_address
|
||||
Flow::Login.sign_in_unless_signed_in
|
||||
page.visit Runtime::Scenario.gitlab_address
|
||||
Flow::Login.sign_in_unless_signed_in
|
||||
|
||||
project.visit!
|
||||
project.visit!
|
||||
|
||||
Page::Project::Menu.perform(&:go_to_integrations_settings)
|
||||
QA::Page::Project::Settings::Integrations.perform(&:click_jira_link)
|
||||
Page::Project::Menu.perform(&:go_to_integrations_settings)
|
||||
QA::Page::Project::Settings::Integrations.perform(&:click_jira_link)
|
||||
|
||||
QA::Page::Project::Settings::Services::Jira.perform do |jira|
|
||||
jira.setup_service_with(url: Vendor::Jira::JiraAPI.perform(&:base_url))
|
||||
end
|
||||
|
||||
expect(page).not_to have_text("Url is blocked")
|
||||
expect(page).to have_text("Jira settings saved and active.")
|
||||
QA::Page::Project::Settings::Services::Jira.perform do |jira|
|
||||
jira.setup_service_with(url: Vendor::Jira::JiraAPI.perform(&:base_url))
|
||||
end
|
||||
|
||||
expect(page).not_to have_text("Url is blocked")
|
||||
expect(page).to have_text("Jira settings saved and active.")
|
||||
end
|
||||
|
||||
def import_jira_issues
|
||||
|
|
|
@ -19,26 +19,21 @@ module QA
|
|||
page.has_text? 'Welcome to Jira'
|
||||
end
|
||||
|
||||
# Retry is required because allow_local_requests_from_web_hooks_and_services
|
||||
# takes some time to get enabled.
|
||||
# Bug issue: https://gitlab.com/gitlab-org/gitlab/-/issues/217010
|
||||
QA::Support::Retrier.retry_on_exception(max_attempts: 5, sleep_interval: 3) do
|
||||
Runtime::ApplicationSettings.set_application_settings(allow_local_requests_from_web_hooks_and_services: true)
|
||||
Runtime::ApplicationSettings.set_application_settings(allow_local_requests_from_web_hooks_and_services: true)
|
||||
|
||||
page.visit Runtime::Scenario.gitlab_address
|
||||
Flow::Login.sign_in_unless_signed_in
|
||||
page.visit Runtime::Scenario.gitlab_address
|
||||
Flow::Login.sign_in_unless_signed_in
|
||||
|
||||
project.visit!
|
||||
project.visit!
|
||||
|
||||
Page::Project::Menu.perform(&:go_to_integrations_settings)
|
||||
QA::Page::Project::Settings::Integrations.perform(&:click_jira_link)
|
||||
Page::Project::Menu.perform(&:go_to_integrations_settings)
|
||||
QA::Page::Project::Settings::Integrations.perform(&:click_jira_link)
|
||||
|
||||
QA::Page::Project::Settings::Services::Jira.perform do |jira|
|
||||
jira.setup_service_with(url: Vendor::Jira::JiraAPI.perform(&:base_url))
|
||||
end
|
||||
|
||||
expect(page).not_to have_text("Requests to the local network are not allowed")
|
||||
QA::Page::Project::Settings::Services::Jira.perform do |jira|
|
||||
jira.setup_service_with(url: Vendor::Jira::JiraAPI.perform(&:base_url))
|
||||
end
|
||||
|
||||
expect(page).not_to have_text("Requests to the local network are not allowed")
|
||||
end
|
||||
|
||||
it 'closes an issue via pushing a commit', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/827' do
|
||||
|
|
|
@ -200,7 +200,7 @@ module Trigger
|
|||
|
||||
class Docs < Base
|
||||
def self.access_token
|
||||
ENV['DOCS_API_TOKEN']
|
||||
ENV['DOCS_PROJECT_API_TOKEN']
|
||||
end
|
||||
|
||||
SUCCESS_MESSAGE = <<~MSG
|
||||
|
|
|
@ -150,7 +150,7 @@ RSpec.describe Projects::NotesController do
|
|||
end
|
||||
|
||||
it 'returns an empty page of notes' do
|
||||
expect(Gitlab::EtagCaching::Middleware).not_to receive(:skip!)
|
||||
expect(Gitlab::EtagCaching::Middleware).to receive(:skip!)
|
||||
|
||||
request.headers['X-Last-Fetched-At'] = microseconds(Time.zone.now)
|
||||
|
||||
|
@ -169,6 +169,8 @@ RSpec.describe Projects::NotesController do
|
|||
end
|
||||
|
||||
it 'returns all notes' do
|
||||
expect(Gitlab::EtagCaching::Middleware).to receive(:skip!)
|
||||
|
||||
get :index, params: request_params
|
||||
|
||||
expect(json_response['notes'].count).to eq((page_1 + page_2 + page_3).size + 1)
|
||||
|
|
|
@ -5,292 +5,296 @@ require 'spec_helper'
|
|||
RSpec.describe SearchController do
|
||||
include ExternalAuthorizationServiceHelpers
|
||||
|
||||
let(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
shared_examples_for 'when the user cannot read cross project' do |action, params|
|
||||
before do
|
||||
allow(Ability).to receive(:allowed?).and_call_original
|
||||
allow(Ability).to receive(:allowed?)
|
||||
.with(user, :read_cross_project, :global) { false }
|
||||
end
|
||||
|
||||
it 'blocks access without a project_id' do
|
||||
get action, params: params
|
||||
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
end
|
||||
|
||||
it 'allows access with a project_id' do
|
||||
get action, params: params.merge(project_id: create(:project, :public).id)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples_for 'with external authorization service enabled' do |action, params|
|
||||
let(:project) { create(:project, namespace: user.namespace) }
|
||||
let(:note) { create(:note_on_issue, project: project) }
|
||||
context 'authorized user' do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
enable_external_authorization_service_check
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it 'renders a 403 when no project is given' do
|
||||
get action, params: params
|
||||
shared_examples_for 'when the user cannot read cross project' do |action, params|
|
||||
before do
|
||||
allow(Ability).to receive(:allowed?).and_call_original
|
||||
allow(Ability).to receive(:allowed?)
|
||||
.with(user, :read_cross_project, :global) { false }
|
||||
end
|
||||
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
end
|
||||
it 'blocks access without a project_id' do
|
||||
get action, params: params
|
||||
|
||||
it 'renders a 200 when a project was set' do
|
||||
get action, params: params.merge(project_id: project.id)
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
end
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET #show' do
|
||||
it_behaves_like 'when the user cannot read cross project', :show, { search: 'hello' } do
|
||||
it 'still allows accessing the search page' do
|
||||
get :show
|
||||
it 'allows access with a project_id' do
|
||||
get action, params: params.merge(project_id: create(:project, :public).id)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'with external authorization service enabled', :show, { search: 'hello' }
|
||||
|
||||
context 'uses the right partials depending on scope' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
render_views
|
||||
|
||||
let_it_be(:project) { create(:project, :public, :repository, :wiki_repo) }
|
||||
shared_examples_for 'with external authorization service enabled' do |action, params|
|
||||
let(:project) { create(:project, namespace: user.namespace) }
|
||||
let(:note) { create(:note_on_issue, project: project) }
|
||||
|
||||
before do
|
||||
expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original
|
||||
enable_external_authorization_service_check
|
||||
end
|
||||
|
||||
subject { get(:show, params: { project_id: project.id, scope: scope, search: 'merge' }) }
|
||||
it 'renders a 403 when no project is given' do
|
||||
get action, params: params
|
||||
|
||||
where(:partial, :scope) do
|
||||
'_blob' | :blobs
|
||||
'_wiki_blob' | :wiki_blobs
|
||||
'_commit' | :commits
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
end
|
||||
|
||||
with_them do
|
||||
it do
|
||||
project_wiki = create(:project_wiki, project: project, user: user)
|
||||
create(:wiki_page, wiki: project_wiki, title: 'merge', content: 'merge')
|
||||
it 'renders a 200 when a project was set' do
|
||||
get action, params: params.merge(project_id: project.id)
|
||||
|
||||
expect(subject).to render_template("search/results/#{partial}")
|
||||
end
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
end
|
||||
|
||||
context 'global search' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
render_views
|
||||
describe 'GET #show' do
|
||||
it_behaves_like 'when the user cannot read cross project', :show, { search: 'hello' } do
|
||||
it 'still allows accessing the search page' do
|
||||
get :show
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'with external authorization service enabled', :show, { search: 'hello' }
|
||||
|
||||
context 'uses the right partials depending on scope' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
render_views
|
||||
|
||||
let_it_be(:project) { create(:project, :public, :repository, :wiki_repo) }
|
||||
|
||||
context 'when block_anonymous_global_searches is disabled' do
|
||||
before do
|
||||
stub_feature_flags(block_anonymous_global_searches: false)
|
||||
expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original
|
||||
end
|
||||
|
||||
it 'omits pipeline status from load' do
|
||||
project = create(:project, :public)
|
||||
expect(Gitlab::Cache::Ci::ProjectPipelineStatus).not_to receive(:load_in_batch_for_projects)
|
||||
subject { get(:show, params: { project_id: project.id, scope: scope, search: 'merge' }) }
|
||||
|
||||
get :show, params: { scope: 'projects', search: project.name }
|
||||
|
||||
expect(assigns[:search_objects].first).to eq project
|
||||
where(:partial, :scope) do
|
||||
'_blob' | :blobs
|
||||
'_wiki_blob' | :wiki_blobs
|
||||
'_commit' | :commits
|
||||
end
|
||||
|
||||
context 'check search term length' do
|
||||
let(:search_queries) do
|
||||
char_limit = SearchService::SEARCH_CHAR_LIMIT
|
||||
term_limit = SearchService::SEARCH_TERM_LIMIT
|
||||
{
|
||||
chars_under_limit: ('a' * (char_limit - 1)),
|
||||
chars_over_limit: ('a' * (char_limit + 1)),
|
||||
terms_under_limit: ('abc ' * (term_limit - 1)),
|
||||
terms_over_limit: ('abc ' * (term_limit + 1))
|
||||
}
|
||||
with_them do
|
||||
it do
|
||||
project_wiki = create(:project_wiki, project: project, user: user)
|
||||
create(:wiki_page, wiki: project_wiki, title: 'merge', content: 'merge')
|
||||
|
||||
expect(subject).to render_template("search/results/#{partial}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'global search' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
render_views
|
||||
|
||||
context 'when block_anonymous_global_searches is disabled' do
|
||||
before do
|
||||
stub_feature_flags(block_anonymous_global_searches: false)
|
||||
end
|
||||
|
||||
where(:string_name, :expectation) do
|
||||
:chars_under_limit | :not_to_set_flash
|
||||
:chars_over_limit | :set_chars_flash
|
||||
:terms_under_limit | :not_to_set_flash
|
||||
:terms_over_limit | :set_terms_flash
|
||||
it 'omits pipeline status from load' do
|
||||
project = create(:project, :public)
|
||||
expect(Gitlab::Cache::Ci::ProjectPipelineStatus).not_to receive(:load_in_batch_for_projects)
|
||||
|
||||
get :show, params: { scope: 'projects', search: project.name }
|
||||
|
||||
expect(assigns[:search_objects].first).to eq project
|
||||
end
|
||||
|
||||
with_them do
|
||||
it do
|
||||
get :show, params: { scope: 'projects', search: search_queries[string_name] }
|
||||
context 'check search term length' do
|
||||
let(:search_queries) do
|
||||
char_limit = SearchService::SEARCH_CHAR_LIMIT
|
||||
term_limit = SearchService::SEARCH_TERM_LIMIT
|
||||
{
|
||||
chars_under_limit: ('a' * (char_limit - 1)),
|
||||
chars_over_limit: ('a' * (char_limit + 1)),
|
||||
terms_under_limit: ('abc ' * (term_limit - 1)),
|
||||
terms_over_limit: ('abc ' * (term_limit + 1))
|
||||
}
|
||||
end
|
||||
|
||||
case expectation
|
||||
when :not_to_set_flash
|
||||
expect(controller).not_to set_flash[:alert]
|
||||
when :set_chars_flash
|
||||
expect(controller).to set_flash[:alert].to(/characters/)
|
||||
when :set_terms_flash
|
||||
expect(controller).to set_flash[:alert].to(/terms/)
|
||||
where(:string_name, :expectation) do
|
||||
:chars_under_limit | :not_to_set_flash
|
||||
:chars_over_limit | :set_chars_flash
|
||||
:terms_under_limit | :not_to_set_flash
|
||||
:terms_over_limit | :set_terms_flash
|
||||
end
|
||||
|
||||
with_them do
|
||||
it do
|
||||
get :show, params: { scope: 'projects', search: search_queries[string_name] }
|
||||
|
||||
case expectation
|
||||
when :not_to_set_flash
|
||||
expect(controller).not_to set_flash[:alert]
|
||||
when :set_chars_flash
|
||||
expect(controller).to set_flash[:alert].to(/characters/)
|
||||
when :set_terms_flash
|
||||
expect(controller).to set_flash[:alert].to(/terms/)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when block_anonymous_global_searches is enabled' do
|
||||
context 'for unauthenticated user' do
|
||||
before do
|
||||
sign_out(user)
|
||||
end
|
||||
|
||||
it 'redirects to login page' do
|
||||
get :show, params: { scope: 'projects', search: '*' }
|
||||
|
||||
expect(response).to redirect_to new_user_session_path
|
||||
end
|
||||
end
|
||||
|
||||
context 'for authenticated user' do
|
||||
it 'succeeds' do
|
||||
get :show, params: { scope: 'projects', search: '*' }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when block_anonymous_global_searches is enabled' do
|
||||
context 'for unauthenticated user' do
|
||||
it 'finds issue comments' do
|
||||
project = create(:project, :public)
|
||||
note = create(:note_on_issue, project: project)
|
||||
|
||||
get :show, params: { project_id: project.id, scope: 'notes', search: note.note }
|
||||
|
||||
expect(assigns[:search_objects].first).to eq note
|
||||
end
|
||||
|
||||
context 'unique users tracking' do
|
||||
before do
|
||||
allow(Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:track_event)
|
||||
end
|
||||
|
||||
it_behaves_like 'tracking unique hll events', :search_track_unique_users do
|
||||
subject(:request) { get :show, params: { scope: 'projects', search: 'term' } }
|
||||
|
||||
let(:target_id) { 'i_search_total' }
|
||||
let(:expected_type) { instance_of(String) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'on restricted projects' do
|
||||
context 'when signed out' do
|
||||
before do
|
||||
sign_out(user)
|
||||
end
|
||||
|
||||
it 'redirects to login page' do
|
||||
get :show, params: { scope: 'projects', search: '*' }
|
||||
it "doesn't expose comments on issues" do
|
||||
project = create(:project, :public, :issues_private)
|
||||
note = create(:note_on_issue, project: project)
|
||||
|
||||
expect(response).to redirect_to new_user_session_path
|
||||
get :show, params: { project_id: project.id, scope: 'notes', search: note.note }
|
||||
|
||||
expect(assigns[:search_objects].count).to eq(0)
|
||||
end
|
||||
end
|
||||
|
||||
context 'for authenticated user' do
|
||||
it 'succeeds' do
|
||||
get :show, params: { scope: 'projects', search: '*' }
|
||||
it "doesn't expose comments on merge_requests" do
|
||||
project = create(:project, :public, :merge_requests_private)
|
||||
note = create(:note_on_merge_request, project: project)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
get :show, params: { project_id: project.id, scope: 'notes', search: note.note }
|
||||
|
||||
it 'finds issue comments' do
|
||||
project = create(:project, :public)
|
||||
note = create(:note_on_issue, project: project)
|
||||
|
||||
get :show, params: { project_id: project.id, scope: 'notes', search: note.note }
|
||||
|
||||
expect(assigns[:search_objects].first).to eq note
|
||||
end
|
||||
|
||||
context 'unique users tracking' do
|
||||
before do
|
||||
allow(Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:track_event)
|
||||
end
|
||||
|
||||
it_behaves_like 'tracking unique hll events', :search_track_unique_users do
|
||||
subject(:request) { get :show, params: { scope: 'projects', search: 'term' } }
|
||||
|
||||
let(:target_id) { 'i_search_total' }
|
||||
let(:expected_type) { instance_of(String) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'on restricted projects' do
|
||||
context 'when signed out' do
|
||||
before do
|
||||
sign_out(user)
|
||||
expect(assigns[:search_objects].count).to eq(0)
|
||||
end
|
||||
|
||||
it "doesn't expose comments on issues" do
|
||||
project = create(:project, :public, :issues_private)
|
||||
note = create(:note_on_issue, project: project)
|
||||
it "doesn't expose comments on snippets" do
|
||||
project = create(:project, :public, :snippets_private)
|
||||
note = create(:note_on_project_snippet, project: project)
|
||||
|
||||
get :show, params: { project_id: project.id, scope: 'notes', search: note.note }
|
||||
|
||||
expect(assigns[:search_objects].count).to eq(0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "doesn't expose comments on merge_requests" do
|
||||
project = create(:project, :public, :merge_requests_private)
|
||||
note = create(:note_on_merge_request, project: project)
|
||||
describe 'GET #count' do
|
||||
it_behaves_like 'when the user cannot read cross project', :count, { search: 'hello', scope: 'projects' }
|
||||
it_behaves_like 'with external authorization service enabled', :count, { search: 'hello', scope: 'projects' }
|
||||
|
||||
get :show, params: { project_id: project.id, scope: 'notes', search: note.note }
|
||||
it 'returns the result count for the given term and scope' do
|
||||
create(:project, :public, name: 'hello world')
|
||||
create(:project, :public, name: 'foo bar')
|
||||
|
||||
expect(assigns[:search_objects].count).to eq(0)
|
||||
get :count, params: { search: 'hello', scope: 'projects' }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(json_response).to eq({ 'count' => '1' })
|
||||
end
|
||||
|
||||
it "doesn't expose comments on snippets" do
|
||||
project = create(:project, :public, :snippets_private)
|
||||
note = create(:note_on_project_snippet, project: project)
|
||||
it 'raises an error if search term is missing' do
|
||||
expect do
|
||||
get :count, params: { scope: 'projects' }
|
||||
end.to raise_error(ActionController::ParameterMissing)
|
||||
end
|
||||
|
||||
get :show, params: { project_id: project.id, scope: 'notes', search: note.note }
|
||||
it 'raises an error if search scope is missing' do
|
||||
expect do
|
||||
get :count, params: { search: 'hello' }
|
||||
end.to raise_error(ActionController::ParameterMissing)
|
||||
end
|
||||
end
|
||||
|
||||
expect(assigns[:search_objects].count).to eq(0)
|
||||
describe 'GET #autocomplete' do
|
||||
it_behaves_like 'when the user cannot read cross project', :autocomplete, { term: 'hello' }
|
||||
it_behaves_like 'with external authorization service enabled', :autocomplete, { term: 'hello' }
|
||||
end
|
||||
|
||||
describe '#append_info_to_payload' do
|
||||
it 'appends search metadata for logging' do
|
||||
last_payload = nil
|
||||
original_append_info_to_payload = controller.method(:append_info_to_payload)
|
||||
|
||||
expect(controller).to receive(:append_info_to_payload) do |payload|
|
||||
original_append_info_to_payload.call(payload)
|
||||
last_payload = payload
|
||||
end
|
||||
|
||||
get :show, params: { scope: 'issues', search: 'hello world', group_id: '123', project_id: '456', confidential: true, state: true, force_search_results: true }
|
||||
|
||||
expect(last_payload[:metadata]['meta.search.group_id']).to eq('123')
|
||||
expect(last_payload[:metadata]['meta.search.project_id']).to eq('456')
|
||||
expect(last_payload[:metadata]).not_to have_key('meta.search.search')
|
||||
expect(last_payload[:metadata]['meta.search.scope']).to eq('issues')
|
||||
expect(last_payload[:metadata]['meta.search.force_search_results']).to eq('true')
|
||||
expect(last_payload[:metadata]['meta.search.filters.confidential']).to eq('true')
|
||||
expect(last_payload[:metadata]['meta.search.filters.state']).to eq('true')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET #count' do
|
||||
it_behaves_like 'when the user cannot read cross project', :count, { search: 'hello', scope: 'projects' }
|
||||
it_behaves_like 'with external authorization service enabled', :count, { search: 'hello', scope: 'projects' }
|
||||
context 'unauthorized user' do
|
||||
describe 'GET #opensearch' do
|
||||
render_views
|
||||
|
||||
it 'returns the result count for the given term and scope' do
|
||||
create(:project, :public, name: 'hello world')
|
||||
create(:project, :public, name: 'foo bar')
|
||||
it 'renders xml' do
|
||||
get :opensearch, format: :xml
|
||||
|
||||
get :count, params: { search: 'hello', scope: 'projects' }
|
||||
doc = Nokogiri::XML.parse(response.body)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(json_response).to eq({ 'count' => '1' })
|
||||
end
|
||||
|
||||
it 'raises an error if search term is missing' do
|
||||
expect do
|
||||
get :count, params: { scope: 'projects' }
|
||||
end.to raise_error(ActionController::ParameterMissing)
|
||||
end
|
||||
|
||||
it 'raises an error if search scope is missing' do
|
||||
expect do
|
||||
get :count, params: { search: 'hello' }
|
||||
end.to raise_error(ActionController::ParameterMissing)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET #autocomplete' do
|
||||
it_behaves_like 'when the user cannot read cross project', :autocomplete, { term: 'hello' }
|
||||
it_behaves_like 'with external authorization service enabled', :autocomplete, { term: 'hello' }
|
||||
end
|
||||
|
||||
describe 'GET #opensearch' do
|
||||
render_views
|
||||
|
||||
it 'renders xml' do
|
||||
get :opensearch, format: :xml
|
||||
|
||||
doc = Nokogiri::XML.parse(response.body)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(doc.css('OpenSearchDescription ShortName').text).to eq('GitLab')
|
||||
expect(doc.css('OpenSearchDescription *').map(&:name)).to eq(%w[ShortName Description InputEncoding Image Url SearchForm])
|
||||
end
|
||||
end
|
||||
|
||||
describe '#append_info_to_payload' do
|
||||
it 'appends search metadata for logging' do
|
||||
last_payload = nil
|
||||
original_append_info_to_payload = controller.method(:append_info_to_payload)
|
||||
|
||||
expect(controller).to receive(:append_info_to_payload) do |payload|
|
||||
original_append_info_to_payload.call(payload)
|
||||
last_payload = payload
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(doc.css('OpenSearchDescription ShortName').text).to eq('GitLab')
|
||||
expect(doc.css('OpenSearchDescription *').map(&:name)).to eq(%w[ShortName Description InputEncoding Image Url SearchForm])
|
||||
end
|
||||
|
||||
get :show, params: { scope: 'issues', search: 'hello world', group_id: '123', project_id: '456', confidential: true, state: true, force_search_results: true }
|
||||
|
||||
expect(last_payload[:metadata]['meta.search.group_id']).to eq('123')
|
||||
expect(last_payload[:metadata]['meta.search.project_id']).to eq('456')
|
||||
expect(last_payload[:metadata]).not_to have_key('meta.search.search')
|
||||
expect(last_payload[:metadata]['meta.search.scope']).to eq('issues')
|
||||
expect(last_payload[:metadata]['meta.search.force_search_results']).to eq('true')
|
||||
expect(last_payload[:metadata]['meta.search.filters.confidential']).to eq('true')
|
||||
expect(last_payload[:metadata]['meta.search.filters.state']).to eq('true')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -68,7 +68,7 @@ RSpec.describe 'Merge request > User merges when pipeline succeeds', :js do
|
|||
|
||||
wait_for_requests
|
||||
|
||||
expect(page).to have_content 'Merge when pipeline succeeds', wait: 0
|
||||
expect(page).to have_content 'Merge when pipeline succeeds'
|
||||
end
|
||||
|
||||
it_behaves_like 'Merge when pipeline succeeds activator'
|
||||
|
|
|
@ -203,10 +203,11 @@ RSpec.describe 'User browses commits' do
|
|||
|
||||
context 'when click the compare tab' do
|
||||
before do
|
||||
wait_for_requests
|
||||
click_link('Compare')
|
||||
end
|
||||
|
||||
it 'does not render create merge request button' do
|
||||
it 'does not render create merge request button', :js do
|
||||
expect(page).not_to have_link 'Create merge request'
|
||||
end
|
||||
end
|
||||
|
@ -236,10 +237,11 @@ RSpec.describe 'User browses commits' do
|
|||
|
||||
context 'when click the compare tab' do
|
||||
before do
|
||||
wait_for_requests
|
||||
click_link('Compare')
|
||||
end
|
||||
|
||||
it 'renders create merge request button' do
|
||||
it 'renders create merge request button', :js do
|
||||
expect(page).to have_link 'Create merge request'
|
||||
end
|
||||
end
|
||||
|
@ -276,10 +278,11 @@ RSpec.describe 'User browses commits' do
|
|||
|
||||
context 'when click the compare tab' do
|
||||
before do
|
||||
wait_for_requests
|
||||
click_link('Compare')
|
||||
end
|
||||
|
||||
it 'renders button to the merge request' do
|
||||
it 'renders button to the merge request', :js do
|
||||
expect(page).not_to have_link 'Create merge request'
|
||||
expect(page).to have_link 'View open merge request', href: project_merge_request_path(project, merge_request)
|
||||
end
|
||||
|
|
|
@ -17,10 +17,10 @@ RSpec.describe "Compare", :js do
|
|||
visit project_compare_index_path(project, from: 'master', to: 'master')
|
||||
|
||||
select_using_dropdown 'from', 'feature'
|
||||
expect(find('.js-compare-from-dropdown .dropdown-toggle-text')).to have_content('feature')
|
||||
expect(find('.js-compare-from-dropdown .gl-new-dropdown-button-text')).to have_content('feature')
|
||||
|
||||
select_using_dropdown 'to', 'binary-encoding'
|
||||
expect(find('.js-compare-to-dropdown .dropdown-toggle-text')).to have_content('binary-encoding')
|
||||
expect(find('.js-compare-to-dropdown .gl-new-dropdown-button-text')).to have_content('binary-encoding')
|
||||
|
||||
click_button 'Compare'
|
||||
|
||||
|
@ -32,8 +32,8 @@ RSpec.describe "Compare", :js do
|
|||
it "pre-populates fields" do
|
||||
visit project_compare_index_path(project, from: "master", to: "master")
|
||||
|
||||
expect(find(".js-compare-from-dropdown .dropdown-toggle-text")).to have_content("master")
|
||||
expect(find(".js-compare-to-dropdown .dropdown-toggle-text")).to have_content("master")
|
||||
expect(find(".js-compare-from-dropdown .gl-new-dropdown-button-text")).to have_content("master")
|
||||
expect(find(".js-compare-to-dropdown .gl-new-dropdown-button-text")).to have_content("master")
|
||||
end
|
||||
|
||||
it_behaves_like 'compares branches'
|
||||
|
@ -99,7 +99,7 @@ RSpec.describe "Compare", :js do
|
|||
|
||||
find(".js-compare-from-dropdown .compare-dropdown-toggle").click
|
||||
|
||||
expect(find(".js-compare-from-dropdown .dropdown-content")).to have_selector("li", count: 3)
|
||||
expect(find(".js-compare-from-dropdown .gl-new-dropdown-contents")).to have_selector('li.gl-new-dropdown-item', count: 1)
|
||||
end
|
||||
|
||||
context 'when commit has overflow', :js do
|
||||
|
@ -125,10 +125,10 @@ RSpec.describe "Compare", :js do
|
|||
visit project_compare_index_path(project, from: "master", to: "master")
|
||||
|
||||
select_using_dropdown "from", "v1.0.0"
|
||||
expect(find(".js-compare-from-dropdown .dropdown-toggle-text")).to have_content("v1.0.0")
|
||||
expect(find(".js-compare-from-dropdown .gl-new-dropdown-button-text")).to have_content("v1.0.0")
|
||||
|
||||
select_using_dropdown "to", "v1.1.0"
|
||||
expect(find(".js-compare-to-dropdown .dropdown-toggle-text")).to have_content("v1.1.0")
|
||||
expect(find(".js-compare-to-dropdown .gl-new-dropdown-button-text")).to have_content("v1.1.0")
|
||||
|
||||
click_button "Compare"
|
||||
expect(page).to have_content "Commits"
|
||||
|
@ -136,19 +136,22 @@ RSpec.describe "Compare", :js do
|
|||
end
|
||||
|
||||
def select_using_dropdown(dropdown_type, selection, commit: false)
|
||||
wait_for_requests
|
||||
|
||||
dropdown = find(".js-compare-#{dropdown_type}-dropdown")
|
||||
dropdown.find(".compare-dropdown-toggle").click
|
||||
# find input before using to wait for the inputs visibility
|
||||
dropdown.find('.dropdown-menu')
|
||||
dropdown.fill_in("Filter by Git revision", with: selection)
|
||||
|
||||
wait_for_requests
|
||||
|
||||
if commit
|
||||
dropdown.find('input[type="search"]').send_keys(:return)
|
||||
dropdown.find('.gl-search-box-by-type-input').send_keys(:return)
|
||||
else
|
||||
# find before all to wait for the items visibility
|
||||
dropdown.find("a[data-ref=\"#{selection}\"]", match: :first)
|
||||
dropdown.all("a[data-ref=\"#{selection}\"]").last.click
|
||||
dropdown.find(".js-compare-#{dropdown_type}-dropdown .dropdown-item", text: selection, match: :first)
|
||||
dropdown.all(".js-compare-#{dropdown_type}-dropdown .dropdown-item", text: selection).first.click
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -288,23 +288,23 @@ RSpec.describe 'Pipelines', :js do
|
|||
end
|
||||
|
||||
it 'has a dropdown with play button' do
|
||||
expect(page).to have_selector('.dropdown-new.btn.btn-default .icon-play')
|
||||
expect(page).to have_selector('[data-testid="pipelines-manual-actions-dropdown"] [data-testid="play-icon"]')
|
||||
end
|
||||
|
||||
it 'has link to the manual action' do
|
||||
find('.js-pipeline-dropdown-manual-actions').click
|
||||
find('[data-testid="pipelines-manual-actions-dropdown"]').click
|
||||
|
||||
expect(page).to have_button('manual build')
|
||||
end
|
||||
|
||||
context 'when manual action was played' do
|
||||
before do
|
||||
find('.js-pipeline-dropdown-manual-actions').click
|
||||
find('[data-testid="pipelines-manual-actions-dropdown"]').click
|
||||
click_button('manual build')
|
||||
end
|
||||
|
||||
it 'enqueues manual action job' do
|
||||
expect(page).to have_selector('.js-pipeline-dropdown-manual-actions:disabled')
|
||||
expect(page).to have_selector('[data-testid="pipelines-manual-actions-dropdown"] .gl-dropdown-toggle:disabled')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -322,11 +322,11 @@ RSpec.describe 'Pipelines', :js do
|
|||
end
|
||||
|
||||
it 'has a dropdown for actionable jobs' do
|
||||
expect(page).to have_selector('.dropdown-new.btn.btn-default .icon-play')
|
||||
expect(page).to have_selector('[data-testid="pipelines-manual-actions-dropdown"] [data-testid="play-icon"]')
|
||||
end
|
||||
|
||||
it "has link to the delayed job's action" do
|
||||
find('.js-pipeline-dropdown-manual-actions').click
|
||||
find('[data-testid="pipelines-manual-actions-dropdown"]').click
|
||||
|
||||
time_diff = [0, delayed_job.scheduled_at - Time.now].max
|
||||
expect(page).to have_button('delayed job 1')
|
||||
|
@ -342,7 +342,7 @@ RSpec.describe 'Pipelines', :js do
|
|||
end
|
||||
|
||||
it "shows 00:00:00 as the remaining time" do
|
||||
find('.js-pipeline-dropdown-manual-actions').click
|
||||
find('[data-testid="pipelines-manual-actions-dropdown"]').click
|
||||
|
||||
expect(page).to have_content("00:00:00")
|
||||
end
|
||||
|
@ -350,7 +350,7 @@ RSpec.describe 'Pipelines', :js do
|
|||
|
||||
context 'when user played a delayed job immediately' do
|
||||
before do
|
||||
find('.js-pipeline-dropdown-manual-actions').click
|
||||
find('[data-testid="pipelines-manual-actions-dropdown"]').click
|
||||
page.accept_confirm { click_button('delayed job 1') }
|
||||
wait_for_requests
|
||||
end
|
||||
|
|
|
@ -291,9 +291,45 @@ describe('Actions Notes Store', () => {
|
|||
[
|
||||
{ type: 'updateOrCreateNotes', payload: discussionMock.notes },
|
||||
{ type: 'startTaskList' },
|
||||
{ type: 'updateResolvableDiscussionsCounts' },
|
||||
],
|
||||
));
|
||||
});
|
||||
|
||||
describe('paginated notes feature flag enabled', () => {
|
||||
const lastFetchedAt = '12358';
|
||||
|
||||
beforeEach(() => {
|
||||
window.gon = { features: { paginatedNotes: true } };
|
||||
|
||||
axiosMock.onGet(notesDataMock.notesPath).replyOnce(200, {
|
||||
notes: discussionMock.notes,
|
||||
more: false,
|
||||
last_fetched_at: lastFetchedAt,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.gon = null;
|
||||
});
|
||||
|
||||
it('should dispatch setFetchingState, setNotesFetchedState, setLoadingState, updateOrCreateNotes, startTaskList and commit SET_LAST_FETCHED_AT', () => {
|
||||
return testAction(
|
||||
actions.fetchData,
|
||||
null,
|
||||
{ notesData: notesDataMock, isFetching: true },
|
||||
[{ type: 'SET_LAST_FETCHED_AT', payload: lastFetchedAt }],
|
||||
[
|
||||
{ type: 'setFetchingState', payload: false },
|
||||
{ type: 'setNotesFetchedState', payload: true },
|
||||
{ type: 'setLoadingState', payload: false },
|
||||
{ type: 'updateOrCreateNotes', payload: discussionMock.notes },
|
||||
{ type: 'startTaskList' },
|
||||
{ type: 'updateResolvableDiscussionsCounts' },
|
||||
],
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('poll', () => {
|
||||
|
@ -1355,4 +1391,17 @@ describe('Actions Notes Store', () => {
|
|||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setFetchingState', () => {
|
||||
it('commits SET_NOTES_FETCHING_STATE', (done) => {
|
||||
testAction(
|
||||
actions.setFetchingState,
|
||||
true,
|
||||
null,
|
||||
[{ type: mutationTypes.SET_NOTES_FETCHING_STATE, payload: true }],
|
||||
[],
|
||||
done,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -175,7 +175,7 @@ describe('Settings Panel', () => {
|
|||
wrapper = overrideCurrentSettings({ visibilityLevel: visibilityOptions.PRIVATE });
|
||||
|
||||
expect(findRepositoryFeatureProjectRow().props().helpText).toBe(
|
||||
'View and edit files in this project',
|
||||
'View and edit files in this project.',
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -183,7 +183,7 @@ describe('Settings Panel', () => {
|
|||
wrapper = overrideCurrentSettings({ visibilityLevel: visibilityOptions.PUBLIC });
|
||||
|
||||
expect(findRepositoryFeatureProjectRow().props().helpText).toBe(
|
||||
'View and edit files in this project. Non-project members will only have read access',
|
||||
'View and edit files in this project. Non-project members will only have read access.',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -400,7 +400,7 @@ describe('Settings Panel', () => {
|
|||
const link = message.find('a');
|
||||
|
||||
expect(message.text()).toContain(
|
||||
'LFS objects from this repository are still available to forks',
|
||||
'LFS objects from this repository are available to forks.',
|
||||
);
|
||||
expect(link.text()).toBe('How do I remove them?');
|
||||
expect(link.attributes('href')).toBe(
|
||||
|
@ -530,7 +530,7 @@ describe('Settings Panel', () => {
|
|||
|
||||
it('should contain help text', () => {
|
||||
expect(wrapper.find({ ref: 'metrics-visibility-settings' }).props().helpText).toBe(
|
||||
'With Metrics Dashboard you can visualize this project performance metrics',
|
||||
"Visualize the project's performance metrics.",
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -1,25 +1,29 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import { shallowMount, mount } from '@vue/test-utils';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import { GlButton } from '@gitlab/ui';
|
||||
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
|
||||
import { TEST_HOST } from 'spec/test_constants';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import createFlash from '~/flash';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import PipelinesActions from '~/pipelines/components/pipelines_list/pipelines_actions.vue';
|
||||
import GlCountdown from '~/vue_shared/components/gl_countdown.vue';
|
||||
|
||||
jest.mock('~/flash');
|
||||
|
||||
describe('Pipelines Actions dropdown', () => {
|
||||
let wrapper;
|
||||
let mock;
|
||||
|
||||
const createComponent = (actions = []) => {
|
||||
wrapper = shallowMount(PipelinesActions, {
|
||||
const createComponent = (props, mountFn = shallowMount) => {
|
||||
wrapper = mountFn(PipelinesActions, {
|
||||
propsData: {
|
||||
actions,
|
||||
...props,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const findAllDropdownItems = () => wrapper.findAll(GlButton);
|
||||
const findDropdown = () => wrapper.find(GlDropdown);
|
||||
const findAllDropdownItems = () => wrapper.findAll(GlDropdownItem);
|
||||
const findAllCountdowns = () => wrapper.findAll(GlCountdown);
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -47,7 +51,7 @@ describe('Pipelines Actions dropdown', () => {
|
|||
];
|
||||
|
||||
beforeEach(() => {
|
||||
createComponent(mockActions);
|
||||
createComponent({ actions: mockActions });
|
||||
});
|
||||
|
||||
it('renders a dropdown with the provided actions', () => {
|
||||
|
@ -59,16 +63,33 @@ describe('Pipelines Actions dropdown', () => {
|
|||
});
|
||||
|
||||
describe('on click', () => {
|
||||
it('makes a request and toggles the loading state', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({ actions: mockActions }, mount);
|
||||
});
|
||||
|
||||
it('makes a request and toggles the loading state', async () => {
|
||||
mock.onPost(mockActions.path).reply(200);
|
||||
|
||||
wrapper.find(GlButton).vm.$emit('click');
|
||||
findAllDropdownItems().at(0).vm.$emit('click');
|
||||
|
||||
expect(wrapper.vm.isLoading).toBe(true);
|
||||
await wrapper.vm.$nextTick();
|
||||
expect(findDropdown().props('loading')).toBe(true);
|
||||
|
||||
return waitForPromises().then(() => {
|
||||
expect(wrapper.vm.isLoading).toBe(false);
|
||||
});
|
||||
await waitForPromises();
|
||||
expect(findDropdown().props('loading')).toBe(false);
|
||||
});
|
||||
|
||||
it('makes a failed request and toggles the loading state', async () => {
|
||||
mock.onPost(mockActions.path).reply(500);
|
||||
|
||||
findAllDropdownItems().at(0).vm.$emit('click');
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
expect(findDropdown().props('loading')).toBe(true);
|
||||
|
||||
await waitForPromises();
|
||||
expect(findDropdown().props('loading')).toBe(false);
|
||||
expect(createFlash).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -89,10 +110,10 @@ describe('Pipelines Actions dropdown', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
jest.spyOn(Date, 'now').mockImplementation(() => new Date('2063-04-04T00:42:00Z').getTime());
|
||||
createComponent([scheduledJobAction, expiredJobAction]);
|
||||
createComponent({ actions: [scheduledJobAction, expiredJobAction] });
|
||||
});
|
||||
|
||||
it('makes post request after confirming', () => {
|
||||
it('makes post request after confirming', async () => {
|
||||
mock.onPost(scheduledJobAction.path).reply(200);
|
||||
jest.spyOn(window, 'confirm').mockReturnValue(true);
|
||||
|
||||
|
@ -100,19 +121,22 @@ describe('Pipelines Actions dropdown', () => {
|
|||
|
||||
expect(window.confirm).toHaveBeenCalled();
|
||||
|
||||
return waitForPromises().then(() => {
|
||||
expect(mock.history.post.length).toBe(1);
|
||||
});
|
||||
await waitForPromises();
|
||||
|
||||
expect(mock.history.post).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('does not make post request if confirmation is cancelled', () => {
|
||||
it('does not make post request if confirmation is cancelled', async () => {
|
||||
mock.onPost(scheduledJobAction.path).reply(200);
|
||||
jest.spyOn(window, 'confirm').mockReturnValue(false);
|
||||
|
||||
findAllDropdownItems().at(0).vm.$emit('click');
|
||||
|
||||
expect(window.confirm).toHaveBeenCalled();
|
||||
expect(mock.history.post.length).toBe(0);
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(mock.history.post).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('displays the remaining time in the dropdown', () => {
|
||||
|
|
|
@ -0,0 +1,116 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import { GlButton } from '@gitlab/ui';
|
||||
import CompareApp from '~/projects/compare/components/app.vue';
|
||||
import RevisionDropdown from '~/projects/compare/components/revision_dropdown.vue';
|
||||
|
||||
jest.mock('~/lib/utils/csrf', () => ({ token: 'mock-csrf-token' }));
|
||||
|
||||
const projectCompareIndexPath = 'some/path';
|
||||
const refsProjectPath = 'some/refs/path';
|
||||
const paramsFrom = 'master';
|
||||
const paramsTo = 'master';
|
||||
|
||||
describe('CompareApp component', () => {
|
||||
let wrapper;
|
||||
|
||||
const createComponent = (props = {}) => {
|
||||
wrapper = shallowMount(CompareApp, {
|
||||
propsData: {
|
||||
projectCompareIndexPath,
|
||||
refsProjectPath,
|
||||
paramsFrom,
|
||||
paramsTo,
|
||||
projectMergeRequestPath: '',
|
||||
createMrPath: '',
|
||||
...props,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
wrapper = null;
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
it('renders component with prop', () => {
|
||||
expect(wrapper.props()).toEqual(
|
||||
expect.objectContaining({
|
||||
projectCompareIndexPath,
|
||||
refsProjectPath,
|
||||
paramsFrom,
|
||||
paramsTo,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('contains the correct form attributes', () => {
|
||||
expect(wrapper.attributes('action')).toBe(projectCompareIndexPath);
|
||||
expect(wrapper.attributes('method')).toBe('POST');
|
||||
});
|
||||
|
||||
it('has input with csrf token', () => {
|
||||
expect(wrapper.find('input[name="authenticity_token"]').attributes('value')).toBe(
|
||||
'mock-csrf-token',
|
||||
);
|
||||
});
|
||||
|
||||
it('has ellipsis', () => {
|
||||
expect(wrapper.find('[data-testid="ellipsis"]').exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('render Source and Target BranchDropdown components', () => {
|
||||
const branchDropdowns = wrapper.findAll(RevisionDropdown);
|
||||
|
||||
expect(branchDropdowns.length).toBe(2);
|
||||
expect(branchDropdowns.at(0).props('revisionText')).toBe('Source');
|
||||
expect(branchDropdowns.at(1).props('revisionText')).toBe('Target');
|
||||
});
|
||||
|
||||
describe('compare button', () => {
|
||||
const findCompareButton = () => wrapper.find(GlButton);
|
||||
|
||||
it('renders button', () => {
|
||||
expect(findCompareButton().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('submits form', () => {
|
||||
findCompareButton().vm.$emit('click');
|
||||
expect(wrapper.find('form').element.submit).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('has compare text', () => {
|
||||
expect(findCompareButton().text()).toBe('Compare');
|
||||
});
|
||||
});
|
||||
|
||||
describe('merge request buttons', () => {
|
||||
const findProjectMrButton = () => wrapper.find('[data-testid="projectMrButton"]');
|
||||
const findCreateMrButton = () => wrapper.find('[data-testid="createMrButton"]');
|
||||
|
||||
it('does not have merge request buttons', () => {
|
||||
createComponent();
|
||||
expect(findProjectMrButton().exists()).toBe(false);
|
||||
expect(findCreateMrButton().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('has "View open merge request" button', () => {
|
||||
createComponent({
|
||||
projectMergeRequestPath: 'some/project/merge/request/path',
|
||||
});
|
||||
expect(findProjectMrButton().exists()).toBe(true);
|
||||
expect(findCreateMrButton().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('has "Create merge request" button', () => {
|
||||
createComponent({
|
||||
createMrPath: 'some/create/create/mr/path',
|
||||
});
|
||||
expect(findProjectMrButton().exists()).toBe(false);
|
||||
expect(findCreateMrButton().exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,92 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import AxiosMockAdapter from 'axios-mock-adapter';
|
||||
import { GlDropdown } from '@gitlab/ui';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import RevisionDropdown from '~/projects/compare/components/revision_dropdown.vue';
|
||||
import createFlash from '~/flash';
|
||||
|
||||
const defaultProps = {
|
||||
refsProjectPath: 'some/refs/path',
|
||||
revisionText: 'Target',
|
||||
paramsName: 'from',
|
||||
paramsBranch: 'master',
|
||||
};
|
||||
|
||||
jest.mock('~/flash');
|
||||
|
||||
describe('RevisionDropdown component', () => {
|
||||
let wrapper;
|
||||
let axiosMock;
|
||||
|
||||
const createComponent = (props = {}) => {
|
||||
wrapper = shallowMount(RevisionDropdown, {
|
||||
propsData: {
|
||||
...defaultProps,
|
||||
...props,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
axiosMock = new AxiosMockAdapter(axios);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
axiosMock.restore();
|
||||
});
|
||||
|
||||
const findGlDropdown = () => wrapper.find(GlDropdown);
|
||||
|
||||
it('sets hidden input', () => {
|
||||
createComponent();
|
||||
expect(wrapper.find('input[type="hidden"]').attributes('value')).toBe(
|
||||
defaultProps.paramsBranch,
|
||||
);
|
||||
});
|
||||
|
||||
it('update the branches on success', async () => {
|
||||
const Branches = ['branch-1', 'branch-2'];
|
||||
const Tags = ['tag-1', 'tag-2', 'tag-3'];
|
||||
|
||||
axiosMock.onGet(defaultProps.refsProjectPath).replyOnce(200, {
|
||||
Branches,
|
||||
Tags,
|
||||
});
|
||||
|
||||
createComponent();
|
||||
|
||||
await axios.waitForAll();
|
||||
|
||||
expect(wrapper.vm.branches).toEqual(Branches);
|
||||
expect(wrapper.vm.tags).toEqual(Tags);
|
||||
});
|
||||
|
||||
it('shows flash message on error', async () => {
|
||||
axiosMock.onGet('some/invalid/path').replyOnce(404);
|
||||
|
||||
createComponent();
|
||||
|
||||
await wrapper.vm.fetchBranchesAndTags();
|
||||
expect(createFlash).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('GlDropdown component', () => {
|
||||
it('renders props', () => {
|
||||
createComponent();
|
||||
expect(wrapper.props()).toEqual(expect.objectContaining(defaultProps));
|
||||
});
|
||||
|
||||
it('display default text', () => {
|
||||
createComponent({
|
||||
paramsBranch: null,
|
||||
});
|
||||
expect(findGlDropdown().props('text')).toBe('Select branch/tag');
|
||||
});
|
||||
|
||||
it('display params branch text', () => {
|
||||
createComponent();
|
||||
expect(findGlDropdown().props('text')).toBe(defaultProps.paramsBranch);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -202,7 +202,11 @@ describe('MRWidgetAutoMergeEnabled', () => {
|
|||
wrapper.vm.cancelAutomaticMerge();
|
||||
setImmediate(() => {
|
||||
expect(wrapper.vm.isCancellingAutoMerge).toBeTruthy();
|
||||
expect(eventHub.$emit).toHaveBeenCalledWith('UpdateWidgetData', mrObj);
|
||||
if (mergeRequestWidgetGraphql) {
|
||||
expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested');
|
||||
} else {
|
||||
expect(eventHub.$emit).toHaveBeenCalledWith('UpdateWidgetData', mrObj);
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -316,4 +316,15 @@ RSpec.describe NotesHelper do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#notes_data' do
|
||||
let(:issue) { create(:issue, project: project) }
|
||||
|
||||
it 'sets last_fetched_at to 0 when start_at_zero is true' do
|
||||
@project = project
|
||||
@noteable = issue
|
||||
|
||||
expect(helper.notes_data(issue, true)[:lastFetchedAt]).to eq(0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -19,94 +19,6 @@ RSpec.describe TreeHelper do
|
|||
)
|
||||
end
|
||||
|
||||
describe '.render_tree' do
|
||||
before do
|
||||
@id = sha
|
||||
@path = ""
|
||||
@project = project
|
||||
@lfs_blob_ids = []
|
||||
end
|
||||
|
||||
it 'displays all entries without a warning' do
|
||||
tree = repository.tree(sha, 'files')
|
||||
|
||||
html = render_tree(tree)
|
||||
|
||||
expect(html).not_to have_selector('.tree-truncated-warning')
|
||||
end
|
||||
|
||||
it 'truncates entries and adds a warning' do
|
||||
stub_const('TreeHelper::FILE_LIMIT', 1)
|
||||
tree = repository.tree(sha, 'files')
|
||||
|
||||
html = render_tree(tree)
|
||||
|
||||
expect(html).to have_selector('.tree-truncated-warning', count: 1)
|
||||
expect(html).to have_selector('.tree-item-file-name', count: 1)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.fast_project_blob_path' do
|
||||
it 'generates the same path as project_blob_path' do
|
||||
blob_path = repository.tree(sha, 'with space').entries.first.path
|
||||
fast_path = fast_project_blob_path(project, blob_path)
|
||||
std_path = project_blob_path(project, blob_path)
|
||||
|
||||
expect(fast_path).to eq(std_path)
|
||||
end
|
||||
|
||||
it 'generates the same path with encoded file names' do
|
||||
tree = repository.tree(sha, 'encoding')
|
||||
blob_path = tree.entries.find { |entry| entry.path == 'encoding/テスト.txt' }.path
|
||||
fast_path = fast_project_blob_path(project, blob_path)
|
||||
std_path = project_blob_path(project, blob_path)
|
||||
|
||||
expect(fast_path).to eq(std_path)
|
||||
end
|
||||
|
||||
it 'respects a configured relative URL' do
|
||||
allow(Gitlab.config.gitlab).to receive(:relative_url_root).and_return('/gitlab/root')
|
||||
blob_path = repository.tree(sha, '').entries.first.path
|
||||
fast_path = fast_project_blob_path(project, blob_path)
|
||||
|
||||
expect(fast_path).to start_with('/gitlab/root')
|
||||
end
|
||||
|
||||
it 'encodes files starting with #' do
|
||||
filename = '#test-file'
|
||||
create_file(filename)
|
||||
|
||||
fast_path = fast_project_blob_path(project, filename)
|
||||
|
||||
expect(fast_path).to end_with('%23test-file')
|
||||
end
|
||||
end
|
||||
|
||||
describe '.fast_project_tree_path' do
|
||||
let(:tree_path) { repository.tree(sha, 'with space').path }
|
||||
let(:fast_path) { fast_project_tree_path(project, tree_path) }
|
||||
let(:std_path) { project_tree_path(project, tree_path) }
|
||||
|
||||
it 'generates the same path as project_tree_path' do
|
||||
expect(fast_path).to eq(std_path)
|
||||
end
|
||||
|
||||
it 'respects a configured relative URL' do
|
||||
allow(Gitlab.config.gitlab).to receive(:relative_url_root).and_return('/gitlab/root')
|
||||
|
||||
expect(fast_path).to start_with('/gitlab/root')
|
||||
end
|
||||
|
||||
it 'encodes files starting with #' do
|
||||
filename = '#test-file'
|
||||
create_file(filename)
|
||||
|
||||
fast_path = fast_project_tree_path(project, filename)
|
||||
|
||||
expect(fast_path).to end_with('%23test-file')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'flatten_tree' do
|
||||
let(:tree) { repository.tree(sha, 'files') }
|
||||
let(:root_path) { 'files' }
|
||||
|
|
|
@ -74,28 +74,41 @@ RSpec.describe BulkImports::Pipeline::Runner do
|
|||
expect_next_instance_of(Gitlab::Import::Logger) do |logger|
|
||||
expect(logger).to receive(:info)
|
||||
.with(
|
||||
bulk_import_entity_id: entity.id,
|
||||
bulk_import_entity_type: 'group_entity',
|
||||
message: 'Pipeline started',
|
||||
pipeline_class: 'BulkImports::MyPipeline'
|
||||
)
|
||||
expect(logger).to receive(:info)
|
||||
.with(
|
||||
bulk_import_entity_id: entity.id,
|
||||
bulk_import_entity_type: 'group_entity',
|
||||
pipeline_class: 'BulkImports::MyPipeline',
|
||||
bulk_import_entity_id: entity.id,
|
||||
bulk_import_entity_type: 'group_entity'
|
||||
pipeline_step: :extractor,
|
||||
step_class: 'BulkImports::Extractor'
|
||||
)
|
||||
expect(logger).to receive(:info)
|
||||
.with(
|
||||
bulk_import_entity_id: entity.id,
|
||||
bulk_import_entity_type: 'group_entity',
|
||||
extractor: 'BulkImports::Extractor'
|
||||
pipeline_class: 'BulkImports::MyPipeline',
|
||||
pipeline_step: :transformer,
|
||||
step_class: 'BulkImports::Transformer'
|
||||
)
|
||||
expect(logger).to receive(:info)
|
||||
.with(
|
||||
bulk_import_entity_id: entity.id,
|
||||
bulk_import_entity_type: 'group_entity',
|
||||
transformer: 'BulkImports::Transformer'
|
||||
pipeline_class: 'BulkImports::MyPipeline',
|
||||
pipeline_step: :loader,
|
||||
step_class: 'BulkImports::Loader'
|
||||
)
|
||||
expect(logger).to receive(:info)
|
||||
.with(
|
||||
bulk_import_entity_id: entity.id,
|
||||
bulk_import_entity_type: 'group_entity',
|
||||
loader: 'BulkImports::Loader'
|
||||
message: 'Pipeline finished',
|
||||
pipeline_class: 'BulkImports::MyPipeline'
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
@ -38,7 +38,8 @@ RSpec.describe Gitlab::SidekiqLogging::StructuredLogger do
|
|||
'pid' => Process.pid,
|
||||
'created_at' => created_at.to_f,
|
||||
'enqueued_at' => created_at.to_f,
|
||||
'scheduling_latency_s' => scheduling_latency_s
|
||||
'scheduling_latency_s' => scheduling_latency_s,
|
||||
'job_size_bytes' => be > 0
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
@ -40,7 +40,8 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
|
|||
'code_review',
|
||||
'terraform',
|
||||
'ci_templates',
|
||||
'quickactions'
|
||||
'quickactions',
|
||||
'pipeline_authoring'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1324,7 +1324,9 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
|
|||
subject { described_class.redis_hll_counters }
|
||||
|
||||
let(:categories) { ::Gitlab::UsageDataCounters::HLLRedisCounter.categories }
|
||||
let(:ineligible_total_categories) { %w[source_code ci_secrets_management incident_management_alerts snippets terraform] }
|
||||
let(:ineligible_total_categories) do
|
||||
%w[source_code ci_secrets_management incident_management_alerts snippets terraform pipeline_authoring]
|
||||
end
|
||||
|
||||
it 'has all known_events' do
|
||||
expect(subject).to have_key(:redis_hll_counters)
|
||||
|
|
|
@ -1139,7 +1139,7 @@ RSpec.describe API::Projects do
|
|||
let!(:public_project) { create(:project, :public, name: 'public_project', creator_id: user4.id, namespace: user4.namespace) }
|
||||
|
||||
it 'returns error when user not found' do
|
||||
get api('/users/0/projects/')
|
||||
get api("/users/#{non_existing_record_id}/projects/")
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
expect(json_response['message']).to eq('404 User Not Found')
|
||||
|
@ -2154,7 +2154,7 @@ RSpec.describe API::Projects do
|
|||
end
|
||||
|
||||
it 'fails if forked_from project which does not exist' do
|
||||
post api("/projects/#{project_fork_target.id}/fork/0", admin)
|
||||
post api("/projects/#{project_fork_target.id}/fork/#{non_existing_record_id}", admin)
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
|
||||
|
@ -2398,7 +2398,7 @@ RSpec.describe API::Projects do
|
|||
end
|
||||
|
||||
it 'returns a 404 error when project does not exist' do
|
||||
delete api("/projects/123/share/#{non_existing_record_id}", user)
|
||||
delete api("/projects/#{non_existing_record_id}/share/#{non_existing_record_id}", user)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
|
@ -2955,7 +2955,7 @@ RSpec.describe API::Projects do
|
|||
end
|
||||
|
||||
it 'returns the proper security headers' do
|
||||
get api('/projects/1/starrers', current_user)
|
||||
get api("/projects/#{public_project.id}/starrers", current_user)
|
||||
|
||||
expect(response).to include_security_headers
|
||||
end
|
||||
|
@ -3028,7 +3028,7 @@ RSpec.describe API::Projects do
|
|||
end
|
||||
|
||||
it 'returns not_found(404) for not existing project' do
|
||||
get api("/projects/0/languages", user)
|
||||
get api("/projects/#{non_existing_record_id}/languages", user)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
|
@ -3079,7 +3079,7 @@ RSpec.describe API::Projects do
|
|||
end
|
||||
|
||||
it 'does not remove a non existing project' do
|
||||
delete api('/projects/1328', user)
|
||||
delete api("/projects/#{non_existing_record_id}", user)
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
|
||||
|
@ -3098,7 +3098,7 @@ RSpec.describe API::Projects do
|
|||
end
|
||||
|
||||
it 'does not remove a non existing project' do
|
||||
delete api('/projects/1328', admin)
|
||||
delete api("/projects/#{non_existing_record_id}", admin)
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
|
||||
|
|
|
@ -18,9 +18,7 @@ RSpec.describe 'Project noteable notes' do
|
|||
login_as(user)
|
||||
end
|
||||
|
||||
it 'does not set a Gitlab::EtagCaching ETag if there is a note' do
|
||||
create(:note_on_merge_request, noteable: merge_request, project: merge_request.project)
|
||||
|
||||
it 'does not set a Gitlab::EtagCaching ETag' do
|
||||
get notes_path
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
|
@ -29,12 +27,5 @@ RSpec.describe 'Project noteable notes' do
|
|||
# interfere with notes pagination
|
||||
expect(response_etag).not_to eq(stored_etag)
|
||||
end
|
||||
|
||||
it 'sets a Gitlab::EtagCaching ETag if there is no note' do
|
||||
get notes_path
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response_etag).to eq(stored_etag)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -93,12 +93,12 @@ RSpec.describe Git::BranchHooksService do
|
|||
describe 'Push Event' do
|
||||
let(:event) { Event.pushed_action.first }
|
||||
|
||||
before do
|
||||
service.execute
|
||||
end
|
||||
subject(:execute_service) { service.execute }
|
||||
|
||||
context "with an existing branch" do
|
||||
it 'generates a push event with one commit' do
|
||||
execute_service
|
||||
|
||||
expect(event).to be_an_instance_of(PushEvent)
|
||||
expect(event.project).to eq(project)
|
||||
expect(event).to be_pushed_action
|
||||
|
@ -109,12 +109,87 @@ RSpec.describe Git::BranchHooksService do
|
|||
expect(event.push_event_payload.ref).to eq('master')
|
||||
expect(event.push_event_payload.commit_count).to eq(1)
|
||||
end
|
||||
|
||||
context 'with changing CI config' do
|
||||
before do
|
||||
allow_next_instance_of(Gitlab::Git::Diff) do |diff|
|
||||
allow(diff).to receive(:new_path).and_return('.gitlab-ci.yml')
|
||||
end
|
||||
|
||||
allow(Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:track_event)
|
||||
end
|
||||
|
||||
let!(:commit_author) { create(:user, email: sample_commit.author_email) }
|
||||
|
||||
let(:tracking_params) do
|
||||
['o_pipeline_authoring_unique_users_committing_ciconfigfile', values: commit_author.id]
|
||||
end
|
||||
|
||||
it 'tracks the event' do
|
||||
execute_service
|
||||
|
||||
expect(Gitlab::UsageDataCounters::HLLRedisCounter)
|
||||
.to have_received(:track_event).with(*tracking_params)
|
||||
end
|
||||
|
||||
context 'when the FF usage_data_unique_users_committing_ciconfigfile is disabled' do
|
||||
before do
|
||||
stub_feature_flags(usage_data_unique_users_committing_ciconfigfile: false)
|
||||
end
|
||||
|
||||
it 'does not track the event' do
|
||||
execute_service
|
||||
|
||||
expect(Gitlab::UsageDataCounters::HLLRedisCounter)
|
||||
.not_to have_received(:track_event).with(*tracking_params)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when usage ping is disabled' do
|
||||
before do
|
||||
stub_application_setting(usage_ping_enabled: false)
|
||||
end
|
||||
|
||||
it 'does not track the event' do
|
||||
execute_service
|
||||
|
||||
expect(Gitlab::UsageDataCounters::HLLRedisCounter)
|
||||
.not_to have_received(:track_event).with(*tracking_params)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the branch is not the main branch' do
|
||||
let(:branch) { 'feature' }
|
||||
|
||||
it 'does not track the event' do
|
||||
execute_service
|
||||
|
||||
expect(Gitlab::UsageDataCounters::HLLRedisCounter)
|
||||
.not_to have_received(:track_event).with(*tracking_params)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the CI config is a different path' do
|
||||
before do
|
||||
project.ci_config_path = 'config/ci.yml'
|
||||
end
|
||||
|
||||
it 'does not track the event' do
|
||||
execute_service
|
||||
|
||||
expect(Gitlab::UsageDataCounters::HLLRedisCounter)
|
||||
.not_to have_received(:track_event).with(*tracking_params)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "with a new branch" do
|
||||
let(:oldrev) { Gitlab::Git::BLANK_SHA }
|
||||
|
||||
it 'generates a push event with more than one commit' do
|
||||
execute_service
|
||||
|
||||
expect(event).to be_an_instance_of(PushEvent)
|
||||
expect(event.project).to eq(project)
|
||||
expect(event).to be_pushed_action
|
||||
|
@ -131,6 +206,8 @@ RSpec.describe Git::BranchHooksService do
|
|||
let(:newrev) { Gitlab::Git::BLANK_SHA }
|
||||
|
||||
it 'generates a push event with no commits' do
|
||||
execute_service
|
||||
|
||||
expect(event).to be_an_instance_of(PushEvent)
|
||||
expect(event.project).to eq(project)
|
||||
expect(event).to be_pushed_action
|
||||
|
|
|
@ -220,7 +220,7 @@ RSpec.configure do |config|
|
|||
|
||||
# Merge request widget GraphQL requests are disabled in the tests
|
||||
# for now whilst we migrate as much as we can over the GraphQL
|
||||
stub_feature_flags(merge_request_widget_graphql: false)
|
||||
# stub_feature_flags(merge_request_widget_graphql: false)
|
||||
|
||||
# Using FortiAuthenticator as OTP provider is disabled by default in
|
||||
# tests, until we introduce it in user settings
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'projects/tree/_tree_row' do
|
||||
let(:project) { create(:project, :repository) }
|
||||
let(:repository) { project.repository }
|
||||
|
||||
# rubocop: disable Rails/FindBy
|
||||
# This is not ActiveRecord where..first
|
||||
let(:blob_item) { Gitlab::Git::Tree.where(repository, SeedRepo::Commit::ID, 'files/ruby').first }
|
||||
# rubocop: enable Rails/FindBy
|
||||
|
||||
before do
|
||||
assign(:project, project)
|
||||
assign(:repository, repository)
|
||||
assign(:id, File.join('master', ''))
|
||||
assign(:lfs_blob_ids, [])
|
||||
end
|
||||
|
||||
it 'renders blob item' do
|
||||
render_partial(blob_item)
|
||||
|
||||
expect(rendered).to have_content(blob_item.name)
|
||||
expect(rendered).not_to have_selector('.label-lfs', text: 'LFS')
|
||||
end
|
||||
|
||||
describe 'LFS blob' do
|
||||
before do
|
||||
assign(:lfs_blob_ids, [blob_item].map(&:id))
|
||||
|
||||
render_partial(blob_item)
|
||||
end
|
||||
|
||||
it 'renders LFS badge' do
|
||||
expect(rendered).to have_selector('.label-lfs', text: 'LFS')
|
||||
end
|
||||
end
|
||||
|
||||
def render_partial(items)
|
||||
render partial: 'projects/tree/tree_row', collection: [items].flatten
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue