Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
00c8da9174
commit
213bd7e9d3
|
@ -31,7 +31,6 @@ export default () => {
|
|||
return createElement(CommitPipelinesTable, {
|
||||
props: {
|
||||
endpoint: pipelineTableViewEl.dataset.endpoint,
|
||||
helpPagePath: pipelineTableViewEl.dataset.helpPagePath,
|
||||
emptyStateSvgPath: pipelineTableViewEl.dataset.emptyStateSvgPath,
|
||||
errorStateSvgPath: pipelineTableViewEl.dataset.errorStateSvgPath,
|
||||
},
|
||||
|
|
|
@ -25,10 +25,6 @@ export default {
|
|||
type: String,
|
||||
required: true,
|
||||
},
|
||||
helpPagePath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
errorStateSvgPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
|
|
|
@ -35,7 +35,9 @@ export default {
|
|||
this.track(this.$options.dismissEvent);
|
||||
},
|
||||
trackOnShow() {
|
||||
if (!this.isDismissed) this.track(this.$options.displayEvent);
|
||||
this.$nextTick(() => {
|
||||
if (!this.isDismissed) this.track(this.$options.displayEvent);
|
||||
});
|
||||
},
|
||||
addTrackingAttributesToButton() {
|
||||
if (this.$refs.banner === undefined) return;
|
||||
|
|
|
@ -32,7 +32,7 @@ export default {
|
|||
SafeHtml,
|
||||
},
|
||||
computed: {
|
||||
...mapState(['pipelinesEmptyStateSvgPath', 'links']),
|
||||
...mapState(['pipelinesEmptyStateSvgPath']),
|
||||
...mapGetters(['currentProject']),
|
||||
...mapGetters('pipelines', ['jobsCount', 'failedJobsCount', 'failedStages', 'pipelineFailed']),
|
||||
...mapState('pipelines', [
|
||||
|
@ -85,7 +85,6 @@ export default {
|
|||
</header>
|
||||
<empty-state
|
||||
v-if="!latestPipeline"
|
||||
:help-page-path="links.ciHelpPagePath"
|
||||
:empty-state-svg-path="pipelinesEmptyStateSvgPath"
|
||||
:can-set-ci="true"
|
||||
class="mb-auto mt-auto"
|
||||
|
|
|
@ -53,7 +53,6 @@ export function initIde(el, options = {}) {
|
|||
promotionSvgPath: el.dataset.promotionSvgPath,
|
||||
});
|
||||
this.setLinks({
|
||||
ciHelpPagePath: el.dataset.ciHelpPagePath,
|
||||
webIDEHelpPagePath: el.dataset.webIdeHelpPagePath,
|
||||
});
|
||||
this.setInitialData({
|
||||
|
|
|
@ -51,7 +51,7 @@ export const NO_ISSUE_TEMPLATE_SELECTED = { key: '', name: __('No template selec
|
|||
export const TAKING_INCIDENT_ACTION_DOCS_LINK =
|
||||
'/help/operations/metrics/alerts#trigger-actions-from-alerts';
|
||||
export const ISSUE_TEMPLATES_DOCS_LINK =
|
||||
'/help/user/project/description_templates#creating-issue-templates';
|
||||
'/help/user/project/description_templates#create-an-issue-template';
|
||||
|
||||
/* PagerDuty integration settings constants */
|
||||
|
||||
|
|
|
@ -307,7 +307,7 @@ export default {
|
|||
});
|
||||
},
|
||||
|
||||
updateAndShowForm(templates = []) {
|
||||
updateAndShowForm(templates = {}) {
|
||||
if (!this.showForm) {
|
||||
this.showForm = true;
|
||||
this.store.setFormState({
|
||||
|
|
|
@ -13,9 +13,9 @@ export default {
|
|||
required: true,
|
||||
},
|
||||
issuableTemplates: {
|
||||
type: Array,
|
||||
type: [Object, Array],
|
||||
required: false,
|
||||
default: () => [],
|
||||
default: () => {},
|
||||
},
|
||||
projectPath: {
|
||||
type: String,
|
||||
|
|
|
@ -26,9 +26,9 @@ export default {
|
|||
required: true,
|
||||
},
|
||||
issuableTemplates: {
|
||||
type: Array,
|
||||
type: [Object, Array],
|
||||
required: false,
|
||||
default: () => [],
|
||||
default: () => {},
|
||||
},
|
||||
issuableType: {
|
||||
type: String,
|
||||
|
@ -72,7 +72,7 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
hasIssuableTemplates() {
|
||||
return this.issuableTemplates.length;
|
||||
return Object.values(Object(this.issuableTemplates)).length;
|
||||
},
|
||||
showLockedWarning() {
|
||||
return this.formState.lockedWarningVisible && !this.formState.updateLoading;
|
||||
|
|
|
@ -11,7 +11,7 @@ export default class Store {
|
|||
lockedWarningVisible: false,
|
||||
updateLoading: false,
|
||||
lock_version: 0,
|
||||
issuableTemplates: [],
|
||||
issuableTemplates: {},
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -361,7 +361,6 @@ export default class MergeRequestTabs {
|
|||
return createElement(CommitPipelinesTable, {
|
||||
props: {
|
||||
endpoint: pipelineTableViewEl.dataset.endpoint,
|
||||
helpPagePath: pipelineTableViewEl.dataset.helpPagePath,
|
||||
emptyStateSvgPath: pipelineTableViewEl.dataset.emptyStateSvgPath,
|
||||
errorStateSvgPath: pipelineTableViewEl.dataset.errorStateSvgPath,
|
||||
canCreatePipelineInTargetProject: Boolean(
|
||||
|
|
|
@ -30,6 +30,10 @@ export default {
|
|||
type: String,
|
||||
required: true,
|
||||
},
|
||||
statsUrl: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
detailedMetrics: [
|
||||
{
|
||||
|
@ -169,6 +173,9 @@ export default {
|
|||
class="ml-auto"
|
||||
@change-current-request="changeCurrentRequest"
|
||||
/>
|
||||
<div v-if="statsUrl" id="peek-stats" class="view">
|
||||
<a class="gl-text-blue-300" :href="statsUrl">{{ s__('PerformanceBar|Stats') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -29,6 +29,7 @@ const initPerformanceBar = (el) => {
|
|||
requestId: performanceBarData.requestId,
|
||||
peekUrl: performanceBarData.peekUrl,
|
||||
profileUrl: performanceBarData.profileUrl,
|
||||
statsUrl: performanceBarData.statsUrl,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
|
@ -119,6 +120,7 @@ const initPerformanceBar = (el) => {
|
|||
requestId: this.requestId,
|
||||
peekUrl: this.peekUrl,
|
||||
profileUrl: this.profileUrl,
|
||||
statsUrl: this.statsUrl,
|
||||
},
|
||||
on: {
|
||||
'add-request': this.addRequestManually,
|
||||
|
|
|
@ -1,12 +1,35 @@
|
|||
<script>
|
||||
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
import PipelineStatus from './pipeline_status.vue';
|
||||
import ValidationSegment from './validation_segment.vue';
|
||||
|
||||
const baseClasses = ['gl-p-5', 'gl-bg-gray-10', 'gl-border-solid', 'gl-border-gray-100'];
|
||||
|
||||
const pipelineStatusClasses = [
|
||||
...baseClasses,
|
||||
'gl-border-1',
|
||||
'gl-border-b-0!',
|
||||
'gl-rounded-top-base',
|
||||
];
|
||||
|
||||
const validationSegmentClasses = [...baseClasses, 'gl-border-1', 'gl-rounded-base'];
|
||||
|
||||
const validationSegmentWithPipelineStatusClasses = [
|
||||
...baseClasses,
|
||||
'gl-border-1',
|
||||
'gl-rounded-bottom-left-base',
|
||||
'gl-rounded-bottom-right-base',
|
||||
];
|
||||
|
||||
export default {
|
||||
validationSegmentClasses:
|
||||
'gl-p-5 gl-bg-gray-10 gl-border-solid gl-border-1 gl-border-gray-100 gl-rounded-base',
|
||||
pipelineStatusClasses,
|
||||
validationSegmentClasses,
|
||||
validationSegmentWithPipelineStatusClasses,
|
||||
components: {
|
||||
PipelineStatus,
|
||||
ValidationSegment,
|
||||
},
|
||||
mixins: [glFeatureFlagsMixin()],
|
||||
props: {
|
||||
ciConfigData: {
|
||||
type: Object,
|
||||
|
@ -17,12 +40,25 @@ export default {
|
|||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
showPipelineStatus() {
|
||||
return this.glFeatures.pipelineStatusForPipelineEditor;
|
||||
},
|
||||
// make sure corners are rounded correctly depending on if
|
||||
// pipeline status is rendered
|
||||
validationStyling() {
|
||||
return this.showPipelineStatus
|
||||
? this.$options.validationSegmentWithPipelineStatusClasses
|
||||
: this.$options.validationSegmentClasses;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div class="gl-mb-5">
|
||||
<pipeline-status v-if="showPipelineStatus" :class="$options.pipelineStatusClasses" />
|
||||
<validation-segment
|
||||
:class="$options.validationSegmentClasses"
|
||||
:class="validationStyling"
|
||||
:loading="isCiConfigDataLoading"
|
||||
:ci-config="ciConfigData"
|
||||
/>
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
<script>
|
||||
import { GlIcon, GlLink, GlLoadingIcon, GlSprintf } from '@gitlab/ui';
|
||||
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||
import { s__ } from '~/locale';
|
||||
import getCommitSha from '~/pipeline_editor/graphql/queries/client/commit_sha.graphql';
|
||||
import getPipelineQuery from '~/pipeline_editor/graphql/queries/client/pipeline.graphql';
|
||||
import CiIcon from '~/vue_shared/components/ci_icon.vue';
|
||||
|
||||
const POLL_INTERVAL = 10000;
|
||||
export const i18n = {
|
||||
fetchError: s__('Pipeline|We are currently unable to fetch pipeline data'),
|
||||
fetchLoading: s__('Pipeline|Checking pipeline status'),
|
||||
pipelineInfo: s__(
|
||||
`Pipeline|Pipeline %{idStart}#%{idEnd} %{statusStart}%{statusEnd} for %{commitStart}%{commitEnd}`,
|
||||
),
|
||||
};
|
||||
|
||||
export default {
|
||||
i18n,
|
||||
components: {
|
||||
CiIcon,
|
||||
GlIcon,
|
||||
GlLink,
|
||||
GlLoadingIcon,
|
||||
GlSprintf,
|
||||
},
|
||||
inject: ['projectFullPath'],
|
||||
apollo: {
|
||||
commitSha: {
|
||||
query: getCommitSha,
|
||||
},
|
||||
pipeline: {
|
||||
query: getPipelineQuery,
|
||||
variables() {
|
||||
return {
|
||||
fullPath: this.projectFullPath,
|
||||
sha: this.commitSha,
|
||||
};
|
||||
},
|
||||
update: (data) => {
|
||||
const { id, commitPath = '', shortSha = '', detailedStatus = {} } =
|
||||
data.project?.pipeline || {};
|
||||
|
||||
return {
|
||||
id,
|
||||
commitPath,
|
||||
shortSha,
|
||||
detailedStatus,
|
||||
};
|
||||
},
|
||||
error() {
|
||||
this.hasError = true;
|
||||
},
|
||||
pollInterval: POLL_INTERVAL,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
hasError: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
hasPipelineData() {
|
||||
return Boolean(this.$apollo.queries.pipeline?.id);
|
||||
},
|
||||
isQueryLoading() {
|
||||
return this.$apollo.queries.pipeline.loading && !this.hasPipelineData;
|
||||
},
|
||||
status() {
|
||||
return this.pipeline.detailedStatus;
|
||||
},
|
||||
pipelineId() {
|
||||
return getIdFromGraphQLId(this.pipeline.id);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="gl-white-space-nowrap gl-max-w-full">
|
||||
<template v-if="isQueryLoading">
|
||||
<gl-loading-icon class="gl-mr-auto gl-display-inline-block" size="sm" />
|
||||
<span data-testid="pipeline-loading-msg">{{ $options.i18n.fetchLoading }}</span>
|
||||
</template>
|
||||
<template v-else-if="hasError">
|
||||
<gl-icon class="gl-mr-auto" name="warning-solid" />
|
||||
<span data-testid="pipeline-error-msg">{{ $options.i18n.fetchError }}</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a :href="status.detailsPath" class="gl-mr-auto">
|
||||
<ci-icon :status="status" :size="18" />
|
||||
</a>
|
||||
<span class="gl-font-weight-bold">
|
||||
<gl-sprintf :message="$options.i18n.pipelineInfo">
|
||||
<template #id="{ content }">
|
||||
<gl-link
|
||||
:href="status.detailsPath"
|
||||
class="pipeline-id gl-font-weight-normal pipeline-number"
|
||||
target="_blank"
|
||||
data-testid="pipeline-id"
|
||||
>
|
||||
{{ content }}{{ pipelineId }}</gl-link
|
||||
>
|
||||
</template>
|
||||
<template #status>{{ status.text }}</template>
|
||||
<template #commit>
|
||||
<gl-link
|
||||
:href="pipeline.commitPath"
|
||||
class="commit-sha gl-font-weight-normal"
|
||||
target="_blank"
|
||||
data-testid="pipeline-commit"
|
||||
>
|
||||
{{ pipeline.shortSha }}
|
||||
</gl-link>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</span>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,17 @@
|
|||
query getPipeline($fullPath: ID!, $sha: String!) {
|
||||
project(fullPath: $fullPath) @client {
|
||||
pipeline(sha: $sha) {
|
||||
commitPath
|
||||
id
|
||||
iid
|
||||
shortSha
|
||||
status
|
||||
detailedStatus {
|
||||
detailsPath
|
||||
icon
|
||||
group
|
||||
text
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,6 +11,29 @@ export const resolvers = {
|
|||
}),
|
||||
};
|
||||
},
|
||||
|
||||
/* eslint-disable @gitlab/require-i18n-strings */
|
||||
project() {
|
||||
return {
|
||||
__typename: 'Project',
|
||||
pipeline: {
|
||||
__typename: 'Pipeline',
|
||||
commitPath: `/-/commit/aabbccdd`,
|
||||
id: 'gid://gitlab/Ci::Pipeline/118',
|
||||
iid: '28',
|
||||
shortSha: 'aabbccdd',
|
||||
status: 'SUCCESS',
|
||||
detailedStatus: {
|
||||
__typename: 'DetailedStatus',
|
||||
detailsPath: '/root/sample-ci-project/-/pipelines/118"',
|
||||
group: 'success',
|
||||
icon: 'status_success',
|
||||
text: 'passed',
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
/* eslint-enable @gitlab/require-i18n-strings */
|
||||
},
|
||||
Mutation: {
|
||||
lintCI: (_, { endpoint, content, dry_run }) => {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<script>
|
||||
import { GlButton } from '@gitlab/ui';
|
||||
import { helpPagePath } from '~/helpers/help_page_helper';
|
||||
import { s__ } from '~/locale';
|
||||
|
||||
export default {
|
||||
|
@ -14,10 +15,6 @@ export default {
|
|||
GlButton,
|
||||
},
|
||||
props: {
|
||||
helpPagePath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
emptyStateSvgPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
|
@ -27,6 +24,11 @@ export default {
|
|||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
ciHelpPagePath() {
|
||||
return helpPagePath('ci/quick_start/index.md');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
|
@ -47,7 +49,7 @@ export default {
|
|||
|
||||
<div class="gl-text-center">
|
||||
<gl-button
|
||||
:href="helpPagePath"
|
||||
:href="ciHelpPagePath"
|
||||
variant="info"
|
||||
category="primary"
|
||||
data-testid="get-started-pipelines"
|
||||
|
|
|
@ -52,10 +52,6 @@ export default {
|
|||
required: false,
|
||||
default: '',
|
||||
},
|
||||
helpPagePath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
emptyStateSvgPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
|
@ -333,7 +329,6 @@ export default {
|
|||
|
||||
<empty-state
|
||||
v-else-if="stateToRender === $options.stateMap.emptyState"
|
||||
:help-page-path="helpPagePath"
|
||||
:empty-state-svg-path="emptyStateSvgPath"
|
||||
:can-set-ci="canCreatePipeline"
|
||||
/>
|
||||
|
|
|
@ -23,7 +23,6 @@ export const initPipelinesIndex = (selector = '#pipelines-list-vue') => {
|
|||
const {
|
||||
endpoint,
|
||||
pipelineScheduleUrl,
|
||||
helpPagePath,
|
||||
emptyStateSvgPath,
|
||||
errorStateSvgPath,
|
||||
noPipelinesSvgPath,
|
||||
|
@ -55,7 +54,6 @@ export const initPipelinesIndex = (selector = '#pipelines-list-vue') => {
|
|||
store: this.store,
|
||||
endpoint,
|
||||
pipelineScheduleUrl,
|
||||
helpPagePath,
|
||||
emptyStateSvgPath,
|
||||
errorStateSvgPath,
|
||||
noPipelinesSvgPath,
|
||||
|
|
|
@ -69,21 +69,21 @@ export default {
|
|||
<gl-button category="primary" variant="success" class="gl-ml-3" @click="onSubmit">
|
||||
{{ s__('CompareRevisions|Compare') }}
|
||||
</gl-button>
|
||||
<a
|
||||
<gl-button
|
||||
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
|
||||
</gl-button>
|
||||
<gl-button
|
||||
v-else-if="createMrPath"
|
||||
:href="createMrPath"
|
||||
data-testid="createMrButton"
|
||||
class="btn btn-default gl-button gl-ml-3"
|
||||
>
|
||||
{{ s__('CompareRevisions|Create merge request') }}
|
||||
</a>
|
||||
</gl-button>
|
||||
</form>
|
||||
</template>
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
<script>
|
||||
/* eslint-disable vue/no-v-html */
|
||||
import { GlButton } from '@gitlab/ui';
|
||||
import { joinPaths } from '~/lib/utils/url_utility';
|
||||
import { sprintf, s__ } from '../../../locale';
|
||||
|
||||
export default {
|
||||
name: 'TimeTrackingHelpState',
|
||||
components: {
|
||||
GlButton,
|
||||
},
|
||||
computed: {
|
||||
href() {
|
||||
return joinPaths(gon.relative_url_root || '', '/help/user/project/time_tracking.md');
|
||||
|
@ -40,7 +44,7 @@ export default {
|
|||
<p>{{ __('Quick actions can be used in the issues description and comment boxes.') }}</p>
|
||||
<p v-html="estimateText"></p>
|
||||
<p v-html="spendText"></p>
|
||||
<a :href="href" class="btn btn-default learn-more-button"> {{ __('Learn more') }} </a>
|
||||
<gl-button :href="href">{{ __('Learn more') }}</gl-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -1,5 +1,13 @@
|
|||
import { omitBy, isUndefined } from 'lodash';
|
||||
|
||||
export const STANDARD_CONTEXT = {
|
||||
schema: 'iglu:com.gitlab/gitlab_standard/jsonschema/1-0-3',
|
||||
data: {
|
||||
environment: process.env.NODE_ENV,
|
||||
source: 'gitlab-javascript',
|
||||
},
|
||||
};
|
||||
|
||||
const DEFAULT_SNOWPLOW_OPTIONS = {
|
||||
namespace: 'gl',
|
||||
hostname: window.location.hostname,
|
||||
|
@ -67,8 +75,13 @@ export default class Tracking {
|
|||
// eslint-disable-next-line @gitlab/require-i18n-strings
|
||||
if (!category) throw new Error('Tracking: no category provided for tracking.');
|
||||
|
||||
const { label, property, value, context } = data;
|
||||
const contexts = context ? [context] : undefined;
|
||||
const { label, property, value } = data;
|
||||
const contexts = [STANDARD_CONTEXT];
|
||||
|
||||
if (data.context) {
|
||||
contexts.push(data.context);
|
||||
}
|
||||
|
||||
return window.snowplow('trackStructEvent', category, action, label, property, value, contexts);
|
||||
}
|
||||
|
||||
|
@ -134,7 +147,8 @@ export function initDefaultTrackers() {
|
|||
if (!Tracking.enabled()) return;
|
||||
|
||||
window.snowplow('enableActivityTracking', 30, 30);
|
||||
window.snowplow('trackPageView'); // must be after enableActivityTracking
|
||||
// must be after enableActivityTracking
|
||||
window.snowplow('trackPageView', null, [STANDARD_CONTEXT]);
|
||||
|
||||
if (window.snowplowOptions.formTracking) window.snowplow('enableFormTracking');
|
||||
if (window.snowplowOptions.linkClickTracking) window.snowplow('enableLinkClickTracking');
|
||||
|
|
|
@ -59,11 +59,33 @@ const populateUserInfo = (user) => {
|
|||
};
|
||||
|
||||
const initializedPopovers = new Map();
|
||||
let domObservedForChanges = false;
|
||||
|
||||
export default (elements = document.querySelectorAll('.js-user-link')) => {
|
||||
const addPopoversToModifiedTree = new MutationObserver(() => {
|
||||
const userLinks = document?.querySelectorAll('.js-user-link, .gfm-project_member');
|
||||
|
||||
if (userLinks) {
|
||||
addPopovers(userLinks); /* eslint-disable-line no-use-before-define */
|
||||
}
|
||||
});
|
||||
|
||||
function observeBody() {
|
||||
if (!domObservedForChanges) {
|
||||
addPopoversToModifiedTree.observe(document.body, {
|
||||
subtree: true,
|
||||
childList: true,
|
||||
});
|
||||
|
||||
domObservedForChanges = true;
|
||||
}
|
||||
}
|
||||
|
||||
export default function addPopovers(elements = document.querySelectorAll('.js-user-link')) {
|
||||
const userLinks = Array.from(elements);
|
||||
const UserPopoverComponent = Vue.extend(UserPopover);
|
||||
|
||||
observeBody();
|
||||
|
||||
return userLinks
|
||||
.filter(({ dataset }) => dataset.user || dataset.userId)
|
||||
.map((el) => {
|
||||
|
@ -105,4 +127,4 @@ export default (elements = document.querySelectorAll('.js-user-link')) => {
|
|||
|
||||
return renderedPopover;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ class Projects::Ci::PipelineEditorController < Projects::ApplicationController
|
|||
before_action do
|
||||
push_frontend_feature_flag(:ci_config_visualization_tab, @project, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:ci_config_merged_tab, @project, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:pipeline_status_for_pipeline_editor, @project, default_enabled: :yaml)
|
||||
end
|
||||
|
||||
feature_category :pipeline_authoring
|
||||
|
|
|
@ -25,7 +25,7 @@ class Projects::TemplatesController < Projects::ApplicationController
|
|||
|
||||
def names
|
||||
respond_to do |format|
|
||||
format.json { render json: TemplateFinder.all_template_names_array(project, params[:template_type].to_s.pluralize) }
|
||||
format.json { render json: TemplateFinder.all_template_names_hash_or_array(project, params[:template_type].to_s) }
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -22,16 +22,26 @@ class TemplateFinder
|
|||
end
|
||||
end
|
||||
|
||||
# This is temporary and will be removed once we introduce group level inherited templates and
|
||||
# remove the inherited_issuable_templates FF
|
||||
def all_template_names_hash_or_array(project, issuable_type)
|
||||
if project.inherited_issuable_templates_enabled?
|
||||
all_template_names(project, issuable_type.pluralize)
|
||||
else
|
||||
all_template_names_array(project, issuable_type.pluralize)
|
||||
end
|
||||
end
|
||||
|
||||
def all_template_names(project, type)
|
||||
return {} if !VENDORED_TEMPLATES.key?(type.to_s) && type.to_s != 'licenses'
|
||||
|
||||
build(type, project).template_names
|
||||
end
|
||||
|
||||
# This is issues and merge requests description templates only.
|
||||
# This will be removed once we introduce group level inherited templates
|
||||
# This is for issues and merge requests description templates only.
|
||||
# This will be removed once we introduce group level inherited templates and remove the inherited_issuable_templates FF
|
||||
def all_template_names_array(project, type)
|
||||
all_template_names(project, type).values.flatten.uniq
|
||||
all_template_names(project, type).values.flatten.select { |tmpl| tmpl[:project_id] == project.id }.compact.uniq
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Resolvers
|
||||
module AlertManagement
|
||||
class HttpIntegrationsResolver < BaseResolver
|
||||
alias_method :project, :synchronized_object
|
||||
|
||||
type Types::AlertManagement::HttpIntegrationType.connection_type, null: true
|
||||
|
||||
def resolve(**args)
|
||||
http_integrations
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def http_integrations
|
||||
return [] unless Ability.allowed?(current_user, :admin_operations, project)
|
||||
|
||||
::AlertManagement::HttpIntegrationsFinder.new(project, {}).execute
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -20,3 +20,5 @@ module Types
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
Types::AlertManagement::HttpIntegrationType.prepend_ee_mod
|
||||
|
|
|
@ -273,6 +273,12 @@ module Types
|
|||
description: 'Integrations which can receive alerts for the project.',
|
||||
resolver: Resolvers::AlertManagement::IntegrationsResolver
|
||||
|
||||
field :alert_management_http_integrations,
|
||||
Types::AlertManagement::HttpIntegrationType.connection_type,
|
||||
null: true,
|
||||
description: 'HTTP Integrations which can receive alerts for the project.',
|
||||
resolver: Resolvers::AlertManagement::HttpIntegrationsResolver
|
||||
|
||||
field :releases,
|
||||
Types::ReleaseType.connection_type,
|
||||
null: true,
|
||||
|
|
|
@ -5,7 +5,8 @@ module IssuablesDescriptionTemplatesHelper
|
|||
include GitlabRoutingHelper
|
||||
|
||||
def template_dropdown_tag(issuable, &block)
|
||||
title = selected_template(issuable) || "Choose a template"
|
||||
selected_template = selected_template(issuable)
|
||||
title = selected_template || "Choose a template"
|
||||
options = {
|
||||
toggle_class: 'js-issuable-selector',
|
||||
title: title,
|
||||
|
@ -15,7 +16,7 @@ module IssuablesDescriptionTemplatesHelper
|
|||
data: {
|
||||
data: issuable_templates(ref_project, issuable.to_ability_name),
|
||||
field_name: 'issuable_template',
|
||||
selected: selected_template(issuable),
|
||||
selected: selected_template,
|
||||
project_id: ref_project.id
|
||||
}
|
||||
}
|
||||
|
@ -28,15 +29,21 @@ module IssuablesDescriptionTemplatesHelper
|
|||
def issuable_templates(project, issuable_type)
|
||||
@template_types ||= {}
|
||||
@template_types[project.id] ||= {}
|
||||
@template_types[project.id][issuable_type] ||= TemplateFinder.all_template_names_array(project, issuable_type.pluralize)
|
||||
@template_types[project.id][issuable_type] ||= TemplateFinder.all_template_names_hash_or_array(project, issuable_type)
|
||||
end
|
||||
|
||||
def issuable_templates_names(issuable)
|
||||
issuable_templates(ref_project, issuable.to_ability_name).map { |template| template[:name] }
|
||||
all_templates = issuable_templates(ref_project, issuable.to_ability_name)
|
||||
|
||||
if ref_project.inherited_issuable_templates_enabled?
|
||||
all_templates.values.flatten.map { |tpl| tpl[:name] if tpl[:project_id] == ref_project.id }.compact.uniq
|
||||
else
|
||||
all_templates.map { |template| template[:name] }
|
||||
end
|
||||
end
|
||||
|
||||
def selected_template(issuable)
|
||||
params[:issuable_template] if issuable_templates(ref_project, issuable.to_ability_name).any? { |template| template[:name] == params[:issuable_template] }
|
||||
params[:issuable_template] if issuable_templates_names(issuable).any? { |tmpl_name| tmpl_name == params[:issuable_template] }
|
||||
end
|
||||
|
||||
def template_names_path(parent, issuable)
|
||||
|
|
|
@ -2532,6 +2532,10 @@ class Project < ApplicationRecord
|
|||
Projects::GitGarbageCollectWorker
|
||||
end
|
||||
|
||||
def inherited_issuable_templates_enabled?
|
||||
Feature.enabled?(:inherited_issuable_templates, self, default_enabled: :yaml)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_service(services, name)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class MattermostService < ChatNotificationService
|
||||
include ::SlackService::Notifier
|
||||
include SlackMattermost::Notifier
|
||||
|
||||
def title
|
||||
'Mattermost notifications'
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module SlackMattermost
|
||||
module Notifier
|
||||
private
|
||||
|
||||
def notify(message, opts)
|
||||
# See https://gitlab.com/gitlab-org/slack-notifier/#custom-http-client
|
||||
notifier = Slack::Messenger.new(webhook, opts.merge(http_client: HTTPClient))
|
||||
notifier.ping(
|
||||
message.pretext,
|
||||
attachments: message.attachments,
|
||||
fallback: message.fallback
|
||||
)
|
||||
end
|
||||
|
||||
class HTTPClient
|
||||
def self.post(uri, params = {})
|
||||
params.delete(:http_options) # these are internal to the client and we do not want them
|
||||
Gitlab::HTTP.post(uri, body: params)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,6 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class SlackService < ChatNotificationService
|
||||
include SlackMattermost::Notifier
|
||||
|
||||
prop_accessor EVENT_CHANNEL['alert']
|
||||
|
||||
def title
|
||||
|
@ -35,27 +37,4 @@ class SlackService < ChatNotificationService
|
|||
|
||||
super
|
||||
end
|
||||
|
||||
module Notifier
|
||||
private
|
||||
|
||||
def notify(message, opts)
|
||||
# See https://gitlab.com/gitlab-org/slack-notifier/#custom-http-client
|
||||
notifier = Slack::Messenger.new(webhook, opts.merge(http_client: HTTPClient))
|
||||
notifier.ping(
|
||||
message.pretext,
|
||||
attachments: message.attachments,
|
||||
fallback: message.fallback
|
||||
)
|
||||
end
|
||||
|
||||
class HTTPClient
|
||||
def self.post(uri, params = {})
|
||||
params.delete(:http_options) # these are internal to the client and we do not want them
|
||||
Gitlab::HTTP.post(uri, body: params)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
include Notifier
|
||||
end
|
||||
|
|
|
@ -5,6 +5,12 @@ module Issues
|
|||
include Gitlab::Routing.url_helpers
|
||||
include GitlabRoutingHelper
|
||||
|
||||
def initialize(issuables_relation, project)
|
||||
super
|
||||
|
||||
@labels = @issuables.labels_hash.transform_values { |labels| labels.sort.join(',').presence }
|
||||
end
|
||||
|
||||
def email(user)
|
||||
Notify.issues_csv_email(user, project, csv_data, csv_builder.status).deliver_now
|
||||
end
|
||||
|
@ -12,7 +18,7 @@ module Issues
|
|||
private
|
||||
|
||||
def associations_to_preload
|
||||
%i(author assignees timelogs milestone)
|
||||
%i(author assignees timelogs milestone project)
|
||||
end
|
||||
|
||||
def header_to_value_hash
|
||||
|
@ -41,7 +47,7 @@ module Issues
|
|||
end
|
||||
|
||||
def issue_labels(issue)
|
||||
issuables.labels_hash[issue.id].sort.join(',').presence
|
||||
@labels[issue.id]
|
||||
end
|
||||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
|
|
|
@ -19,6 +19,8 @@ module Projects
|
|||
|
||||
@project = Project.new(params)
|
||||
|
||||
@project.visibility_level = @project.group.visibility_level unless @project.visibility_level_allowed_by_group?
|
||||
|
||||
# If a project is newly created it should have shared runners settings
|
||||
# based on its group having it enabled. This is like the "default value"
|
||||
@project.shared_runners_enabled = false if !params.key?(:shared_runners_enabled) && @project.group && @project.group.shared_runners_setting != 'enabled'
|
||||
|
|
|
@ -2,5 +2,6 @@
|
|||
|
||||
#js-peek{ data: { env: Peek.env,
|
||||
request_id: peek_request_id,
|
||||
stats_url: ENV.fetch('GITLAB_PERFORMANCE_BAR_STATS_URL', ''),
|
||||
peek_url: "#{peek_routes_path}/results" },
|
||||
class: Peek.env }
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
- disable_initialization = local_assigns.fetch(:disable_initialization, false)
|
||||
#commit-pipeline-table-view{ data: { disable_initialization: disable_initialization,
|
||||
endpoint: endpoint,
|
||||
"help-page-path" => help_page_path('ci/quick_start/README'),
|
||||
"empty-state-svg-path" => image_path('illustrations/pipelines_empty.svg'),
|
||||
"error-state-svg-path" => image_path('illustrations/pipelines_failed.svg'),
|
||||
"project-id": @project.id,
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
#pipelines-list-vue{ data: { endpoint: project_pipelines_path(@project, format: :json),
|
||||
project_id: @project.id,
|
||||
params: params.to_json,
|
||||
"help-page-path" => help_page_path('ci/quick_start/README'),
|
||||
"pipeline-schedule-url" => pipeline_schedules_path(@project),
|
||||
"empty-state-svg-path" => image_path('illustrations/pipelines_empty.svg'),
|
||||
"error-state-svg-path" => image_path('illustrations/pipelines_failed.svg'),
|
||||
|
|
|
@ -12,21 +12,21 @@
|
|||
= render 'shared/ref_switcher', destination: 'badges', align_right: true
|
||||
.card-body
|
||||
.row
|
||||
.col-md-2.text-center
|
||||
.col-md-2.gl-text-center
|
||||
Markdown
|
||||
.col-md-10.code.js-syntax-highlight
|
||||
= highlight_badge('.md', badge.to_markdown, language: 'markdown')
|
||||
.row
|
||||
%hr
|
||||
.row
|
||||
.col-md-2.text-center
|
||||
.col-md-2.gl-text-center
|
||||
HTML
|
||||
.col-md-10.code.js-syntax-highlight
|
||||
= highlight_badge('.html', badge.to_html, language: 'html')
|
||||
.row
|
||||
%hr
|
||||
.row
|
||||
.col-md-2.text-center
|
||||
.col-md-2.gl-text-center
|
||||
AsciiDoc
|
||||
.col-md-10.code.js-syntax-highlight
|
||||
= highlight_badge('.adoc', badge.to_asciidoc)
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Send gitlab_standard context with events from the frontend
|
||||
merge_request: 52959
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Update 'Get Started with CI/CD' button with latest URL
|
||||
merge_request: 54344
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix project import error occurring due to default visibility
|
||||
merge_request: 53827
|
||||
author: Jonas Wälter @wwwjon
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Apply new GitLab UI for learn more button in time tracking
|
||||
merge_request: 54142
|
||||
author: Yogi (@yo)
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix N+1 SQL regression in exporting issues to CSV
|
||||
merge_request: 54287
|
||||
author:
|
||||
type: performance
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
title: React to new DOM nodes being added to the page to bind the user information
|
||||
popover to them
|
||||
merge_request: 54411
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: inherited_issuable_templates
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/52360
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/321247
|
||||
milestone: '13.9'
|
||||
type: development
|
||||
group: group::project management
|
||||
default_enabled: false
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: pipeline_status_for_pipeline_editor
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/53797
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/321518
|
||||
milestone: '13.10'
|
||||
type: development
|
||||
group: group::pipeline authoring
|
||||
default_enabled: false
|
|
@ -6,6 +6,8 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
|
||||
# Performance Bar **(FREE SELF)**
|
||||
|
||||
> The **Stats** field [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/271551) in GitLab SaaS 13.9.
|
||||
|
||||
You can display the GitLab Performance Bar to see statistics for the performance
|
||||
of a page. When activated, it looks as follows:
|
||||
|
||||
|
@ -53,6 +55,8 @@ From left to right, it displays:
|
|||
- **Request Selector**: a select box displayed on the right-hand side of the
|
||||
Performance Bar which enables you to view these metrics for any requests made while
|
||||
the current page was open. Only the first two requests per unique URL are captured.
|
||||
- **Stats** (optional): if the `GITLAB_PERFORMANCE_BAR_STATS_URL` environment variable is set,
|
||||
this URL is displayed in the bar. In GitLab 13.9 and later, used only in GitLab SaaS.
|
||||
|
||||
## Request warnings
|
||||
|
||||
|
|
|
@ -634,6 +634,21 @@ type AlertManagementHttpIntegration implements AlertManagementIntegration {
|
|||
"""
|
||||
name: String
|
||||
|
||||
"""
|
||||
Extract alert fields from payload example for custom mapping.
|
||||
"""
|
||||
payloadAlertFields: [AlertManagementPayloadAlertField!]
|
||||
|
||||
"""
|
||||
The custom mapping of GitLab alert attributes to fields from the payload_example.
|
||||
"""
|
||||
payloadAttributeMappings: [AlertManagementPayloadAlertMappingField!]
|
||||
|
||||
"""
|
||||
The example of an alert payload.
|
||||
"""
|
||||
payloadExample: JsonString
|
||||
|
||||
"""
|
||||
Token used to authenticate alert notification requests.
|
||||
"""
|
||||
|
@ -650,6 +665,41 @@ type AlertManagementHttpIntegration implements AlertManagementIntegration {
|
|||
url: String
|
||||
}
|
||||
|
||||
"""
|
||||
The connection type for AlertManagementHttpIntegration.
|
||||
"""
|
||||
type AlertManagementHttpIntegrationConnection {
|
||||
"""
|
||||
A list of edges.
|
||||
"""
|
||||
edges: [AlertManagementHttpIntegrationEdge]
|
||||
|
||||
"""
|
||||
A list of nodes.
|
||||
"""
|
||||
nodes: [AlertManagementHttpIntegration]
|
||||
|
||||
"""
|
||||
Information to aid in pagination.
|
||||
"""
|
||||
pageInfo: PageInfo!
|
||||
}
|
||||
|
||||
"""
|
||||
An edge in a connection.
|
||||
"""
|
||||
type AlertManagementHttpIntegrationEdge {
|
||||
"""
|
||||
A cursor for use in pagination.
|
||||
"""
|
||||
cursor: String!
|
||||
|
||||
"""
|
||||
The item at the end of the edge.
|
||||
"""
|
||||
node: AlertManagementHttpIntegration
|
||||
}
|
||||
|
||||
"""
|
||||
Identifier of AlertManagement::HttpIntegration.
|
||||
"""
|
||||
|
@ -862,6 +912,31 @@ enum AlertManagementPayloadAlertFieldType {
|
|||
STRING
|
||||
}
|
||||
|
||||
"""
|
||||
Parsed field (with its name) from an alert used for custom mappings
|
||||
"""
|
||||
type AlertManagementPayloadAlertMappingField {
|
||||
"""
|
||||
A GitLab alert field name.
|
||||
"""
|
||||
fieldName: AlertManagementPayloadAlertFieldName
|
||||
|
||||
"""
|
||||
Human-readable label of the payload path.
|
||||
"""
|
||||
label: String
|
||||
|
||||
"""
|
||||
Path to value inside payload JSON.
|
||||
"""
|
||||
path: [String!]
|
||||
|
||||
"""
|
||||
Type of the parsed value.
|
||||
"""
|
||||
type: AlertManagementPayloadAlertFieldType
|
||||
}
|
||||
|
||||
"""
|
||||
An endpoint and credentials used to accept Prometheus alerts for a project
|
||||
"""
|
||||
|
@ -19031,6 +19106,31 @@ type Project {
|
|||
statuses: [AlertManagementStatus!]
|
||||
): AlertManagementAlertConnection
|
||||
|
||||
"""
|
||||
HTTP Integrations which can receive alerts for the project.
|
||||
"""
|
||||
alertManagementHttpIntegrations(
|
||||
"""
|
||||
Returns the elements in the list that come after the specified cursor.
|
||||
"""
|
||||
after: String
|
||||
|
||||
"""
|
||||
Returns the elements in the list that come before the specified cursor.
|
||||
"""
|
||||
before: String
|
||||
|
||||
"""
|
||||
Returns the first _n_ elements from the list.
|
||||
"""
|
||||
first: Int
|
||||
|
||||
"""
|
||||
Returns the last _n_ elements from the list.
|
||||
"""
|
||||
last: Int
|
||||
): AlertManagementHttpIntegrationConnection
|
||||
|
||||
"""
|
||||
Integrations which can receive alerts for the project.
|
||||
"""
|
||||
|
|
|
@ -1578,6 +1578,64 @@
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "payloadAlertFields",
|
||||
"description": "Extract alert fields from payload example for custom mapping.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "AlertManagementPayloadAlertField",
|
||||
"ofType": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "payloadAttributeMappings",
|
||||
"description": "The custom mapping of GitLab alert attributes to fields from the payload_example.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "AlertManagementPayloadAlertMappingField",
|
||||
"ofType": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "payloadExample",
|
||||
"description": "The example of an alert payload.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "JsonString",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "token",
|
||||
"description": "Token used to authenticate alert notification requests.",
|
||||
|
@ -1636,6 +1694,118 @@
|
|||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "AlertManagementHttpIntegrationConnection",
|
||||
"description": "The connection type for AlertManagementHttpIntegration.",
|
||||
"fields": [
|
||||
{
|
||||
"name": "edges",
|
||||
"description": "A list of edges.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "AlertManagementHttpIntegrationEdge",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "nodes",
|
||||
"description": "A list of nodes.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "AlertManagementHttpIntegration",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "pageInfo",
|
||||
"description": "Information to aid in pagination.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "PageInfo",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [
|
||||
|
||||
],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "AlertManagementHttpIntegrationEdge",
|
||||
"description": "An edge in a connection.",
|
||||
"fields": [
|
||||
{
|
||||
"name": "cursor",
|
||||
"description": "A cursor for use in pagination.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "node",
|
||||
"description": "The item at the end of the edge.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "OBJECT",
|
||||
"name": "AlertManagementHttpIntegration",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [
|
||||
|
||||
],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "SCALAR",
|
||||
"name": "AlertManagementHttpIntegrationID",
|
||||
|
@ -2143,6 +2313,83 @@
|
|||
],
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "AlertManagementPayloadAlertMappingField",
|
||||
"description": "Parsed field (with its name) from an alert used for custom mappings",
|
||||
"fields": [
|
||||
{
|
||||
"name": "fieldName",
|
||||
"description": "A GitLab alert field name.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "ENUM",
|
||||
"name": "AlertManagementPayloadAlertFieldName",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"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": "OBJECT",
|
||||
"name": "AlertManagementPrometheusIntegration",
|
||||
|
@ -55864,6 +56111,59 @@
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "alertManagementHttpIntegrations",
|
||||
"description": "HTTP Integrations which can receive alerts for the project.",
|
||||
"args": [
|
||||
{
|
||||
"name": "after",
|
||||
"description": "Returns the elements in the list that come after the specified cursor.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "before",
|
||||
"description": "Returns the elements in the list that come before the specified cursor.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "first",
|
||||
"description": "Returns the first _n_ elements from the list.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Int",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "last",
|
||||
"description": "Returns the last _n_ elements from the list.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Int",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
"kind": "OBJECT",
|
||||
"name": "AlertManagementHttpIntegrationConnection",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "alertManagementIntegrations",
|
||||
"description": "Integrations which can receive alerts for the project.",
|
||||
|
|
|
@ -274,6 +274,9 @@ An endpoint and credentials used to accept alerts for a project.
|
|||
| `apiUrl` | String | URL at which Prometheus metrics can be queried to populate the metrics dashboard. |
|
||||
| `id` | ID! | ID of the integration. |
|
||||
| `name` | String | Name of the integration. |
|
||||
| `payloadAlertFields` | AlertManagementPayloadAlertField! => Array | Extract alert fields from payload example for custom mapping. |
|
||||
| `payloadAttributeMappings` | AlertManagementPayloadAlertMappingField! => Array | The custom mapping of GitLab alert attributes to fields from the payload_example. |
|
||||
| `payloadExample` | JsonString | The example of an alert payload. |
|
||||
| `token` | String | Token used to authenticate alert notification requests. |
|
||||
| `type` | AlertManagementIntegrationType! | Type of integration. |
|
||||
| `url` | String | Endpoint which accepts alert notifications. |
|
||||
|
@ -288,6 +291,17 @@ Parsed field from an alert used for custom mappings.
|
|||
| `path` | String! => Array | Path to value inside payload JSON. |
|
||||
| `type` | AlertManagementPayloadAlertFieldType | Type of the parsed value. |
|
||||
|
||||
### AlertManagementPayloadAlertMappingField
|
||||
|
||||
Parsed field (with its name) from an alert used for custom mappings.
|
||||
|
||||
| Field | Type | Description |
|
||||
| ----- | ---- | ----------- |
|
||||
| `fieldName` | AlertManagementPayloadAlertFieldName | A GitLab alert field name. |
|
||||
| `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.
|
||||
|
@ -3028,6 +3042,7 @@ Autogenerated return type of PipelineRetry.
|
|||
| `alertManagementAlert` | AlertManagementAlert | A single Alert Management alert of the project. |
|
||||
| `alertManagementAlertStatusCounts` | AlertManagementAlertStatusCountsType | Counts of alerts by status for the project. |
|
||||
| `alertManagementAlerts` | AlertManagementAlertConnection | Alert Management alerts of the project. |
|
||||
| `alertManagementHttpIntegrations` | AlertManagementHttpIntegrationConnection | HTTP Integrations which can receive alerts for 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. |
|
||||
|
|
|
@ -100,7 +100,7 @@ GET /projects/:id/templates/:type/:name
|
|||
| Attribute | Type | Required | Description |
|
||||
| ---------- | ------ | -------- | ----------- |
|
||||
| `id` | integer / string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
|
||||
| `type` | string | yes| The type `(dockerfiles|gitignores|gitlab_ci_ymls|licenses|issues|merge_requests)` of the template |
|
||||
| `type` | string | yes| The type of the template. One of: `dockerfiles`, `gitignores`, `gitlab_ci_ymls`, `licenses`, `issues`, or `merge_requests`. |
|
||||
| `name` | string | yes | The key of the template, as obtained from the collection endpoint |
|
||||
| `source_template_project_id` | integer | no | The project ID where a given template is being stored. This is useful when multiple templates from different projects have the same name. If multiple templates have the same name, the match from `closest ancestor` is returned if `source_template_project_id` is not specified |
|
||||
| `project` | string | no | The project name to use when expanding placeholders in the template. Only affects licenses |
|
||||
|
|
|
@ -10,8 +10,8 @@ We have implemented standard features that depend on configuration files in the
|
|||
When implementing new features, please refer to these existing features to avoid conflicts:
|
||||
|
||||
- [Custom Dashboards](../operations/metrics/dashboards/index.md#add-a-new-dashboard-to-your-project): `.gitlab/dashboards/`.
|
||||
- [Issue Templates](../user/project/description_templates.md#creating-issue-templates): `.gitlab/issue_templates/`.
|
||||
- [Merge Request Templates](../user/project/description_templates.md#creating-merge-request-templates): `.gitlab/merge_request_templates/`.
|
||||
- [Issue Templates](../user/project/description_templates.md#create-an-issue-template): `.gitlab/issue_templates/`.
|
||||
- [Merge Request Templates](../user/project/description_templates.md#create-a-merge-request-template): `.gitlab/merge_request_templates/`.
|
||||
- [GitLab Kubernetes Agents](https://gitlab.com/gitlab-org/cluster-integration/gitlab-agent/-/blob/master/doc/configuration_repository.md#layout): `.gitlab/agents/`.
|
||||
- [CODEOWNERS](../user/project/code_owners.md#how-to-set-up-code-owners): `.gitlab/CODEOWNERS`.
|
||||
- [Route Maps](../ci/review_apps/#route-maps): `.gitlab/route-map.yml`.
|
||||
|
|
|
@ -484,9 +484,9 @@ For GitLab.com, we're setting up a [QA and Testing environment](https://gitlab.c
|
|||
|
||||
### `gitlab_standard`
|
||||
|
||||
We are currently working towards including the [`gitlab_standard` schema](https://gitlab.com/gitlab-org/iglu/-/blob/master/public/schemas/com.gitlab/gitlab_standard/jsonschema/) with every event. See [Standardize Snowplow Schema](https://gitlab.com/groups/gitlab-org/-/epics/5218) for details.
|
||||
We are including the [`gitlab_standard` schema](https://gitlab.com/gitlab-org/iglu/-/blob/master/public/schemas/com.gitlab/gitlab_standard/jsonschema/) with every event. See [Standardize Snowplow Schema](https://gitlab.com/groups/gitlab-org/-/epics/5218) for details.
|
||||
|
||||
The [`StandardContext`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/tracking/standard_context.rb) class represents this schema in the application.
|
||||
The [`StandardContext`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/tracking/standard_context.rb) class represents this schema in the application.
|
||||
|
||||
| Field Name | Required | Type | Description |
|
||||
|----------------|---------------------|-----------------------|---------------------------------------------------------------------------------------------|
|
||||
|
|
|
@ -54,7 +54,7 @@ With Maintainer or higher [permissions](../../user/permissions.md), you can enab
|
|||
1. Navigate to **Settings > Operations > Incidents** and expand **Incidents**.
|
||||
1. Check the **Create an incident** checkbox.
|
||||
1. To customize the incident, select an
|
||||
[issue template](../../user/project/description_templates.md#creating-issue-templates).
|
||||
[issue template](../../user/project/description_templates.md#create-an-issue-template).
|
||||
1. To send [an email notification](paging.md#email-notifications) to users
|
||||
with [Developer permissions](../../user/permissions.md), select
|
||||
**Send a separate email notification to Developers**. Email notifications are
|
||||
|
|
|
@ -20,7 +20,7 @@ the tiers are no longer mentioned in GitLab documentation:
|
|||
[per-group charts](../user/project/milestones/index.md#group-burndown-charts)
|
||||
- [Code owners](../user/project/code_owners.md)
|
||||
- Description templates:
|
||||
- [Setting a default template for merge requests and issues](../user/project/description_templates.md#setting-a-default-template-for-merge-requests-and-issues)
|
||||
- [Setting a default template for merge requests and issues](../user/project/description_templates.md#set-a-default-template-for-merge-requests-and-issues)
|
||||
- [Email from GitLab](../tools/email.md)
|
||||
- Groups:
|
||||
- [Creating group memberships via CN](../user/group/index.md#creating-group-links-via-cn)
|
||||
|
|
|
@ -37,10 +37,10 @@ To learn how to create templates for various file types in groups, visit
|
|||
images guidelines, link to the related issue, reviewer name, and so on.
|
||||
- You can also create issues and merge request templates for different
|
||||
stages of your workflow, for example, feature proposal, feature improvement, or a bug report.
|
||||
- You can use an [issue description template](#creating-issue-templates) as a
|
||||
- You can use an [issue description template](#create-an-issue-template) as a
|
||||
[Service Desk email template](service_desk.md#new-service-desk-issues).
|
||||
|
||||
## Creating issue templates
|
||||
## Create an issue template
|
||||
|
||||
Create a new Markdown (`.md`) file inside the `.gitlab/issue_templates/`
|
||||
directory in your repository. Commit and push to your default branch.
|
||||
|
@ -65,13 +65,13 @@ To create the `.gitlab/issue_templates` directory:
|
|||
To check if this has worked correctly, [create a new issue](issues/managing_issues.md#create-a-new-issue)
|
||||
and see if you can choose a description template.
|
||||
|
||||
## Creating merge request templates
|
||||
## Create a merge request template
|
||||
|
||||
Similarly to issue templates, create a new Markdown (`.md`) file inside the
|
||||
`.gitlab/merge_request_templates/` directory in your repository. Commit and
|
||||
push to your default branch.
|
||||
|
||||
## Using the templates
|
||||
## Use the templates
|
||||
|
||||
Let's take for example that you've created the file `.gitlab/issue_templates/Bug.md`.
|
||||
This enables the `Bug` dropdown option when creating or editing issues. When
|
||||
|
@ -85,9 +85,45 @@ For example: `https://gitlab.com/gitlab-org/gitlab/-/issues/new?issuable_templat
|
|||
|
||||
![Description templates](img/description_templates.png)
|
||||
|
||||
## Setting a default template for merge requests and issues **(PREMIUM)**
|
||||
### Set an issue and merge request description template at group level **(PREMIUM)**
|
||||
|
||||
> - Moved to GitLab Premium in 13.9.
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/52360) in GitLab 13.9.
|
||||
> - It's [deployed behind a feature flag](../feature_flags.md), disabled by default.
|
||||
> - It's disabled by default on GitLab.com.
|
||||
> - To use it in GitLab self-managed instances, ask a GitLab administrator to
|
||||
[enable it](#enable-or-disable-issue-and-merge-request-description-templates-at-group-and-instance-level).
|
||||
|
||||
Templates can be useful because you can create a template once and use it multiple times.
|
||||
To re-use templates [you've created](../project/description_templates.md#create-an-issue-template):
|
||||
|
||||
1. Go to the group's **Settings > General > Templates**.
|
||||
1. From the dropdown, select your template project as the template repository at group level.
|
||||
|
||||
![Group template settings](../group/img/group_file_template_settings.png)
|
||||
|
||||
### Set an issue and merge request description template at instance level **(PREMIUM ONLY)**
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/52360) in GitLab 13.9.
|
||||
> - It's [deployed behind a feature flag](../feature_flags.md), disabled by default.
|
||||
> - It's disabled by default on GitLab.com.
|
||||
> - To use it in GitLab self-managed instances, ask a GitLab administrator to
|
||||
[enable it](#enable-or-disable-issue-and-merge-request-description-templates-at-group-and-instance-level).
|
||||
|
||||
Similar to group templates, issue and merge request templates can also be set up at the instance level.
|
||||
This results in those templates being available in all projects within the instance.
|
||||
Only instance administrators can set instance-level templates.
|
||||
|
||||
To set the instance-level description template repository:
|
||||
|
||||
1. Select the **Admin Area** icon (**{admin}**).
|
||||
1. Go to **Settings > Templates**.
|
||||
1. From the dropdown, select your template project as the template repository at instance level.
|
||||
|
||||
Learn more about [instance template repository](../admin_area/settings/instance_template_repository.md).
|
||||
|
||||
![Setting templates in the Admin Area](../admin_area/settings/img/file_template_admin_area.png)
|
||||
|
||||
### Set a default template for merge requests and issues **(PREMIUM)**
|
||||
|
||||
The visibility of issues or merge requests should be set to either "Everyone
|
||||
with access" or "Only Project Members" in your project's **Settings / Visibility, project features, permissions** section, otherwise the
|
||||
|
@ -159,3 +195,28 @@ it's very hard to read otherwise.)
|
|||
/cc @project-manager
|
||||
/assign @qa-tester
|
||||
```
|
||||
|
||||
## Enable or disable issue and merge request description templates at group and instance level
|
||||
|
||||
Setting issue and merge request description templates at group and instance levels
|
||||
is under development and not ready for production use. It is deployed behind a
|
||||
feature flag that is **disabled by default**.
|
||||
[GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md)
|
||||
can enable it.
|
||||
|
||||
To enable it:
|
||||
|
||||
```ruby
|
||||
Feature.enable(:inherited_issuable_templates)
|
||||
```
|
||||
|
||||
To disable it:
|
||||
|
||||
```ruby
|
||||
Feature.disable(:inherited_issuable_templates)
|
||||
```
|
||||
|
||||
The feature flag affects these features:
|
||||
|
||||
- Setting a templates project as issue and merge request description templates source at group level.
|
||||
- Setting a templates project as issue and merge request description templates source at instance level.
|
||||
|
|
|
@ -184,7 +184,7 @@ You can then see issue statuses in the [issue list](#issues-list) and the
|
|||
|
||||
## Other Issue actions
|
||||
|
||||
- [Create an issue from a template](../../project/description_templates.md#using-the-templates)
|
||||
- [Create an issue from a template](../../project/description_templates.md#use-the-templates)
|
||||
- [Set a due date](due_dates.md)
|
||||
- [Bulk edit issues](../bulk_editing.md) - From the Issues List, select multiple issues
|
||||
in order to change their status, assignee, milestone, or labels in bulk.
|
||||
|
|
|
@ -137,13 +137,13 @@ You can use these placeholders to be automatically replaced in each email:
|
|||
|
||||
#### New Service Desk issues
|
||||
|
||||
You can select one [issue description template](description_templates.md#creating-issue-templates)
|
||||
You can select one [issue description template](description_templates.md#create-an-issue-template)
|
||||
**per project** to be appended to every new Service Desk issue's description.
|
||||
Issue description templates should reside in your repository's `.gitlab/issue_templates/` directory.
|
||||
|
||||
To use a custom issue template with Service Desk, in your project:
|
||||
|
||||
1. [Create a description template](description_templates.md#creating-issue-templates)
|
||||
1. [Create a description template](description_templates.md#create-an-issue-template)
|
||||
1. Go to **Settings > General > Service Desk**.
|
||||
1. From the dropdown **Template to append to all Service Desk issues**, select your template.
|
||||
|
||||
|
|
|
@ -102,7 +102,7 @@ To edit a file:
|
|||
in the bottom-right corner.
|
||||
1. When you're done, click **Submit changes...**.
|
||||
1. (Optional) Adjust the default title and description of the merge request, to submit
|
||||
with your changes. Alternatively, select a [merge request template](../../../user/project/description_templates.md#creating-merge-request-templates)
|
||||
with your changes. Alternatively, select a [merge request template](../../../user/project/description_templates.md#create-a-merge-request-template)
|
||||
from the dropdown menu and edit it accordingly.
|
||||
1. Click **Submit changes**.
|
||||
1. A new merge request is automatically created and you can assign a colleague for review.
|
||||
|
|
|
@ -7,7 +7,7 @@ module Gitlab
|
|||
module Samplers
|
||||
class RubySampler < BaseSampler
|
||||
DEFAULT_SAMPLING_INTERVAL_SECONDS = 60
|
||||
GC_REPORT_BUCKETS = [0.005, 0.01, 0.02, 0.04, 0.07, 0.1, 0.5].freeze
|
||||
GC_REPORT_BUCKETS = [0.01, 0.05, 0.1, 0.2, 0.3, 0.5, 1].freeze
|
||||
|
||||
def initialize(*)
|
||||
GC::Profiler.clear
|
||||
|
|
|
@ -17,7 +17,7 @@ module Gitlab
|
|||
# to a structured log
|
||||
# rubocop:disable Gitlab/ModuleWithInstanceVariables
|
||||
def enqueue_stats_job(request_id)
|
||||
return unless gather_stats?
|
||||
return unless Feature.enabled?(:performance_bar_stats)
|
||||
|
||||
@client.sadd(GitlabPerformanceBarStatsWorker::STATS_KEY, request_id)
|
||||
|
||||
|
@ -43,12 +43,6 @@ module Gitlab
|
|||
)
|
||||
end
|
||||
# rubocop:enable Gitlab/ModuleWithInstanceVariables
|
||||
|
||||
def gather_stats?
|
||||
return unless Feature.enabled?(:performance_bar_stats)
|
||||
|
||||
Gitlab.com? || Gitlab.staging? || !Rails.env.production?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -87,11 +87,11 @@ module Gitlab
|
|||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def by_category(category, project = nil)
|
||||
def by_category(category, project = nil, empty_category_title: nil)
|
||||
directory = category_directory(category)
|
||||
files = finder(project).list_files_for(directory)
|
||||
|
||||
files.map { |f| new(f, project, category: category) }.sort
|
||||
files.map { |f| new(f, project, category: category.presence || empty_category_title) }.sort
|
||||
end
|
||||
|
||||
def category_directory(category)
|
||||
|
|
|
@ -25,6 +25,10 @@ module Gitlab
|
|||
# follow-up issue: https://gitlab.com/gitlab-org/gitlab/-/issues/300279
|
||||
project.repository.issue_template_names_by_category
|
||||
end
|
||||
|
||||
def by_category(category, project = nil, empty_category_title: nil)
|
||||
super(category, project, empty_category_title: _('Project Templates'))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -25,6 +25,10 @@ module Gitlab
|
|||
# follow-up issue: https://gitlab.com/gitlab-org/gitlab/-/issues/300279
|
||||
project.repository.merge_request_template_names_by_category
|
||||
end
|
||||
|
||||
def by_category(category, project = nil, empty_category_title: nil)
|
||||
super(category, project, empty_category_title: _('Project Templates'))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3927,9 +3927,6 @@ msgstr ""
|
|||
msgid "Are you sure you want to close this blocked issue?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Are you sure you want to delete \"%{name}\" Value Stream?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Are you sure you want to delete %{name}?"
|
||||
msgstr ""
|
||||
|
||||
|
@ -8639,6 +8636,9 @@ msgstr ""
|
|||
msgid "CreateValueStreamForm|'%{name}' Value Stream created"
|
||||
msgstr ""
|
||||
|
||||
msgid "CreateValueStreamForm|'%{name}' Value Stream edited"
|
||||
msgstr ""
|
||||
|
||||
msgid "CreateValueStreamForm|Add another stage"
|
||||
msgstr ""
|
||||
|
||||
|
@ -8651,15 +8651,24 @@ msgstr ""
|
|||
msgid "CreateValueStreamForm|Code stage start"
|
||||
msgstr ""
|
||||
|
||||
msgid "CreateValueStreamForm|Create Value Stream"
|
||||
msgstr ""
|
||||
|
||||
msgid "CreateValueStreamForm|Create from default template"
|
||||
msgstr ""
|
||||
|
||||
msgid "CreateValueStreamForm|Create from no template"
|
||||
msgstr ""
|
||||
|
||||
msgid "CreateValueStreamForm|Create new Value Stream"
|
||||
msgstr ""
|
||||
|
||||
msgid "CreateValueStreamForm|Default stages"
|
||||
msgstr ""
|
||||
|
||||
msgid "CreateValueStreamForm|Edit Value Stream"
|
||||
msgstr ""
|
||||
|
||||
msgid "CreateValueStreamForm|Editing stage"
|
||||
msgstr ""
|
||||
|
||||
|
@ -8708,6 +8717,9 @@ msgstr ""
|
|||
msgid "CreateValueStreamForm|Restore stage"
|
||||
msgstr ""
|
||||
|
||||
msgid "CreateValueStreamForm|Save Value Stream"
|
||||
msgstr ""
|
||||
|
||||
msgid "CreateValueStreamForm|Select end event"
|
||||
msgstr ""
|
||||
|
||||
|
@ -9711,6 +9723,15 @@ msgstr ""
|
|||
msgid "DeleteProject|Failed to restore wiki repository. Please contact the administrator."
|
||||
msgstr ""
|
||||
|
||||
msgid "DeleteValueStream|'%{name}' Value Stream deleted"
|
||||
msgstr ""
|
||||
|
||||
msgid "DeleteValueStream|Are you sure you want to delete \"%{name}\" Value Stream?"
|
||||
msgstr ""
|
||||
|
||||
msgid "DeleteValueStream|Delete %{name}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Deleted"
|
||||
msgstr ""
|
||||
|
||||
|
@ -13234,6 +13255,9 @@ msgstr ""
|
|||
msgid "Geo nodes are paused using a command run on the node"
|
||||
msgstr ""
|
||||
|
||||
msgid "Geo sites"
|
||||
msgstr ""
|
||||
|
||||
msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
|
||||
msgstr ""
|
||||
|
||||
|
@ -13612,6 +13636,9 @@ msgstr ""
|
|||
msgid "Getting started with releases"
|
||||
msgstr ""
|
||||
|
||||
msgid "Git"
|
||||
msgstr ""
|
||||
|
||||
msgid "Git LFS is not enabled on this GitLab server, contact your admin."
|
||||
msgstr ""
|
||||
|
||||
|
@ -16849,6 +16876,9 @@ msgstr ""
|
|||
msgid "Jira service not configured."
|
||||
msgstr ""
|
||||
|
||||
msgid "Jira user"
|
||||
msgstr ""
|
||||
|
||||
msgid "Jira users have been imported from the configured Jira instance. They can be mapped by selecting a GitLab user from the dropdown in the \"GitLab username\" column. When the form appears, the dropdown defaults to the user conducting the import."
|
||||
msgstr ""
|
||||
|
||||
|
@ -20121,6 +20151,9 @@ msgstr ""
|
|||
msgid "No application_settings found"
|
||||
msgstr ""
|
||||
|
||||
msgid "No assignee"
|
||||
msgstr ""
|
||||
|
||||
msgid "No authentication methods configured."
|
||||
msgstr ""
|
||||
|
||||
|
@ -21668,6 +21701,9 @@ msgstr ""
|
|||
msgid "PerformanceBar|SQL queries"
|
||||
msgstr ""
|
||||
|
||||
msgid "PerformanceBar|Stats"
|
||||
msgstr ""
|
||||
|
||||
msgid "PerformanceBar|trace"
|
||||
msgstr ""
|
||||
|
||||
|
@ -21986,6 +22022,9 @@ msgstr ""
|
|||
msgid "Pipeline|Canceled"
|
||||
msgstr ""
|
||||
|
||||
msgid "Pipeline|Checking pipeline status"
|
||||
msgstr ""
|
||||
|
||||
msgid "Pipeline|Checking pipeline status."
|
||||
msgstr ""
|
||||
|
||||
|
@ -22037,6 +22076,9 @@ msgstr ""
|
|||
msgid "Pipeline|Pipeline"
|
||||
msgstr ""
|
||||
|
||||
msgid "Pipeline|Pipeline %{idStart}#%{idEnd} %{statusStart}%{statusEnd} for %{commitStart}%{commitEnd}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Pipeline|Pipelines"
|
||||
msgstr ""
|
||||
|
||||
|
@ -22094,6 +22136,9 @@ msgstr ""
|
|||
msgid "Pipeline|Variables"
|
||||
msgstr ""
|
||||
|
||||
msgid "Pipeline|We are currently unable to fetch pipeline data"
|
||||
msgstr ""
|
||||
|
||||
msgid "Pipeline|You’re about to stop pipeline %{pipelineId}."
|
||||
msgstr ""
|
||||
|
||||
|
@ -23003,6 +23048,9 @@ msgstr ""
|
|||
msgid "Project ID"
|
||||
msgstr ""
|
||||
|
||||
msgid "Project Templates"
|
||||
msgstr ""
|
||||
|
||||
msgid "Project URL"
|
||||
msgstr ""
|
||||
|
||||
|
@ -29772,6 +29820,9 @@ msgstr ""
|
|||
msgid "There was an error fetching the %{replicableType}"
|
||||
msgstr ""
|
||||
|
||||
msgid "There was an error fetching the Geo Nodes"
|
||||
msgstr ""
|
||||
|
||||
msgid "There was an error fetching the Geo Settings"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -160,13 +160,28 @@ RSpec.describe Projects::TemplatesController do
|
|||
end
|
||||
|
||||
shared_examples 'template names request' do
|
||||
it 'returns the template names' do
|
||||
get(:names, params: { namespace_id: project.namespace, template_type: template_type, project_id: project }, format: :json)
|
||||
context 'when feature flag enabled' do
|
||||
it 'returns the template names', :aggregate_failures do
|
||||
get(:names, params: { namespace_id: project.namespace, template_type: template_type, project_id: project }, format: :json)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(json_response.size).to eq(2)
|
||||
expect(json_response.size).to eq(2)
|
||||
expect(json_response.map { |x| x.slice('name') }).to match(expected_template_names)
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(json_response['Project Templates'].size).to eq(2)
|
||||
expect(json_response['Project Templates'].map { |x| x.slice('name') }).to match(expected_template_names)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when feature flag disabled' do
|
||||
before do
|
||||
stub_feature_flags(inherited_issuable_templates: false)
|
||||
end
|
||||
|
||||
it 'returns the template names', :aggregate_failures do
|
||||
get(:names, params: { namespace_id: project.namespace, template_type: template_type, project_id: project }, format: :json)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(json_response.size).to eq(2)
|
||||
expect(json_response.map { |x| x.slice('name') }).to match(expected_template_names)
|
||||
end
|
||||
end
|
||||
|
||||
it 'fails for user with no access' do
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'Service Desk Setting', :js do
|
||||
RSpec.describe 'Service Desk Setting', :js, :clean_gitlab_redis_cache do
|
||||
let(:project) { create(:project_empty_repo, :private, service_desk_enabled: false) }
|
||||
let(:presenter) { project.present(current_user: user) }
|
||||
let(:user) { create(:user) }
|
||||
|
@ -66,5 +66,48 @@ RSpec.describe 'Service Desk Setting', :js do
|
|||
|
||||
expect(find('[data-testid="incoming-email"]').value).to eq('address-suffix@example.com')
|
||||
end
|
||||
|
||||
context 'issue description templates' do
|
||||
let_it_be(:issuable_project_template_files) do
|
||||
{
|
||||
'.gitlab/issue_templates/project-issue-bar.md' => 'Project Issue Template Bar',
|
||||
'.gitlab/issue_templates/project-issue-foo.md' => 'Project Issue Template Foo'
|
||||
}
|
||||
end
|
||||
|
||||
let_it_be(:issuable_group_template_files) do
|
||||
{
|
||||
'.gitlab/issue_templates/group-issue-bar.md' => 'Group Issue Template Bar',
|
||||
'.gitlab/issue_templates/group-issue-foo.md' => 'Group Issue Template Foo'
|
||||
}
|
||||
end
|
||||
|
||||
let_it_be_with_reload(:group) { create(:group)}
|
||||
let_it_be_with_reload(:project) { create(:project, :custom_repo, group: group, files: issuable_project_template_files) }
|
||||
let_it_be(:group_template_repo) { create(:project, :custom_repo, group: group, files: issuable_group_template_files) }
|
||||
|
||||
before do
|
||||
stub_licensed_features(custom_file_templates_for_namespace: false, custom_file_templates: false)
|
||||
group.update_columns(file_template_project_id: group_template_repo.id)
|
||||
end
|
||||
|
||||
context 'when inherited_issuable_templates enabled' do
|
||||
before do
|
||||
stub_feature_flags(inherited_issuable_templates: true)
|
||||
visit edit_project_path(project)
|
||||
end
|
||||
|
||||
it_behaves_like 'issue description templates from current project only'
|
||||
end
|
||||
|
||||
context 'when inherited_issuable_templates disabled' do
|
||||
before do
|
||||
stub_feature_flags(inherited_issuable_templates: false)
|
||||
visit edit_project_path(project)
|
||||
end
|
||||
|
||||
it_behaves_like 'issue description templates from current project only'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -49,6 +49,10 @@ RSpec.describe 'User can display performance bar', :js do
|
|||
|
||||
let(:group) { create(:group) }
|
||||
|
||||
before do
|
||||
allow(GitlabPerformanceBarStatsWorker).to receive(:perform_in)
|
||||
end
|
||||
|
||||
context 'when user is logged-out' do
|
||||
before do
|
||||
visit root_path
|
||||
|
@ -97,6 +101,26 @@ RSpec.describe 'User can display performance bar', :js do
|
|||
|
||||
it_behaves_like 'performance bar is enabled by default in development'
|
||||
it_behaves_like 'performance bar can be displayed'
|
||||
|
||||
it 'does not show Stats link by default' do
|
||||
find('body').native.send_keys('pb')
|
||||
|
||||
expect(page).not_to have_link('Stats', visible: :all)
|
||||
end
|
||||
|
||||
context 'when GITLAB_PERFORMANCE_BAR_STATS_URL environment variable is set' do
|
||||
let(:stats_url) { 'https://log.gprd.gitlab.net/app/dashboards#/view/' }
|
||||
|
||||
before do
|
||||
stub_env('GITLAB_PERFORMANCE_BAR_STATS_URL', stats_url)
|
||||
end
|
||||
|
||||
it 'shows Stats link' do
|
||||
find('body').native.send_keys('pb')
|
||||
|
||||
expect(page).to have_link('Stats', href: stats_url, visible: :all)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -13,7 +13,6 @@ describe('Pipelines table in Commits and Merge requests', () => {
|
|||
let vm;
|
||||
const props = {
|
||||
endpoint: 'endpoint.json',
|
||||
helpPagePath: 'foo',
|
||||
emptyStateSvgPath: 'foo',
|
||||
errorStateSvgPath: 'foo',
|
||||
};
|
||||
|
|
|
@ -10,7 +10,6 @@ exports[`IDE pipelines list when loaded renders empty state when no latestPipeli
|
|||
cansetci="true"
|
||||
class="mb-auto mt-auto"
|
||||
emptystatesvgpath="http://test.host"
|
||||
helppagepath="http://test.host"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
|
|
@ -19,7 +19,6 @@ describe('IDE pipelines list', () => {
|
|||
let wrapper;
|
||||
|
||||
const defaultState = {
|
||||
links: { ciHelpPagePath: TEST_HOST },
|
||||
pipelinesEmptyStateSvgPath: TEST_HOST,
|
||||
};
|
||||
const defaultPipelinesState = {
|
||||
|
|
|
@ -35,7 +35,7 @@ exports[`Alert integration settings form default state should match the default
|
|||
Incident template (optional)
|
||||
|
||||
<gl-link-stub
|
||||
href="/help/user/project/description_templates#creating-issue-templates"
|
||||
href="/help/user/project/description_templates#create-an-issue-template"
|
||||
target="_blank"
|
||||
>
|
||||
<gl-icon-stub
|
||||
|
|
|
@ -422,7 +422,18 @@ describe('Issuable output', () => {
|
|||
formSpy = jest.spyOn(wrapper.vm, 'updateAndShowForm');
|
||||
});
|
||||
|
||||
it('shows the form if template names request is successful', () => {
|
||||
it('shows the form if template names as hash request is successful', () => {
|
||||
const mockData = {
|
||||
test: [{ name: 'test', id: 'test', project_path: '/', namespace_path: '/' }],
|
||||
};
|
||||
mock.onGet('/issuable-templates-path').reply(() => Promise.resolve([200, mockData]));
|
||||
|
||||
return wrapper.vm.requestTemplatesAndShowForm().then(() => {
|
||||
expect(formSpy).toHaveBeenCalledWith(mockData);
|
||||
});
|
||||
});
|
||||
|
||||
it('shows the form if template names as array request is successful', () => {
|
||||
const mockData = [{ name: 'test', id: 'test', project_path: '/', namespace_path: '/' }];
|
||||
mock.onGet('/issuable-templates-path').reply(() => Promise.resolve([200, mockData]));
|
||||
|
||||
|
|
|
@ -1,7 +1,50 @@
|
|||
import Vue from 'vue';
|
||||
import descriptionTemplate from '~/issue_show/components/fields/description_template.vue';
|
||||
|
||||
describe('Issue description template component', () => {
|
||||
describe('Issue description template component with templates as hash', () => {
|
||||
let vm;
|
||||
let formState;
|
||||
|
||||
beforeEach(() => {
|
||||
const Component = Vue.extend(descriptionTemplate);
|
||||
formState = {
|
||||
description: 'test',
|
||||
};
|
||||
|
||||
vm = new Component({
|
||||
propsData: {
|
||||
formState,
|
||||
issuableTemplates: {
|
||||
test: [{ name: 'test', id: 'test', project_path: '/', namespace_path: '/' }],
|
||||
},
|
||||
projectId: 1,
|
||||
projectPath: '/',
|
||||
namespacePath: '/',
|
||||
projectNamespace: '/',
|
||||
},
|
||||
}).$mount();
|
||||
});
|
||||
|
||||
it('renders templates as JSON hash in data attribute', () => {
|
||||
expect(vm.$el.querySelector('.js-issuable-selector').getAttribute('data-data')).toBe(
|
||||
'{"test":[{"name":"test","id":"test","project_path":"/","namespace_path":"/"}]}',
|
||||
);
|
||||
});
|
||||
|
||||
it('updates formState when changing template', () => {
|
||||
vm.issuableTemplate.editor.setValue('test new template');
|
||||
|
||||
expect(formState.description).toBe('test new template');
|
||||
});
|
||||
|
||||
it('returns formState description with editor getValue', () => {
|
||||
formState.description = 'testing new template';
|
||||
|
||||
expect(vm.issuableTemplate.editor.getValue()).toBe('testing new template');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Issue description template component with templates as array', () => {
|
||||
let vm;
|
||||
let formState;
|
||||
|
||||
|
@ -28,16 +71,4 @@ describe('Issue description template component', () => {
|
|||
'[{"name":"test","id":"test","project_path":"/","namespace_path":"/"}]',
|
||||
);
|
||||
});
|
||||
|
||||
it('updates formState when changing template', () => {
|
||||
vm.issuableTemplate.editor.setValue('test new template');
|
||||
|
||||
expect(formState.description).toBe('test new template');
|
||||
});
|
||||
|
||||
it('returns formState description with editor getValue', () => {
|
||||
formState.description = 'testing new template';
|
||||
|
||||
expect(vm.issuableTemplate.editor.getValue()).toBe('testing new template');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -42,7 +42,7 @@ describe('Inline edit form component', () => {
|
|||
expect(vm.$el.querySelector('.js-issuable-selector-wrap')).toBeNull();
|
||||
});
|
||||
|
||||
it('renders template selector when templates exists', () => {
|
||||
it('renders template selector when templates as array exists', () => {
|
||||
createComponent({
|
||||
issuableTemplates: [
|
||||
{ name: 'test', id: 'test', project_path: 'test', namespace_path: 'test' },
|
||||
|
@ -52,6 +52,16 @@ describe('Inline edit form component', () => {
|
|||
expect(vm.$el.querySelector('.js-issuable-selector-wrap')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('renders template selector when templates as hash exists', () => {
|
||||
createComponent({
|
||||
issuableTemplates: {
|
||||
test: [{ name: 'test', id: 'test', project_path: 'test', namespace_path: 'test' }],
|
||||
},
|
||||
});
|
||||
|
||||
expect(vm.$el.querySelector('.js-issuable-selector-wrap')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('hides locked warning by default', () => {
|
||||
createComponent();
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ describe('performance bar app', () => {
|
|||
store,
|
||||
env: 'development',
|
||||
requestId: '123',
|
||||
statsUrl: 'https://log.gprd.gitlab.net/app/dashboards#/view/',
|
||||
peekUrl: '/-/peek/results',
|
||||
profileUrl: '?lineprofiler=true',
|
||||
},
|
||||
|
|
|
@ -19,6 +19,7 @@ describe('performance bar wrapper', () => {
|
|||
peekWrapper.setAttribute('data-env', 'development');
|
||||
peekWrapper.setAttribute('data-request-id', '123');
|
||||
peekWrapper.setAttribute('data-peek-url', '/-/peek/results');
|
||||
peekWrapper.setAttribute('data-stats-url', 'https://log.gprd.gitlab.net/app/dashboards#/view/');
|
||||
peekWrapper.setAttribute('data-profile-url', '?lineprofiler=true');
|
||||
|
||||
mock = new MockAdapter(axios);
|
||||
|
|
|
@ -1,14 +1,24 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import PipelineEditorHeader from '~/pipeline_editor/components/header/pipeline_editor_header.vue';
|
||||
import PipelineStatus from '~/pipeline_editor/components/header/pipeline_status.vue';
|
||||
import ValidationSegment from '~/pipeline_editor/components/header/validation_segment.vue';
|
||||
|
||||
import { mockLintResponse } from '../../mock_data';
|
||||
|
||||
describe('Pipeline editor header', () => {
|
||||
let wrapper;
|
||||
const mockProvide = {
|
||||
glFeatures: {
|
||||
pipelineStatusForPipelineEditor: true,
|
||||
},
|
||||
};
|
||||
|
||||
const createComponent = () => {
|
||||
const createComponent = ({ provide = {} } = {}) => {
|
||||
wrapper = shallowMount(PipelineEditorHeader, {
|
||||
provide: {
|
||||
...mockProvide,
|
||||
...provide,
|
||||
},
|
||||
props: {
|
||||
ciConfigData: mockLintResponse,
|
||||
isCiConfigDataLoading: false,
|
||||
|
@ -16,6 +26,7 @@ describe('Pipeline editor header', () => {
|
|||
});
|
||||
};
|
||||
|
||||
const findPipelineStatus = () => wrapper.findComponent(PipelineStatus);
|
||||
const findValidationSegment = () => wrapper.findComponent(ValidationSegment);
|
||||
|
||||
afterEach(() => {
|
||||
|
@ -27,8 +38,27 @@ describe('Pipeline editor header', () => {
|
|||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
it('renders the pipeline status', () => {
|
||||
expect(findPipelineStatus().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('renders the validation segment', () => {
|
||||
expect(findValidationSegment().exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with pipeline status feature flag off', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
provide: {
|
||||
glFeatures: { pipelineStatusForPipelineEditor: false },
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('does not render the pipeline status', () => {
|
||||
expect(findPipelineStatus().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,150 @@
|
|||
import { GlIcon, GlLink, GlLoadingIcon, GlSprintf } from '@gitlab/ui';
|
||||
import { shallowMount, createLocalVue } from '@vue/test-utils';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import PipelineStatus, { i18n } from '~/pipeline_editor/components/header/pipeline_status.vue';
|
||||
import CiIcon from '~/vue_shared/components/ci_icon.vue';
|
||||
import { mockCommitSha, mockProjectPipeline, mockProjectFullPath } from '../../mock_data';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueApollo);
|
||||
|
||||
const mockProvide = {
|
||||
projectFullPath: mockProjectFullPath,
|
||||
};
|
||||
|
||||
describe('Pipeline Status', () => {
|
||||
let wrapper;
|
||||
let mockApollo;
|
||||
let mockPipelineQuery;
|
||||
|
||||
const createComponent = ({ hasPipeline = true, isQueryLoading = false }) => {
|
||||
const pipeline = hasPipeline
|
||||
? { loading: isQueryLoading, ...mockProjectPipeline.pipeline }
|
||||
: { loading: isQueryLoading };
|
||||
|
||||
wrapper = shallowMount(PipelineStatus, {
|
||||
provide: mockProvide,
|
||||
stubs: { GlLink, GlSprintf },
|
||||
data: () => (hasPipeline ? { pipeline } : {}),
|
||||
mocks: {
|
||||
$apollo: {
|
||||
queries: {
|
||||
pipeline,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const createComponentWithApollo = () => {
|
||||
const resolvers = {
|
||||
Query: {
|
||||
project: mockPipelineQuery,
|
||||
},
|
||||
};
|
||||
mockApollo = createMockApollo([], resolvers);
|
||||
|
||||
wrapper = shallowMount(PipelineStatus, {
|
||||
localVue,
|
||||
apolloProvider: mockApollo,
|
||||
provide: mockProvide,
|
||||
stubs: { GlLink, GlSprintf },
|
||||
data() {
|
||||
return {
|
||||
commitSha: mockCommitSha,
|
||||
};
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const findIcon = () => wrapper.findComponent(GlIcon);
|
||||
const findCiIcon = () => wrapper.findComponent(CiIcon);
|
||||
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
|
||||
const findPipelineId = () => wrapper.find('[data-testid="pipeline-id"]');
|
||||
const findPipelineCommit = () => wrapper.find('[data-testid="pipeline-commit"]');
|
||||
const findPipelineErrorMsg = () => wrapper.find('[data-testid="pipeline-error-msg"]');
|
||||
const findPipelineLoadingMsg = () => wrapper.find('[data-testid="pipeline-loading-msg"]');
|
||||
|
||||
beforeEach(() => {
|
||||
mockPipelineQuery = jest.fn();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mockPipelineQuery.mockReset();
|
||||
|
||||
wrapper.destroy();
|
||||
wrapper = null;
|
||||
});
|
||||
|
||||
describe('while querying', () => {
|
||||
it('renders loading icon', () => {
|
||||
createComponent({ isQueryLoading: true, hasPipeline: false });
|
||||
|
||||
expect(findLoadingIcon().exists()).toBe(true);
|
||||
expect(findPipelineLoadingMsg().text()).toBe(i18n.fetchLoading);
|
||||
});
|
||||
|
||||
it('does not render loading icon if pipeline data is already set', () => {
|
||||
createComponent({ isQueryLoading: true });
|
||||
|
||||
expect(findLoadingIcon().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when querying data', () => {
|
||||
describe('when data is set', () => {
|
||||
beforeEach(async () => {
|
||||
mockPipelineQuery.mockResolvedValue(mockProjectPipeline);
|
||||
|
||||
createComponentWithApollo();
|
||||
await waitForPromises();
|
||||
});
|
||||
|
||||
it('query is called with correct variables', async () => {
|
||||
expect(mockPipelineQuery).toHaveBeenCalledTimes(1);
|
||||
expect(mockPipelineQuery).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
{
|
||||
fullPath: mockProjectFullPath,
|
||||
},
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
);
|
||||
});
|
||||
|
||||
it('does not render error', () => {
|
||||
expect(findIcon().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('renders pipeline data', () => {
|
||||
const { id } = mockProjectPipeline.pipeline;
|
||||
|
||||
expect(findCiIcon().exists()).toBe(true);
|
||||
expect(findPipelineId().text()).toBe(`#${id.match(/\d+/g)[0]}`);
|
||||
expect(findPipelineCommit().text()).toBe(mockCommitSha);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when data cannot be fetched', () => {
|
||||
beforeEach(async () => {
|
||||
mockPipelineQuery.mockRejectedValue(new Error());
|
||||
|
||||
createComponentWithApollo();
|
||||
await waitForPromises();
|
||||
});
|
||||
|
||||
it('renders error', () => {
|
||||
expect(findIcon().attributes('name')).toBe('warning-solid');
|
||||
expect(findPipelineErrorMsg().text()).toBe(i18n.fetchError);
|
||||
});
|
||||
|
||||
it('does not render pipeline data', () => {
|
||||
expect(findCiIcon().exists()).toBe(false);
|
||||
expect(findPipelineId().exists()).toBe(false);
|
||||
expect(findPipelineCommit().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -46,6 +46,24 @@ describe('~/pipeline_editor/graphql/resolvers', () => {
|
|||
await expect(result.rawData).resolves.toBe(mockCiYml);
|
||||
});
|
||||
});
|
||||
|
||||
describe('pipeline', () => {
|
||||
it('resolves pipeline data with type names', async () => {
|
||||
const result = await resolvers.Query.project(null);
|
||||
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
expect(result.__typename).toBe('Project');
|
||||
});
|
||||
|
||||
it('resolves pipeline data with necessary data', async () => {
|
||||
const result = await resolvers.Query.project(null);
|
||||
const pipelineKeys = Object.keys(result.pipeline);
|
||||
const statusKeys = Object.keys(result.pipeline.detailedStatus);
|
||||
|
||||
expect(pipelineKeys).toContain('id', 'commitPath', 'detailedStatus', 'shortSha');
|
||||
expect(statusKeys).toContain('detailsPath', 'text');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Mutation', () => {
|
||||
|
|
|
@ -138,6 +138,22 @@ export const mergeUnwrappedCiConfig = (mergedConfig) => {
|
|||
};
|
||||
};
|
||||
|
||||
export const mockProjectPipeline = {
|
||||
pipeline: {
|
||||
commitPath: '/-/commit/aabbccdd',
|
||||
id: 'gid://gitlab/Ci::Pipeline/118',
|
||||
iid: '28',
|
||||
shortSha: mockCommitSha,
|
||||
status: 'SUCCESS',
|
||||
detailedStatus: {
|
||||
detailsPath: '/root/sample-ci-project/-/pipelines/118"',
|
||||
group: 'success',
|
||||
icon: 'status_success',
|
||||
text: 'passed',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const mockLintResponse = {
|
||||
valid: true,
|
||||
mergedYaml: mockCiYml,
|
||||
|
|
|
@ -9,7 +9,6 @@ describe('Pipelines Empty State', () => {
|
|||
const createWrapper = () => {
|
||||
wrapper = shallowMount(EmptyState, {
|
||||
propsData: {
|
||||
helpPagePath: 'foo',
|
||||
emptyStateSvgPath: 'foo',
|
||||
canSetCi: true,
|
||||
},
|
||||
|
@ -35,7 +34,7 @@ describe('Pipelines Empty State', () => {
|
|||
});
|
||||
|
||||
it('should render a link with provided help path', () => {
|
||||
expect(findGetStartedButton().attributes('href')).toBe('foo');
|
||||
expect(findGetStartedButton().attributes('href')).toBe('/help/ci/quick_start/index.md');
|
||||
});
|
||||
|
||||
it('should render empty state information', () => {
|
||||
|
|
|
@ -34,7 +34,6 @@ describe('Pipelines', () => {
|
|||
let origWindowLocation;
|
||||
|
||||
const paths = {
|
||||
helpPagePath: '/help/ci/quick_start/README',
|
||||
emptyStateSvgPath: '/assets/illustrations/pipelines_empty.svg',
|
||||
errorStateSvgPath: '/assets/illustrations/pipelines_failed.svg',
|
||||
noPipelinesSvgPath: '/assets/illustrations/pipelines_pending.svg',
|
||||
|
@ -44,7 +43,6 @@ describe('Pipelines', () => {
|
|||
};
|
||||
|
||||
const noPermissions = {
|
||||
helpPagePath: '/help/ci/quick_start/README',
|
||||
emptyStateSvgPath: '/assets/illustrations/pipelines_empty.svg',
|
||||
errorStateSvgPath: '/assets/illustrations/pipelines_failed.svg',
|
||||
noPipelinesSvgPath: '/assets/illustrations/pipelines_pending.svg',
|
||||
|
@ -544,7 +542,9 @@ describe('Pipelines', () => {
|
|||
'GitLab CI/CD can automatically build, test, and deploy your code.',
|
||||
);
|
||||
expect(findEmptyState().find(GlButton).text()).toBe('Get started with CI/CD');
|
||||
expect(findEmptyState().find(GlButton).attributes('href')).toBe(paths.helpPagePath);
|
||||
expect(findEmptyState().find(GlButton).attributes('href')).toBe(
|
||||
'/help/ci/quick_start/index.md',
|
||||
);
|
||||
});
|
||||
|
||||
it('does not render tabs nor buttons', () => {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { setHTMLFixture } from 'helpers/fixtures';
|
||||
import Tracking, { initUserTracking, initDefaultTrackers } from '~/tracking';
|
||||
import Tracking, { initUserTracking, initDefaultTrackers, STANDARD_CONTEXT } from '~/tracking';
|
||||
|
||||
describe('Tracking', () => {
|
||||
let snowplowSpy;
|
||||
|
@ -45,7 +45,7 @@ describe('Tracking', () => {
|
|||
it('should activate features based on what has been enabled', () => {
|
||||
initDefaultTrackers();
|
||||
expect(snowplowSpy).toHaveBeenCalledWith('enableActivityTracking', 30, 30);
|
||||
expect(snowplowSpy).toHaveBeenCalledWith('trackPageView');
|
||||
expect(snowplowSpy).toHaveBeenCalledWith('trackPageView', null, [STANDARD_CONTEXT]);
|
||||
expect(snowplowSpy).not.toHaveBeenCalledWith('enableFormTracking');
|
||||
expect(snowplowSpy).not.toHaveBeenCalledWith('enableLinkClickTracking');
|
||||
|
||||
|
@ -88,7 +88,7 @@ describe('Tracking', () => {
|
|||
'_label_',
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
[STANDARD_CONTEXT],
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -6,6 +6,19 @@ describe('User Popovers', () => {
|
|||
preloadFixtures(fixtureTemplate);
|
||||
|
||||
const selector = '.js-user-link, .gfm-project_member';
|
||||
const findFixtureLinks = () => {
|
||||
return Array.from(document.querySelectorAll(selector)).filter(
|
||||
({ dataset }) => dataset.user || dataset.userId,
|
||||
);
|
||||
};
|
||||
const createUserLink = () => {
|
||||
const link = document.createElement('a');
|
||||
|
||||
link.classList.add('js-user-link');
|
||||
link.setAttribute('data-user', '1');
|
||||
|
||||
return link;
|
||||
};
|
||||
|
||||
const dummyUser = { name: 'root' };
|
||||
const dummyUserStatus = { message: 'active' };
|
||||
|
@ -37,13 +50,20 @@ describe('User Popovers', () => {
|
|||
});
|
||||
|
||||
it('initializes a popover for each user link with a user id', () => {
|
||||
const linksWithUsers = Array.from(document.querySelectorAll(selector)).filter(
|
||||
({ dataset }) => dataset.user || dataset.userId,
|
||||
);
|
||||
const linksWithUsers = findFixtureLinks();
|
||||
|
||||
expect(linksWithUsers.length).toBe(popovers.length);
|
||||
});
|
||||
|
||||
it('adds popovers to user links added to the DOM tree after the initial call', async () => {
|
||||
document.body.appendChild(createUserLink());
|
||||
document.body.appendChild(createUserLink());
|
||||
|
||||
const linksWithUsers = findFixtureLinks();
|
||||
|
||||
expect(linksWithUsers.length).toBe(popovers.length + 2);
|
||||
});
|
||||
|
||||
it('does not initialize the user popovers twice for the same element', () => {
|
||||
const newPopovers = initUserPopovers(document.querySelectorAll(selector));
|
||||
const samePopovers = popovers.every((popover, index) => newPopovers[index] === popover);
|
||||
|
|
|
@ -4,7 +4,6 @@ export const IDE_DATASET = {
|
|||
committedStateSvgPath: '/test/committed_state.svg',
|
||||
pipelinesEmptyStateSvgPath: '/test/pipelines_empty_state.svg',
|
||||
promotionSvgPath: '/test/promotion.svg',
|
||||
ciHelpPagePath: '/test/ci_help_page',
|
||||
webIDEHelpPagePath: '/test/web_ide_help_page',
|
||||
clientsidePreviewEnabled: 'true',
|
||||
renderWhitespaceInCode: 'false',
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Resolvers::AlertManagement::HttpIntegrationsResolver do
|
||||
include GraphqlHelpers
|
||||
|
||||
let_it_be(:guest) { create(:user) }
|
||||
let_it_be(:developer) { create(:user) }
|
||||
let_it_be(:maintainer) { create(:user) }
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be(:prometheus_integration) { create(:prometheus_service, project: project) }
|
||||
let_it_be(:active_http_integration) { create(:alert_management_http_integration, project: project) }
|
||||
let_it_be(:inactive_http_integration) { create(:alert_management_http_integration, :inactive, project: project) }
|
||||
let_it_be(:other_proj_integration) { create(:alert_management_http_integration) }
|
||||
|
||||
subject { sync(resolve_http_integrations) }
|
||||
|
||||
before do
|
||||
project.add_developer(developer)
|
||||
project.add_maintainer(maintainer)
|
||||
end
|
||||
|
||||
specify do
|
||||
expect(described_class).to have_nullable_graphql_type(Types::AlertManagement::HttpIntegrationType.connection_type)
|
||||
end
|
||||
|
||||
context 'user does not have permission' do
|
||||
let(:current_user) { guest }
|
||||
|
||||
it { is_expected.to be_empty }
|
||||
end
|
||||
|
||||
context 'user has developer permission' do
|
||||
let(:current_user) { developer }
|
||||
|
||||
it { is_expected.to be_empty }
|
||||
end
|
||||
|
||||
context 'user has maintainer permission' do
|
||||
let(:current_user) { maintainer }
|
||||
|
||||
it { is_expected.to contain_exactly(active_http_integration) }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def resolve_http_integrations(args = {}, context = { current_user: current_user })
|
||||
resolve(described_class, obj: project, ctx: context)
|
||||
end
|
||||
end
|
|
@ -13,22 +13,33 @@ RSpec.describe IssuablesDescriptionTemplatesHelper, :clean_gitlab_redis_cache do
|
|||
let_it_be(:group_member) { create(:group_member, :developer, group: parent_group, user: user) }
|
||||
let_it_be(:project_member) { create(:project_member, :developer, user: user, project: project) }
|
||||
|
||||
it 'returns empty hash when template type does not exist' do
|
||||
expect(helper.issuable_templates(build(:project), 'non-existent-template-type')).to eq([])
|
||||
context 'when feature flag disabled' do
|
||||
before do
|
||||
stub_feature_flags(inherited_issuable_templates: false)
|
||||
end
|
||||
|
||||
it 'returns empty array when template type does not exist' do
|
||||
expect(helper.issuable_templates(project, 'non-existent-template-type')).to eq([])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when feature flag enabled' do
|
||||
before do
|
||||
stub_feature_flags(inherited_issuable_templates: true)
|
||||
end
|
||||
|
||||
it 'returns empty hash when template type does not exist' do
|
||||
expect(helper.issuable_templates(build(:project), 'non-existent-template-type')).to eq({})
|
||||
end
|
||||
end
|
||||
|
||||
context 'with cached issuable templates' do
|
||||
before do
|
||||
allow(Gitlab::Template::IssueTemplate).to receive(:template_names).and_return({})
|
||||
allow(Gitlab::Template::MergeRequestTemplate).to receive(:template_names).and_return({})
|
||||
it 'does not call TemplateFinder' do
|
||||
expect(Gitlab::Template::IssueTemplate).to receive(:template_names).once.and_call_original
|
||||
expect(Gitlab::Template::MergeRequestTemplate).to receive(:template_names).once.and_call_original
|
||||
|
||||
helper.issuable_templates(project, 'issues')
|
||||
helper.issuable_templates(project, 'merge_request')
|
||||
end
|
||||
|
||||
it 'does not call TemplateFinder' do
|
||||
expect(Gitlab::Template::IssueTemplate).not_to receive(:template_names)
|
||||
expect(Gitlab::Template::MergeRequestTemplate).not_to receive(:template_names)
|
||||
helper.issuable_templates(project, 'issues')
|
||||
helper.issuable_templates(project, 'merge_request')
|
||||
end
|
||||
|
@ -63,29 +74,78 @@ RSpec.describe IssuablesDescriptionTemplatesHelper, :clean_gitlab_redis_cache do
|
|||
end
|
||||
|
||||
describe '#issuable_templates_names' do
|
||||
let(:project) { double(Project, id: 21) }
|
||||
let_it_be(:project) { build(:project) }
|
||||
|
||||
let(:templates) do
|
||||
[
|
||||
{ name: "another_issue_template", id: "another_issue_template", project_id: project.id },
|
||||
{ name: "custom_issue_template", id: "custom_issue_template", project_id: project.id }
|
||||
]
|
||||
end
|
||||
|
||||
it 'returns project templates only' do
|
||||
before do
|
||||
allow(helper).to receive(:ref_project).and_return(project)
|
||||
allow(helper).to receive(:issuable_templates).and_return(templates)
|
||||
end
|
||||
|
||||
expect(helper.issuable_templates_names(Issue.new)).to eq(%w[another_issue_template custom_issue_template])
|
||||
context 'when feature flag disabled' do
|
||||
let(:templates) do
|
||||
[
|
||||
{ name: "another_issue_template", id: "another_issue_template", project_id: project.id },
|
||||
{ name: "custom_issue_template", id: "custom_issue_template", project_id: project.id }
|
||||
]
|
||||
end
|
||||
|
||||
before do
|
||||
stub_feature_flags(inherited_issuable_templates: false)
|
||||
end
|
||||
|
||||
it 'returns project templates only' do
|
||||
expect(helper.issuable_templates_names(Issue.new)).to eq(%w[another_issue_template custom_issue_template])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when feature flag enabled' do
|
||||
before do
|
||||
stub_feature_flags(inherited_issuable_templates: true)
|
||||
end
|
||||
|
||||
context 'with matching project templates' do
|
||||
let(:templates) do
|
||||
{
|
||||
"" => [
|
||||
{ name: "another_issue_template", id: "another_issue_template", project_id: project.id },
|
||||
{ name: "custom_issue_template", id: "custom_issue_template", project_id: project.id }
|
||||
],
|
||||
"Instance" => [
|
||||
{ name: "first_issue_issue_template", id: "first_issue_issue_template", project_id: non_existing_record_id },
|
||||
{ name: "second_instance_issue_template", id: "second_instance_issue_template", project_id: non_existing_record_id }
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
it 'returns project templates only' do
|
||||
expect(helper.issuable_templates_names(Issue.new)).to eq(%w[another_issue_template custom_issue_template])
|
||||
end
|
||||
end
|
||||
|
||||
context 'without matching project templates' do
|
||||
let(:templates) do
|
||||
{
|
||||
"Project Templates" => [
|
||||
{ name: "another_issue_template", id: "another_issue_template", project_id: non_existing_record_id },
|
||||
{ name: "custom_issue_template", id: "custom_issue_template", project_id: non_existing_record_id }
|
||||
],
|
||||
"Instance" => [
|
||||
{ name: "first_issue_issue_template", id: "first_issue_issue_template", project_id: non_existing_record_id },
|
||||
{ name: "second_instance_issue_template", id: "second_instance_issue_template", project_id: non_existing_record_id }
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
it 'returns empty array' do
|
||||
expect(helper.issuable_templates_names(Issue.new)).to eq([])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there are not templates in the project' do
|
||||
let(:templates) { {} }
|
||||
|
||||
it 'returns empty array' do
|
||||
allow(helper).to receive(:ref_project).and_return(project)
|
||||
allow(helper).to receive(:issuable_templates).and_return(templates)
|
||||
|
||||
expect(helper.issuable_templates_names(Issue.new)).to eq([])
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,11 +4,11 @@ require 'spec_helper'
|
|||
|
||||
RSpec.describe Issues::ExportCsvService do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let(:group) { create(:group) }
|
||||
let(:project) { create(:project, :public, group: group) }
|
||||
let!(:issue) { create(:issue, project: project, author: user) }
|
||||
let!(:bad_issue) { create(:issue, project: project, author: user) }
|
||||
let(:subject) { described_class.new(Issue.all, project) }
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:project) { create(:project, :public, group: group) }
|
||||
let_it_be(:issue) { create(:issue, project: project, author: user) }
|
||||
let_it_be(:bad_issue) { create(:issue, project: project, author: user) }
|
||||
subject { described_class.new(Issue.all, project) }
|
||||
|
||||
it 'renders csv to string' do
|
||||
expect(subject.csv_data).to be_a String
|
||||
|
@ -33,11 +33,11 @@ RSpec.describe Issues::ExportCsvService do
|
|||
end
|
||||
|
||||
context 'includes' do
|
||||
let(:milestone) { create(:milestone, title: 'v1.0', project: project) }
|
||||
let(:idea_label) { create(:label, project: project, title: 'Idea') }
|
||||
let(:feature_label) { create(:label, project: project, title: 'Feature') }
|
||||
let_it_be(:milestone) { create(:milestone, title: 'v1.0', project: project) }
|
||||
let_it_be(:idea_label) { create(:label, project: project, title: 'Idea') }
|
||||
let_it_be(:feature_label) { create(:label, project: project, title: 'Feature') }
|
||||
|
||||
before do
|
||||
before_all do
|
||||
# Creating a timelog touches the updated_at timestamp of issue,
|
||||
# so create these first.
|
||||
issue.timelogs.create!(time_spent: 360, user: user)
|
||||
|
@ -60,6 +60,10 @@ RSpec.describe Issues::ExportCsvService do
|
|||
expect(csv.headers).to include('Title', 'Description')
|
||||
end
|
||||
|
||||
it 'returns two issues' do
|
||||
expect(csv.count).to eq(2)
|
||||
end
|
||||
|
||||
specify 'iid' do
|
||||
expect(csv[0]['Issue ID']).to eq issue.iid.to_s
|
||||
end
|
||||
|
@ -150,7 +154,7 @@ RSpec.describe Issues::ExportCsvService do
|
|||
end
|
||||
|
||||
context 'with issues filtered by labels and project' do
|
||||
let(:subject) do
|
||||
subject do
|
||||
described_class.new(
|
||||
IssuesFinder.new(user,
|
||||
project_id: project.id,
|
||||
|
@ -162,6 +166,27 @@ RSpec.describe Issues::ExportCsvService do
|
|||
expect(csv[0]['Issue ID']).to eq issue.iid.to_s
|
||||
end
|
||||
end
|
||||
|
||||
context 'with label links' do
|
||||
let(:labeled_issues) { create_list(:labeled_issue, 2, project: project, author: user, labels: [feature_label, idea_label]) }
|
||||
|
||||
it 'does not run a query for each label link' do
|
||||
control_count = ActiveRecord::QueryRecorder.new { csv }.count
|
||||
|
||||
labeled_issues
|
||||
|
||||
expect { csv }.not_to exceed_query_limit(control_count)
|
||||
expect(csv.count).to eq(4)
|
||||
end
|
||||
|
||||
it 'returns the labels in sorted order' do
|
||||
labeled_issues
|
||||
|
||||
labeled_rows = csv.select { |entry| labeled_issues.map(&:iid).include?(entry['Issue ID'].to_i) }
|
||||
expect(labeled_rows.count).to eq(2)
|
||||
expect(labeled_rows.map { |entry| entry['Labels'] }).to all( eq("Feature,Idea") )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with minimal details' do
|
||||
|
|
|
@ -349,27 +349,38 @@ RSpec.describe Projects::CreateService, '#execute' do
|
|||
context 'default visibility level' do
|
||||
let(:group) { create(:group, :private) }
|
||||
|
||||
before do
|
||||
stub_application_setting(default_project_visibility: Gitlab::VisibilityLevel::INTERNAL)
|
||||
group.add_developer(user)
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
opts.merge!(
|
||||
visibility: 'private',
|
||||
name: 'test',
|
||||
namespace: group,
|
||||
path: 'foo'
|
||||
)
|
||||
where(:case_name, :group_level, :project_level) do
|
||||
[
|
||||
['in public group', Gitlab::VisibilityLevel::PUBLIC, Gitlab::VisibilityLevel::INTERNAL],
|
||||
['in internal group', Gitlab::VisibilityLevel::INTERNAL, Gitlab::VisibilityLevel::INTERNAL],
|
||||
['in private group', Gitlab::VisibilityLevel::PRIVATE, Gitlab::VisibilityLevel::PRIVATE]
|
||||
]
|
||||
end
|
||||
|
||||
it 'creates a private project' do
|
||||
project = create_project(user, opts)
|
||||
with_them do
|
||||
before do
|
||||
stub_application_setting(default_project_visibility: Gitlab::VisibilityLevel::INTERNAL)
|
||||
group.add_developer(user)
|
||||
group.update!(visibility_level: group_level)
|
||||
|
||||
expect(project).to respond_to(:errors)
|
||||
opts.merge!(
|
||||
name: 'test',
|
||||
namespace: group,
|
||||
path: 'foo'
|
||||
)
|
||||
end
|
||||
|
||||
expect(project.errors.any?).to be(false)
|
||||
expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE)
|
||||
expect(project.saved?).to be(true)
|
||||
expect(project.valid?).to be(true)
|
||||
it 'creates project with correct visibility level', :aggregate_failures do
|
||||
project = create_project(user, opts)
|
||||
|
||||
expect(project).to respond_to(:errors)
|
||||
expect(project.errors).to be_blank
|
||||
expect(project.visibility_level).to eq(project_level)
|
||||
expect(project).to be_saved
|
||||
expect(project).to be_valid
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -23,11 +23,11 @@ RSpec.shared_examples 'project issuable templates' do
|
|||
end
|
||||
|
||||
it 'returns only md files as issue templates' do
|
||||
expect(helper.issuable_templates(project, 'issue')).to eq(templates('issue', project))
|
||||
expect(helper.issuable_templates(project, 'issue')).to eq(expected_templates('issue'))
|
||||
end
|
||||
|
||||
it 'returns only md files as merge_request templates' do
|
||||
expect(helper.issuable_templates(project, 'merge_request')).to eq(templates('merge_request', project))
|
||||
expect(helper.issuable_templates(project, 'merge_request')).to eq(expected_templates('merge_request'))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name|
|
|||
let(:webhook_url) { 'https://example.gitlab.com' }
|
||||
|
||||
def execute_with_options(options)
|
||||
receive(:new).with(webhook_url, options.merge(http_client: SlackService::Notifier::HTTPClient))
|
||||
receive(:new).with(webhook_url, options.merge(http_client: SlackMattermost::Notifier::HTTPClient))
|
||||
.and_return(double(:slack_service).as_null_object)
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.shared_examples 'issue description templates from current project only' do
|
||||
it 'loads issue description templates from the project only' do
|
||||
within('#service-desk-template-select') do
|
||||
expect(page).to have_content('project-issue-bar')
|
||||
expect(page).to have_content('project-issue-foo')
|
||||
expect(page).not_to have_content('group-issue-bar')
|
||||
expect(page).not_to have_content('group-issue-foo')
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue