Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
ad2789aeba
commit
e5f2a04e9d
|
@ -83,8 +83,8 @@ const Api = {
|
|||
tagsPath: '/api/:version/projects/:id/repository/tags',
|
||||
freezePeriodsPath: '/api/:version/projects/:id/freeze_periods',
|
||||
freezePeriodPath: '/api/:version/projects/:id/freeze_periods/:freeze_period_id',
|
||||
usageDataIncrementCounterPath: '/api/:version/usage_data/increment_counter',
|
||||
usageDataIncrementUniqueUsersPath: '/api/:version/usage_data/increment_unique_users',
|
||||
serviceDataIncrementCounterPath: '/api/:version/usage_data/increment_counter',
|
||||
serviceDataIncrementUniqueUsersPath: '/api/:version/usage_data/increment_unique_users',
|
||||
featureFlagUserLists: '/api/:version/projects/:id/feature_flags_user_lists',
|
||||
featureFlagUserList: '/api/:version/projects/:id/feature_flags_user_lists/:list_iid',
|
||||
containerRegistryDetailsPath: '/api/:version/registry/repositories/:id/',
|
||||
|
@ -875,7 +875,7 @@ const Api = {
|
|||
return null;
|
||||
}
|
||||
|
||||
const url = Api.buildUrl(this.usageDataIncrementCounterPath);
|
||||
const url = Api.buildUrl(this.serviceDataIncrementCounterPath);
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
|
@ -888,7 +888,7 @@ const Api = {
|
|||
return null;
|
||||
}
|
||||
|
||||
const url = Api.buildUrl(this.usageDataIncrementUniqueUsersPath);
|
||||
const url = Api.buildUrl(this.serviceDataIncrementUniqueUsersPath);
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
|
|
|
@ -41,7 +41,7 @@ import {
|
|||
TOGGLE_TODO_ERROR,
|
||||
designDeletionError,
|
||||
} from '../../utils/error_messages';
|
||||
import { trackDesignDetailView, usagePingDesignDetailView } from '../../utils/tracking';
|
||||
import { trackDesignDetailView, servicePingDesignDetailView } from '../../utils/tracking';
|
||||
|
||||
const DEFAULT_SCALE = 1;
|
||||
|
||||
|
@ -292,7 +292,7 @@ export default {
|
|||
);
|
||||
|
||||
if (this.glFeatures.usageDataDesignAction) {
|
||||
usagePingDesignDetailView();
|
||||
servicePingDesignDetailView();
|
||||
}
|
||||
},
|
||||
updateActiveDiscussion(id, source = ACTIVE_DISCUSSION_SOURCE_TYPES.discussion) {
|
||||
|
|
|
@ -14,7 +14,7 @@ export const DESIGN_SNOWPLOW_EVENT_TYPES = {
|
|||
UPDATE_DESIGN: 'update_design',
|
||||
};
|
||||
|
||||
export const DESIGN_USAGE_PING_EVENT_TYPES = {
|
||||
export const DESIGN_SERVICE_PING_EVENT_TYPES = {
|
||||
DESIGN_ACTION: 'design_action',
|
||||
};
|
||||
|
||||
|
@ -52,8 +52,8 @@ export function trackDesignUpdate() {
|
|||
}
|
||||
|
||||
/**
|
||||
* Track "design detail" view via usage ping
|
||||
* Track "design detail" view via service ping
|
||||
*/
|
||||
export function usagePingDesignDetailView() {
|
||||
Api.trackRedisHllUserEvent(DESIGN_USAGE_PING_EVENT_TYPES.DESIGN_ACTION);
|
||||
export function servicePingDesignDetailView() {
|
||||
Api.trackRedisHllUserEvent(DESIGN_SERVICE_PING_EVENT_TYPES.DESIGN_ACTION);
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
import fuzzaldrinPlus from 'fuzzaldrin-plus';
|
||||
import getIssuesQuery from 'ee_else_ce/issues_list/queries/get_issues.query.graphql';
|
||||
import createFlash from '~/flash';
|
||||
import { convertToGraphQLId } from '~/graphql_shared/utils';
|
||||
import CsvImportExportButtons from '~/issuable/components/csv_import_export_buttons.vue';
|
||||
import IssuableByEmail from '~/issuable/components/issuable_by_email.vue';
|
||||
import IssuableList from '~/issuable_list/components/issuable_list_root.vue';
|
||||
|
@ -70,6 +71,10 @@ import LabelToken from '~/vue_shared/components/filtered_search_bar/tokens/label
|
|||
import MilestoneToken from '~/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue';
|
||||
import WeightToken from '~/vue_shared/components/filtered_search_bar/tokens/weight_token.vue';
|
||||
import eventHub from '../eventhub';
|
||||
import searchIterationsQuery from '../queries/search_iterations.query.graphql';
|
||||
import searchLabelsQuery from '../queries/search_labels.query.graphql';
|
||||
import searchMilestonesQuery from '../queries/search_milestones.query.graphql';
|
||||
import searchUsersQuery from '../queries/search_users.query.graphql';
|
||||
import IssueCardTimeInfo from './issue_card_time_info.vue';
|
||||
|
||||
export default {
|
||||
|
@ -94,9 +99,6 @@ export default {
|
|||
autocompleteAwardEmojisPath: {
|
||||
default: '',
|
||||
},
|
||||
autocompleteUsersPath: {
|
||||
default: '',
|
||||
},
|
||||
calendarPath: {
|
||||
default: '',
|
||||
},
|
||||
|
@ -118,6 +120,9 @@ export default {
|
|||
hasIssueWeightsFeature: {
|
||||
default: false,
|
||||
},
|
||||
hasIterationsFeature: {
|
||||
default: false,
|
||||
},
|
||||
hasMultipleIssueAssigneesFeature: {
|
||||
default: false,
|
||||
},
|
||||
|
@ -139,15 +144,6 @@ export default {
|
|||
newIssuePath: {
|
||||
default: '',
|
||||
},
|
||||
projectIterationsPath: {
|
||||
default: '',
|
||||
},
|
||||
projectLabelsPath: {
|
||||
default: '',
|
||||
},
|
||||
projectMilestonesPath: {
|
||||
default: '',
|
||||
},
|
||||
projectPath: {
|
||||
default: '',
|
||||
},
|
||||
|
@ -233,7 +229,7 @@ export default {
|
|||
|
||||
if (gon.current_user_id) {
|
||||
preloadedAuthors.push({
|
||||
id: gon.current_user_id,
|
||||
id: convertToGraphQLId('User', gon.current_user_id), // eslint-disable-line @gitlab/require-i18n-strings
|
||||
name: gon.current_user_fullname,
|
||||
username: gon.current_username,
|
||||
avatar_url: gon.current_user_avatar_url,
|
||||
|
@ -308,7 +304,7 @@ export default {
|
|||
});
|
||||
}
|
||||
|
||||
if (this.projectIterationsPath) {
|
||||
if (this.hasIterationsFeature) {
|
||||
tokens.push({
|
||||
type: TOKEN_TYPE_ITERATION,
|
||||
title: TOKEN_TITLE_ITERATION,
|
||||
|
@ -407,19 +403,42 @@ export default {
|
|||
: epics.filter((epic) => epic.id === number);
|
||||
},
|
||||
fetchLabels(search) {
|
||||
return this.fetchWithCache(this.projectLabelsPath, 'labels', 'title', search);
|
||||
return this.$apollo
|
||||
.query({
|
||||
query: searchLabelsQuery,
|
||||
variables: { projectPath: this.projectPath, search },
|
||||
})
|
||||
.then(({ data }) => data.project.labels.nodes);
|
||||
},
|
||||
fetchMilestones(search) {
|
||||
return this.fetchWithCache(this.projectMilestonesPath, 'milestones', 'title', search, true);
|
||||
return this.$apollo
|
||||
.query({
|
||||
query: searchMilestonesQuery,
|
||||
variables: { projectPath: this.projectPath, search },
|
||||
})
|
||||
.then(({ data }) => data.project.milestones.nodes);
|
||||
},
|
||||
fetchIterations(search) {
|
||||
const id = Number(search);
|
||||
return !search || Number.isNaN(id)
|
||||
? axios.get(this.projectIterationsPath, { params: { search } })
|
||||
: axios.get(this.projectIterationsPath, { params: { id } });
|
||||
const variables =
|
||||
!search || Number.isNaN(id)
|
||||
? { projectPath: this.projectPath, search }
|
||||
: { projectPath: this.projectPath, id };
|
||||
|
||||
return this.$apollo
|
||||
.query({
|
||||
query: searchIterationsQuery,
|
||||
variables,
|
||||
})
|
||||
.then(({ data }) => data.project.iterations.nodes);
|
||||
},
|
||||
fetchUsers(search) {
|
||||
return axios.get(this.autocompleteUsersPath, { params: { search } });
|
||||
return this.$apollo
|
||||
.query({
|
||||
query: searchUsersQuery,
|
||||
variables: { projectPath: this.projectPath, search },
|
||||
})
|
||||
.then(({ data }) => data.project.projectMembers.nodes.map((member) => member.user));
|
||||
},
|
||||
getExportCsvPathWithQuery() {
|
||||
return `${this.exportCsvPath}${window.location.search}`;
|
||||
|
|
|
@ -82,7 +82,6 @@ export function mountIssuesListApp() {
|
|||
|
||||
const {
|
||||
autocompleteAwardEmojisPath,
|
||||
autocompleteUsersPath,
|
||||
calendarPath,
|
||||
canBulkUpdate,
|
||||
canEdit,
|
||||
|
@ -95,6 +94,7 @@ export function mountIssuesListApp() {
|
|||
hasBlockedIssuesFeature,
|
||||
hasIssuableHealthStatusFeature,
|
||||
hasIssueWeightsFeature,
|
||||
hasIterationsFeature,
|
||||
hasMultipleIssueAssigneesFeature,
|
||||
hasProjectIssues,
|
||||
importCsvIssuesPath,
|
||||
|
@ -106,9 +106,6 @@ export function mountIssuesListApp() {
|
|||
maxAttachmentSize,
|
||||
newIssuePath,
|
||||
projectImportJiraPath,
|
||||
projectIterationsPath,
|
||||
projectLabelsPath,
|
||||
projectMilestonesPath,
|
||||
projectPath,
|
||||
quickActionsHelpPath,
|
||||
resetPath,
|
||||
|
@ -122,7 +119,6 @@ export function mountIssuesListApp() {
|
|||
apolloProvider,
|
||||
provide: {
|
||||
autocompleteAwardEmojisPath,
|
||||
autocompleteUsersPath,
|
||||
calendarPath,
|
||||
canBulkUpdate: parseBoolean(canBulkUpdate),
|
||||
emptyStateSvgPath,
|
||||
|
@ -130,15 +126,13 @@ export function mountIssuesListApp() {
|
|||
hasBlockedIssuesFeature: parseBoolean(hasBlockedIssuesFeature),
|
||||
hasIssuableHealthStatusFeature: parseBoolean(hasIssuableHealthStatusFeature),
|
||||
hasIssueWeightsFeature: parseBoolean(hasIssueWeightsFeature),
|
||||
hasIterationsFeature: parseBoolean(hasIterationsFeature),
|
||||
hasMultipleIssueAssigneesFeature: parseBoolean(hasMultipleIssueAssigneesFeature),
|
||||
hasProjectIssues: parseBoolean(hasProjectIssues),
|
||||
isSignedIn: parseBoolean(isSignedIn),
|
||||
issuesPath,
|
||||
jiraIntegrationPath,
|
||||
newIssuePath,
|
||||
projectIterationsPath,
|
||||
projectLabelsPath,
|
||||
projectMilestonesPath,
|
||||
projectPath,
|
||||
rssPath,
|
||||
showNewIssueLink: parseBoolean(showNewIssueLink),
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
query searchIterations($projectPath: ID!, $search: String, $id: ID) {
|
||||
project(fullPath: $projectPath) {
|
||||
iterations(title: $search, id: $id) {
|
||||
nodes {
|
||||
id
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
query searchLabels($projectPath: ID!, $search: String) {
|
||||
project(fullPath: $projectPath) {
|
||||
labels(searchTerm: $search, includeAncestorGroups: true) {
|
||||
nodes {
|
||||
id
|
||||
color
|
||||
textColor
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
query searchMilestones($projectPath: ID!, $search: String) {
|
||||
project(fullPath: $projectPath) {
|
||||
milestones(searchTitle: $search, includeAncestors: true) {
|
||||
nodes {
|
||||
id
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
query searchUsers($projectPath: ID!, $search: String) {
|
||||
project(fullPath: $projectPath) {
|
||||
projectMembers(search: $search) {
|
||||
nodes {
|
||||
user {
|
||||
id
|
||||
avatarUrl
|
||||
name
|
||||
username
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -31,7 +31,7 @@ import initFrequentItemDropdowns from './frequent_items';
|
|||
import initBreadcrumbs from './breadcrumb';
|
||||
import initPersistentUserCallouts from './persistent_user_callouts';
|
||||
import { initUserTracking, initDefaultTrackers } from './tracking';
|
||||
import initUsagePingConsent from './usage_ping_consent';
|
||||
import initServicePingConsent from './service_ping_consent';
|
||||
import GlFieldErrors from './gl_field_errors';
|
||||
import initUserPopovers from './user_popovers';
|
||||
import initBroadcastNotifications from './broadcast_notification';
|
||||
|
@ -86,7 +86,7 @@ function deferredInitialisation() {
|
|||
initBreadcrumbs();
|
||||
initTodoToggle();
|
||||
initLogoAnimation();
|
||||
initUsagePingConsent();
|
||||
initServicePingConsent();
|
||||
initUserPopovers();
|
||||
initBroadcastNotifications();
|
||||
initFrequentItemDropdowns();
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<script>
|
||||
import { GlLoadingIcon } from '@gitlab/ui';
|
||||
import { fetchPolicies } from '~/lib/graphql';
|
||||
import { queryToObject } from '~/lib/utils/url_utility';
|
||||
import { s__ } from '~/locale';
|
||||
|
||||
import { unwrapStagesWithNeeds } from '~/pipelines/components/unwrapping_utils';
|
||||
|
@ -41,6 +42,7 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
starterTemplateName: STARTER_TEMPLATE_NAME,
|
||||
ciConfigData: {},
|
||||
failureType: null,
|
||||
failureReasons: [],
|
||||
|
@ -160,7 +162,7 @@ export default {
|
|||
variables() {
|
||||
return {
|
||||
projectPath: this.projectFullPath,
|
||||
templateName: STARTER_TEMPLATE_NAME,
|
||||
templateName: this.starterTemplateName,
|
||||
};
|
||||
},
|
||||
skip({ isNewCiConfigFile }) {
|
||||
|
@ -203,6 +205,9 @@ export default {
|
|||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.loadTemplateFromURL();
|
||||
},
|
||||
methods: {
|
||||
hideFailure() {
|
||||
this.showFailure = false;
|
||||
|
@ -258,6 +263,14 @@ export default {
|
|||
// if the user has made changes to the file that are unsaved.
|
||||
this.lastCommittedContent = this.currentCiFileContent;
|
||||
},
|
||||
loadTemplateFromURL() {
|
||||
const templateName = queryToObject(window.location.search)?.template;
|
||||
|
||||
if (templateName) {
|
||||
this.starterTemplateName = templateName;
|
||||
this.setNewEmptyCiConfigFile();
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -16,7 +16,6 @@ export default {
|
|||
consuming tasks, so you can spend more time creating.`),
|
||||
aboutRunnersBtnText: s__('Pipelines|Learn about Runners'),
|
||||
installRunnersBtnText: s__('Pipelines|Install GitLab Runners'),
|
||||
getStartedBtnText: s__('Pipelines|Get started with CI/CD'),
|
||||
codeQualityTitle: s__('Pipelines|Improve code quality with GitLab CI/CD'),
|
||||
codeQualityDescription: s__(`Pipelines|To keep your codebase simple,
|
||||
readable, and accessible to contributors, use GitLab CI/CD
|
||||
|
@ -55,9 +54,6 @@ export default {
|
|||
ciHelpPagePath() {
|
||||
return helpPagePath('ci/quick_start/index.md');
|
||||
},
|
||||
isPipelineEmptyStateTemplatesExperimentActive() {
|
||||
return this.canSetCi && Boolean(getExperimentData('pipeline_empty_state_templates'));
|
||||
},
|
||||
isCodeQualityExperimentActive() {
|
||||
return this.canSetCi && Boolean(getExperimentData('code_quality_walkthrough'));
|
||||
},
|
||||
|
@ -81,37 +77,8 @@ export default {
|
|||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<gitlab-experiment
|
||||
v-if="isPipelineEmptyStateTemplatesExperimentActive"
|
||||
name="pipeline_empty_state_templates"
|
||||
>
|
||||
<template #control>
|
||||
<gl-empty-state
|
||||
:title="$options.i18n.title"
|
||||
:svg-path="emptyStateSvgPath"
|
||||
:description="$options.i18n.description"
|
||||
:primary-button-text="$options.i18n.getStartedBtnText"
|
||||
:primary-button-link="ciHelpPagePath"
|
||||
/>
|
||||
</template>
|
||||
<template #candidate>
|
||||
<pipelines-ci-templates />
|
||||
</template>
|
||||
</gitlab-experiment>
|
||||
<gitlab-experiment v-else-if="isCodeQualityExperimentActive" name="code_quality_walkthrough">
|
||||
<template #control>
|
||||
<gl-empty-state
|
||||
:title="$options.i18n.title"
|
||||
:svg-path="emptyStateSvgPath"
|
||||
:description="$options.i18n.description"
|
||||
>
|
||||
<template #actions>
|
||||
<gl-button :href="ciHelpPagePath" variant="confirm" @click="trackClick()">
|
||||
{{ $options.i18n.getStartedBtnText }}
|
||||
</gl-button>
|
||||
</template>
|
||||
</gl-empty-state>
|
||||
</template>
|
||||
<gitlab-experiment v-if="isCodeQualityExperimentActive" name="code_quality_walkthrough">
|
||||
<template #control><pipelines-ci-templates /></template>
|
||||
<template #candidate>
|
||||
<gl-empty-state
|
||||
:title="$options.i18n.codeQualityTitle"
|
||||
|
@ -127,23 +94,7 @@ export default {
|
|||
</template>
|
||||
</gitlab-experiment>
|
||||
<gitlab-experiment v-else-if="isCiRunnerTemplatesExperimentActive" name="ci_runner_templates">
|
||||
<template #control>
|
||||
<gl-empty-state
|
||||
:title="$options.i18n.title"
|
||||
:svg-path="emptyStateSvgPath"
|
||||
:description="$options.i18n.description"
|
||||
>
|
||||
<template #actions>
|
||||
<gl-button
|
||||
:href="ciHelpPagePath"
|
||||
variant="confirm"
|
||||
@click="trackCiRunnerTemplatesClick('get_started_button_clicked')"
|
||||
>
|
||||
{{ $options.i18n.getStartedBtnText }}
|
||||
</gl-button>
|
||||
</template>
|
||||
</gl-empty-state>
|
||||
</template>
|
||||
<template #control><pipelines-ci-templates /></template>
|
||||
<template #candidate>
|
||||
<gl-empty-state
|
||||
:title="$options.i18n.title"
|
||||
|
@ -169,14 +120,7 @@ export default {
|
|||
</gl-empty-state>
|
||||
</template>
|
||||
</gitlab-experiment>
|
||||
<gl-empty-state
|
||||
v-else-if="canSetCi"
|
||||
:title="$options.i18n.title"
|
||||
:svg-path="emptyStateSvgPath"
|
||||
:description="$options.i18n.description"
|
||||
:primary-button-text="$options.i18n.getStartedBtnText"
|
||||
:primary-button-link="ciHelpPagePath"
|
||||
/>
|
||||
<pipelines-ci-templates v-else-if="canSetCi" />
|
||||
<gl-empty-state
|
||||
v-else
|
||||
title=""
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<script>
|
||||
import { GlAvatar, GlButton, GlCard, GlSprintf } from '@gitlab/ui';
|
||||
import ExperimentTracking from '~/experimentation/experiment_tracking';
|
||||
import { mergeUrlParams } from '~/lib/utils/url_utility';
|
||||
import { s__, sprintf } from '~/locale';
|
||||
import { HELLO_WORLD_TEMPLATE_KEY } from '../../constants';
|
||||
import { STARTER_TEMPLATE_NAME } from '~/pipeline_editor/constants';
|
||||
import Tracking from '~/tracking';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
@ -12,7 +12,8 @@ export default {
|
|||
GlCard,
|
||||
GlSprintf,
|
||||
},
|
||||
HELLO_WORLD_TEMPLATE_KEY,
|
||||
mixins: [Tracking.mixin()],
|
||||
STARTER_TEMPLATE_NAME,
|
||||
i18n: {
|
||||
cta: s__('Pipelines|Use template'),
|
||||
testTemplates: {
|
||||
|
@ -20,10 +21,10 @@ export default {
|
|||
subtitle: s__(
|
||||
'Pipelines|Use a sample %{codeStart}.gitlab-ci.yml%{codeEnd} template file to explore how CI/CD works.',
|
||||
),
|
||||
helloWorld: {
|
||||
title: s__('Pipelines|“Hello world” with GitLab CI/CD'),
|
||||
gettingStarted: {
|
||||
title: s__('Pipelines|Get started with GitLab CI/CD'),
|
||||
description: s__(
|
||||
'Pipelines|Get familiar with GitLab CI/CD syntax by starting with a simple pipeline that runs a “Hello world” script.',
|
||||
'Pipelines|Get familiar with GitLab CI/CD syntax by starting with a basic 3 stage CI/CD pipeline.',
|
||||
),
|
||||
},
|
||||
},
|
||||
|
@ -35,31 +36,30 @@ export default {
|
|||
description: s__('Pipelines|CI/CD template to test and deploy your %{name} project.'),
|
||||
},
|
||||
},
|
||||
inject: ['addCiYmlPath', 'suggestedCiTemplates'],
|
||||
inject: ['pipelineEditorPath', 'suggestedCiTemplates'],
|
||||
data() {
|
||||
const templates = this.suggestedCiTemplates.map(({ name, logo }) => {
|
||||
return {
|
||||
name,
|
||||
logo,
|
||||
link: mergeUrlParams({ template: name }, this.addCiYmlPath),
|
||||
link: mergeUrlParams({ template: name }, this.pipelineEditorPath),
|
||||
description: sprintf(this.$options.i18n.templates.description, { name }),
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
templates,
|
||||
helloWorldTemplateUrl: mergeUrlParams(
|
||||
{ template: HELLO_WORLD_TEMPLATE_KEY },
|
||||
this.addCiYmlPath,
|
||||
gettingStartedTemplateUrl: mergeUrlParams(
|
||||
{ template: STARTER_TEMPLATE_NAME },
|
||||
this.pipelineEditorPath,
|
||||
),
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
trackEvent(template) {
|
||||
const tracking = new ExperimentTracking('pipeline_empty_state_templates', {
|
||||
this.track('template_clicked', {
|
||||
label: template,
|
||||
});
|
||||
tracking.event('template_clicked');
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -82,18 +82,18 @@ export default {
|
|||
<div class="gl-py-5"><gl-emoji class="gl-font-size-h2-xl" data-name="wave" /></div>
|
||||
<div class="gl-mb-3">
|
||||
<strong class="gl-text-gray-800 gl-mb-2">{{
|
||||
$options.i18n.testTemplates.helloWorld.title
|
||||
$options.i18n.testTemplates.gettingStarted.title
|
||||
}}</strong>
|
||||
</div>
|
||||
<p class="gl-font-sm">{{ $options.i18n.testTemplates.helloWorld.description }}</p>
|
||||
<p class="gl-font-sm">{{ $options.i18n.testTemplates.gettingStarted.description }}</p>
|
||||
</div>
|
||||
|
||||
<gl-button
|
||||
category="primary"
|
||||
variant="confirm"
|
||||
:href="helloWorldTemplateUrl"
|
||||
:href="gettingStartedTemplateUrl"
|
||||
data-testid="test-template-link"
|
||||
@click="trackEvent($options.HELLO_WORLD_TEMPLATE_KEY)"
|
||||
@click="trackEvent($options.STARTER_TEMPLATE_NAME)"
|
||||
>
|
||||
{{ $options.i18n.cta }}
|
||||
</gl-button>
|
||||
|
|
|
@ -35,6 +35,3 @@ export const POST_FAILURE = 'post_failure';
|
|||
export const UNSUPPORTED_DATA = 'unsupported_data';
|
||||
|
||||
export const CHILD_VIEW = 'child';
|
||||
|
||||
// The key of the template is the same as the filename
|
||||
export const HELLO_WORLD_TEMPLATE_KEY = 'Hello-World';
|
||||
|
|
|
@ -29,7 +29,7 @@ export const initPipelinesIndex = (selector = '#pipelines-list-vue') => {
|
|||
errorStateSvgPath,
|
||||
noPipelinesSvgPath,
|
||||
newPipelinePath,
|
||||
addCiYmlPath,
|
||||
pipelineEditorPath,
|
||||
suggestedCiTemplates,
|
||||
canCreatePipeline,
|
||||
hasGitlabCi,
|
||||
|
@ -44,7 +44,7 @@ export const initPipelinesIndex = (selector = '#pipelines-list-vue') => {
|
|||
return new Vue({
|
||||
el,
|
||||
provide: {
|
||||
addCiYmlPath,
|
||||
pipelineEditorPath,
|
||||
artifactsEndpoint,
|
||||
artifactsEndpointPlaceholder,
|
||||
suggestedCiTemplates: JSON.parse(suggestedCiTemplates),
|
||||
|
|
|
@ -5,19 +5,20 @@ import { parseBoolean } from './lib/utils/common_utils';
|
|||
import { __ } from './locale';
|
||||
|
||||
export default () => {
|
||||
$('body').on('click', '.js-usage-consent-action', (e) => {
|
||||
$('body').on('click', '.js-service-ping-consent-action', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation(); // overwrite rails listener
|
||||
|
||||
const { url, checkEnabled, pingEnabled } = e.target.dataset;
|
||||
const { url, checkEnabled, servicePingEnabled } = e.target.dataset;
|
||||
const data = {
|
||||
application_setting: {
|
||||
version_check_enabled: parseBoolean(checkEnabled),
|
||||
usage_ping_enabled: parseBoolean(pingEnabled),
|
||||
service_ping_enabled: parseBoolean(servicePingEnabled),
|
||||
},
|
||||
};
|
||||
|
||||
const hideConsentMessage = () => hideFlash(document.querySelector('.ping-consent-message'));
|
||||
const hideConsentMessage = () =>
|
||||
hideFlash(document.querySelector('.service-ping-consent-message'));
|
||||
|
||||
axios
|
||||
.put(url, data)
|
|
@ -28,7 +28,8 @@ export const TRACKING_ACTION_CREATE_COMMIT = 'create_commit';
|
|||
export const TRACKING_ACTION_CREATE_MERGE_REQUEST = 'create_merge_request';
|
||||
export const TRACKING_ACTION_INITIALIZE_EDITOR = 'initialize_editor';
|
||||
|
||||
export const USAGE_PING_TRACKING_ACTION_CREATE_COMMIT = 'static_site_editor_commits';
|
||||
export const USAGE_PING_TRACKING_ACTION_CREATE_MERGE_REQUEST = 'static_site_editor_merge_requests';
|
||||
export const SERVICE_PING_TRACKING_ACTION_CREATE_COMMIT = 'static_site_editor_commits';
|
||||
export const SERVICE_PING_TRACKING_ACTION_CREATE_MERGE_REQUEST =
|
||||
'static_site_editor_merge_requests';
|
||||
|
||||
export const MR_META_LOCAL_STORAGE_KEY = 'sse-merge-request-meta-storage-key';
|
||||
|
|
|
@ -9,8 +9,8 @@ import {
|
|||
SUBMIT_CHANGES_MERGE_REQUEST_ERROR,
|
||||
TRACKING_ACTION_CREATE_COMMIT,
|
||||
TRACKING_ACTION_CREATE_MERGE_REQUEST,
|
||||
USAGE_PING_TRACKING_ACTION_CREATE_COMMIT,
|
||||
USAGE_PING_TRACKING_ACTION_CREATE_MERGE_REQUEST,
|
||||
SERVICE_PING_TRACKING_ACTION_CREATE_COMMIT,
|
||||
SERVICE_PING_TRACKING_ACTION_CREATE_MERGE_REQUEST,
|
||||
DEFAULT_FORMATTING_CHANGES_COMMIT_MESSAGE,
|
||||
DEFAULT_FORMATTING_CHANGES_COMMIT_DESCRIPTION,
|
||||
} from '../constants';
|
||||
|
@ -58,7 +58,7 @@ const createUpdateSourceFileAction = (sourcePath, content) => [
|
|||
|
||||
const commit = (projectId, message, branch, actions) => {
|
||||
Tracking.event(document.body.dataset.page, TRACKING_ACTION_CREATE_COMMIT);
|
||||
Api.trackRedisCounterEvent(USAGE_PING_TRACKING_ACTION_CREATE_COMMIT);
|
||||
Api.trackRedisCounterEvent(SERVICE_PING_TRACKING_ACTION_CREATE_COMMIT);
|
||||
|
||||
return Api.commitMultiple(
|
||||
projectId,
|
||||
|
@ -74,7 +74,7 @@ const commit = (projectId, message, branch, actions) => {
|
|||
|
||||
const createMergeRequest = (projectId, title, description, sourceBranch, targetBranch) => {
|
||||
Tracking.event(document.body.dataset.page, TRACKING_ACTION_CREATE_MERGE_REQUEST);
|
||||
Api.trackRedisCounterEvent(USAGE_PING_TRACKING_ACTION_CREATE_MERGE_REQUEST);
|
||||
Api.trackRedisCounterEvent(SERVICE_PING_TRACKING_ACTION_CREATE_MERGE_REQUEST);
|
||||
|
||||
return Api.createProjectMergeRequest(
|
||||
projectId,
|
||||
|
|
|
@ -6,6 +6,7 @@ import {
|
|||
GlDropdownSectionHeader,
|
||||
GlLoadingIcon,
|
||||
} from '@gitlab/ui';
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
import { DEBOUNCE_DELAY } from '../constants';
|
||||
import { getRecentlyUsedSuggestions, setTokenValueToRecentlyUsed } from '../filtered_search_utils';
|
||||
|
@ -128,12 +129,12 @@ export default {
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
handleInput({ data }) {
|
||||
handleInput: debounce(function debouncedSearch({ data }) {
|
||||
this.searchKey = data;
|
||||
setTimeout(() => {
|
||||
if (!this.suggestionsLoading) this.$emit('fetch-suggestions', data);
|
||||
}, DEBOUNCE_DELAY);
|
||||
},
|
||||
if (!this.suggestionsLoading) {
|
||||
this.$emit('fetch-suggestions', data);
|
||||
}
|
||||
}, DEBOUNCE_DELAY),
|
||||
handleTokenValueSelected(activeTokenValue) {
|
||||
// Make sure that;
|
||||
// 1. Recently used values feature is enabled
|
||||
|
|
|
@ -7,6 +7,7 @@ import {
|
|||
} from '@gitlab/ui';
|
||||
import { debounce } from 'lodash';
|
||||
import createFlash from '~/flash';
|
||||
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||
import { __ } from '~/locale';
|
||||
import { DEBOUNCE_DELAY, DEFAULT_ITERATIONS } from '../constants';
|
||||
|
||||
|
@ -30,7 +31,7 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
iterations: this.config.initialIterations || [],
|
||||
loading: true,
|
||||
loading: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -38,7 +39,9 @@ export default {
|
|||
return this.value.data;
|
||||
},
|
||||
activeIteration() {
|
||||
return this.iterations.find((iteration) => iteration.id === Number(this.currentValue));
|
||||
return this.iterations.find(
|
||||
(iteration) => getIdFromGraphQLId(iteration.id) === Number(this.currentValue),
|
||||
);
|
||||
},
|
||||
defaultIterations() {
|
||||
return this.config.defaultIterations || DEFAULT_ITERATIONS;
|
||||
|
@ -55,6 +58,9 @@ export default {
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
getValue(iteration) {
|
||||
return String(getIdFromGraphQLId(iteration.id));
|
||||
},
|
||||
fetchIterationBySearchTerm(searchTerm) {
|
||||
const fetchPromise = this.config.fetchPath
|
||||
? this.config.fetchIterations(this.config.fetchPath, searchTerm)
|
||||
|
@ -102,7 +108,7 @@ export default {
|
|||
<gl-filtered-search-suggestion
|
||||
v-for="iteration in iterations"
|
||||
:key="iteration.id"
|
||||
:value="String(iteration.id)"
|
||||
:value="getValue(iteration)"
|
||||
>
|
||||
{{ iteration.title }}
|
||||
</gl-filtered-search-suggestion>
|
||||
|
|
|
@ -35,7 +35,7 @@ export default {
|
|||
return {
|
||||
milestones: this.config.initialMilestones || [],
|
||||
defaultMilestones: this.config.defaultMilestones || DEFAULT_MILESTONES,
|
||||
loading: true,
|
||||
loading: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -60,11 +60,16 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
fetchMilestoneBySearchTerm(searchTerm = '') {
|
||||
if (this.loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
this.config
|
||||
.fetchMilestones(searchTerm)
|
||||
.then(({ data }) => {
|
||||
this.milestones = data.sort(sortMilestonesByDueDate);
|
||||
.then((response) => {
|
||||
const data = Array.isArray(response) ? response : response.data;
|
||||
this.milestones = data.slice().sort(sortMilestonesByDueDate);
|
||||
})
|
||||
.catch(() => createFlash({ message: __('There was a problem fetching milestones.') }))
|
||||
.finally(() => {
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
.usage-data {
|
||||
.service-data-payload-container {
|
||||
max-height: 400px;
|
||||
}
|
||||
|
|
|
@ -151,14 +151,14 @@ module AuthenticatesWithTwoFactor
|
|||
|
||||
def handle_two_factor_failure(user, method, message)
|
||||
user.increment_failed_attempts!
|
||||
log_failed_two_factor(user, method, request.remote_ip)
|
||||
log_failed_two_factor(user, method)
|
||||
|
||||
Gitlab::AppLogger.info("Failed Login: user=#{user.username} ip=#{request.remote_ip} method=#{method}")
|
||||
flash.now[:alert] = message
|
||||
prompt_for_two_factor(user)
|
||||
end
|
||||
|
||||
def log_failed_two_factor(user, method, ip_address)
|
||||
def log_failed_two_factor(user, method)
|
||||
# overridden in EE
|
||||
end
|
||||
|
||||
|
|
|
@ -98,7 +98,7 @@ module AuthenticatesWithTwoFactorForAdminMode
|
|||
|
||||
def admin_handle_two_factor_failure(user, method, message)
|
||||
user.increment_failed_attempts!
|
||||
log_failed_two_factor(user, method, request.remote_ip)
|
||||
log_failed_two_factor(user, method)
|
||||
|
||||
Gitlab::AppLogger.info("Failed Admin Mode Login: user=#{user.username} ip=#{request.remote_ip} method=#{method}")
|
||||
flash.now[:alert] = message
|
||||
|
|
|
@ -15,7 +15,7 @@ class Projects::ArtifactsController < Projects::ApplicationController
|
|||
|
||||
MAX_PER_PAGE = 20
|
||||
|
||||
feature_category :continuous_integration
|
||||
feature_category :build_artifacts
|
||||
|
||||
def index
|
||||
# Loading artifacts is very expensive in projects with a lot of artifacts.
|
||||
|
|
|
@ -8,7 +8,7 @@ class Projects::BuildArtifactsController < Projects::ApplicationController
|
|||
before_action :extract_ref_name_and_path
|
||||
before_action :validate_artifacts!, except: [:download]
|
||||
|
||||
feature_category :continuous_integration
|
||||
feature_category :build_artifacts
|
||||
|
||||
def download
|
||||
redirect_to download_project_job_artifacts_path(project, job, params: request.query_parameters)
|
||||
|
|
|
@ -49,7 +49,6 @@ class Projects::PipelinesController < Projects::ApplicationController
|
|||
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
enable_pipeline_empty_state_templates_experiment
|
||||
enable_code_quality_walkthrough_experiment
|
||||
enable_ci_runner_templates_experiment
|
||||
end
|
||||
|
@ -301,18 +300,6 @@ class Projects::PipelinesController < Projects::ApplicationController
|
|||
params.permit(:scope, :username, :ref, :status)
|
||||
end
|
||||
|
||||
def enable_pipeline_empty_state_templates_experiment
|
||||
experiment(:pipeline_empty_state_templates, namespace: project.root_ancestor) do |e|
|
||||
e.exclude! unless current_user
|
||||
e.exclude! if @pipelines_count.to_i > 0
|
||||
e.exclude! if helpers.has_gitlab_ci?(project)
|
||||
|
||||
e.control {}
|
||||
e.candidate {}
|
||||
e.record!
|
||||
end
|
||||
end
|
||||
|
||||
def enable_code_quality_walkthrough_experiment
|
||||
experiment(:code_quality_walkthrough, namespace: project.root_ancestor) do |e|
|
||||
e.exclude! unless current_user
|
||||
|
|
|
@ -50,7 +50,7 @@ module Projects
|
|||
end
|
||||
|
||||
def create_params
|
||||
params.require(:project_access_token).permit(:name, :expires_at, scopes: [])
|
||||
params.require(:project_access_token).permit(:name, :expires_at, :access_level, scopes: [])
|
||||
end
|
||||
|
||||
def set_index_vars
|
||||
|
|
|
@ -127,6 +127,9 @@ module Types
|
|||
field :timelogs, Types::TimelogType.connection_type, null: false,
|
||||
description: 'Timelogs on the issue.'
|
||||
|
||||
field :project_id, GraphQL::INT_TYPE, null: false, method: :project_id,
|
||||
description: 'ID of the issue project.'
|
||||
|
||||
def author
|
||||
Gitlab::Graphql::Loaders::BatchModelLoader.new(User, object.author_id).find
|
||||
end
|
||||
|
|
|
@ -30,9 +30,7 @@ module Ci
|
|||
project.has_ci? && project.builds_enabled?
|
||||
end
|
||||
|
||||
# This list of templates is for the pipeline_empty_state_templates experiment
|
||||
# and will be cleaned up with https://gitlab.com/gitlab-org/gitlab/-/issues/326299
|
||||
def experiment_suggested_ci_templates
|
||||
def suggested_ci_templates
|
||||
[
|
||||
{ name: 'Android', logo: image_path('illustrations/third-party-logos/ci_cd-template-logos/android.svg') },
|
||||
{ name: 'Bash', logo: image_path('illustrations/third-party-logos/ci_cd-template-logos/bash.svg') },
|
||||
|
|
|
@ -181,7 +181,6 @@ module IssuesHelper
|
|||
|
||||
def issues_list_data(project, current_user, finder)
|
||||
{
|
||||
autocomplete_users_path: autocomplete_users_path(active: true, current_user: true, project_id: project.id, format: :json),
|
||||
autocomplete_award_emojis_path: autocomplete_award_emojis_path,
|
||||
calendar_path: url_for(safe_params.merge(calendar_url_options)),
|
||||
can_bulk_update: can?(current_user, :admin_issue, project).to_s,
|
||||
|
@ -201,8 +200,6 @@ module IssuesHelper
|
|||
max_attachment_size: number_to_human_size(Gitlab::CurrentSettings.max_attachment_size.megabytes),
|
||||
new_issue_path: new_project_issue_path(project, issue: { milestone_id: finder.milestones.first.try(:id) }),
|
||||
project_import_jira_path: project_import_jira_path(project),
|
||||
project_labels_path: project_labels_path(project, include_ancestor_groups: true, format: :json),
|
||||
project_milestones_path: project_milestones_path(project, format: :json),
|
||||
project_path: project.full_path,
|
||||
quick_actions_help_path: help_page_path('user/project/quick_actions'),
|
||||
reset_path: new_issuable_address_project_path(project, issuable_type: 'issue'),
|
||||
|
|
|
@ -43,7 +43,7 @@ module UserCalloutsHelper
|
|||
end
|
||||
|
||||
def show_customize_homepage_banner?
|
||||
!user_dismissed?(CUSTOMIZE_HOMEPAGE)
|
||||
current_user.default_dashboard? && !user_dismissed?(CUSTOMIZE_HOMEPAGE)
|
||||
end
|
||||
|
||||
def show_feature_flags_new_version?
|
||||
|
|
|
@ -808,6 +808,10 @@ class User < ApplicationRecord
|
|||
# Instance methods
|
||||
#
|
||||
|
||||
def default_dashboard?
|
||||
dashboard == self.class.column_defaults['dashboard']
|
||||
end
|
||||
|
||||
def full_path
|
||||
username
|
||||
end
|
||||
|
|
|
@ -135,10 +135,6 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
|
|||
ide_edit_path(project, default_branch_or_main, 'README.md')
|
||||
end
|
||||
|
||||
def add_ci_yml_path
|
||||
add_special_file_path(file_name: ci_config_path_or_default)
|
||||
end
|
||||
|
||||
def add_code_quality_ci_yml_path
|
||||
add_special_file_path(
|
||||
file_name: ci_config_path_or_default,
|
||||
|
|
|
@ -16,7 +16,7 @@ class AuditEventService
|
|||
@author = build_author(author)
|
||||
@entity = entity
|
||||
@details = details
|
||||
@ip_address = resolve_ip_address(@details, @author)
|
||||
@ip_address = resolve_ip_address(@author)
|
||||
end
|
||||
|
||||
# Builds the @details attribute for authentication
|
||||
|
@ -64,9 +64,8 @@ class AuditEventService
|
|||
end
|
||||
end
|
||||
|
||||
def resolve_ip_address(details, author)
|
||||
details[:ip_address].presence ||
|
||||
Gitlab::RequestContext.instance.client_ip ||
|
||||
def resolve_ip_address(author)
|
||||
Gitlab::RequestContext.instance.client_ip ||
|
||||
author.current_sign_in_ip
|
||||
end
|
||||
|
||||
|
|
|
@ -16,11 +16,12 @@ module ResourceAccessTokens
|
|||
|
||||
return error(user.errors.full_messages.to_sentence) unless user.persisted?
|
||||
|
||||
member = create_membership(resource, user)
|
||||
access_level = params[:access_level] || Gitlab::Access::MAINTAINER
|
||||
member = create_membership(resource, user, access_level)
|
||||
|
||||
unless member.persisted?
|
||||
delete_failed_user(user)
|
||||
return error("Could not provision maintainer access to project access token")
|
||||
return error("Could not provision #{Gitlab::Access.human_access(access_level).downcase} access to project access token")
|
||||
end
|
||||
|
||||
token_response = create_personal_access_token(user)
|
||||
|
@ -102,8 +103,8 @@ module ResourceAccessTokens
|
|||
Gitlab::Auth.resource_bot_scopes
|
||||
end
|
||||
|
||||
def create_membership(resource, user)
|
||||
resource.add_user(user, :maintainer, expires_at: params[:expires_at])
|
||||
def create_membership(resource, user, access_level)
|
||||
resource.add_user(user, access_level, expires_at: params[:expires_at])
|
||||
end
|
||||
|
||||
def log_event(token)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
- payload_class = 'js-usage-ping-payload'
|
||||
- payload_class = 'js-service-ping-payload'
|
||||
|
||||
= form_for @application_setting, url: metrics_and_profiling_admin_application_settings_path(anchor: 'js-usage-settings'), html: { class: 'fieldset-form' } do |f|
|
||||
= form_errors(@application_setting)
|
||||
|
@ -17,23 +17,23 @@
|
|||
.form-check
|
||||
= f.check_box :usage_ping_enabled, disabled: !can_be_configured, class: 'form-check-input'
|
||||
= f.label :usage_ping_enabled, class: 'form-check-label' do
|
||||
= _('Enable usage ping')
|
||||
= _('Enable service ping')
|
||||
.form-text.text-muted
|
||||
- if can_be_configured
|
||||
%p.mb-2= _('To help improve GitLab and its user experience, GitLab will periodically collect usage information.')
|
||||
|
||||
- usage_ping_path = help_page_path('user/admin_area/settings/usage_statistics', anchor: 'usage-ping')
|
||||
- usage_ping_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: usage_ping_path }
|
||||
%p.mb-2= s_('%{usage_ping_link_start}Learn more%{usage_ping_link_end} about what information is shared with GitLab Inc.').html_safe % { usage_ping_link_start: usage_ping_link_start, usage_ping_link_end: '</a>'.html_safe }
|
||||
- service_ping_path = help_page_path('user/admin_area/settings/usage_statistics', anchor: 'usage-ping')
|
||||
- service_ping_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: service_ping_path }
|
||||
%p.mb-2= s_('%{service_ping_link_start}Learn more%{service_ping_link_end} about what information is shared with GitLab Inc.').html_safe % { service_ping_link_start: service_ping_link_start, service_ping_link_end: '</a>'.html_safe }
|
||||
|
||||
%button.gl-button.btn.btn-default.js-payload-preview-trigger{ type: 'button', data: { payload_selector: ".#{payload_class}" } }
|
||||
.gl-spinner.js-spinner.gl-display-none.gl-mr-2
|
||||
.js-text.d-inline= _('Preview payload')
|
||||
%pre.usage-data.js-syntax-highlight.code.highlight.mt-2.d-none{ class: payload_class, data: { endpoint: usage_data_admin_application_settings_path(format: :html) } }
|
||||
%pre.service-data-payload-container.js-syntax-highlight.code.highlight.mt-2.d-none{ class: payload_class, data: { endpoint: usage_data_admin_application_settings_path(format: :html) } }
|
||||
- else
|
||||
= _('The usage ping is disabled, and cannot be configured through this form.')
|
||||
- deactivating_usage_ping_path = help_page_path('development/usage_ping/index.md', anchor: 'disable-usage-ping')
|
||||
- deactivating_usage_ping_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: deactivating_usage_ping_path }
|
||||
= s_('For more information, see the documentation on %{deactivating_usage_ping_link_start}deactivating the usage ping%{deactivating_usage_ping_link_end}.').html_safe % { deactivating_usage_ping_link_start: deactivating_usage_ping_link_start, deactivating_usage_ping_link_end: '</a>'.html_safe }
|
||||
= _('Service ping is disabled, and cannot be configured through this form.')
|
||||
- deactivating_service_ping_path = help_page_path('development/usage_ping/index.md', anchor: 'disable-usage-ping')
|
||||
- deactivating_service_ping_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: deactivating_service_ping_path }
|
||||
= s_('For more information, see the documentation on %{deactivating_service_ping_link_start}deactivating service ping%{deactivating_service_ping_link_end}.').html_safe % { deactivating_service_ping_link_start: deactivating_service_ping_link_start, deactivating_service_ping_link_end: '</a>'.html_safe }
|
||||
|
||||
= f.submit _('Save changes'), class: "gl-button btn btn-confirm"
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
%button.btn.gl-button.btn-default.js-settings-toggle{ type: 'button' }
|
||||
= expanded_by_default? ? _('Collapse') : _('Expand')
|
||||
%p
|
||||
= _('Enable or disable version check and usage ping.')
|
||||
= _('Enable or disable version check and service ping.')
|
||||
.settings-content
|
||||
= render 'usage'
|
||||
|
||||
|
|
|
@ -3,15 +3,6 @@
|
|||
= content_for :meta_tags do
|
||||
= auto_discovery_link_tag(:atom, dashboard_projects_url(rss_url_options), title: "All activity")
|
||||
|
||||
- if show_customize_homepage_banner?
|
||||
= content_for :customize_homepage_banner do
|
||||
.gl-display-none.gl-md-display-block{ class: "gl-pt-6! gl-pb-2! #{(container_class unless @no_container)} #{@content_class}" }
|
||||
.js-customize-homepage-banner{ data: { svg_path: image_path('illustrations/monitoring/getting_started.svg'),
|
||||
preferences_behavior_path: profile_preferences_path(anchor: 'behavior'),
|
||||
callouts_path: user_callouts_path,
|
||||
callouts_feature_id: UserCalloutsHelper::CUSTOMIZE_HOMEPAGE,
|
||||
track_label: 'home_page' } }
|
||||
|
||||
= render_dashboard_ultimate_trial(current_user)
|
||||
|
||||
- page_title _("Projects")
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
= render "layouts/header/service_templates_deprecation_callout"
|
||||
= render "layouts/nav/classification_level_banner"
|
||||
= yield :flash_message
|
||||
= render "shared/ping_consent"
|
||||
= render "shared/service_ping_consent"
|
||||
= render_account_recovery_regular_check
|
||||
= render_if_exists "layouts/header/ee_subscribable_banner"
|
||||
= render_if_exists "shared/namespace_storage_limit_alert"
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
- breadcrumb_title _('Two-Factor Authentication')
|
||||
- page_title _('Two-Factor Authentication'), _('Account')
|
||||
- add_to_breadcrumbs(_('Two-Factor Authentication'), profile_account_path)
|
||||
- add_to_breadcrumbs _('Account'), profile_account_path
|
||||
- @content_class = "limit-container-width" unless fluid_layout
|
||||
- webauthn_enabled = Feature.enabled?(:webauthn)
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
"ci-lint-path" => can?(current_user, :create_pipeline, @project) && project_ci_lint_path(@project),
|
||||
"reset-cache-path" => can?(current_user, :admin_pipeline, @project) && reset_cache_project_settings_ci_cd_path(@project),
|
||||
"has-gitlab-ci" => has_gitlab_ci?(@project).to_s,
|
||||
"add-ci-yml-path" => can?(current_user, :create_pipeline, @project) && @project.present(current_user: current_user).add_ci_yml_path,
|
||||
"suggested-ci-templates" => experiment_suggested_ci_templates.to_json,
|
||||
"pipeline-editor-path" => can?(current_user, :create_pipeline, @project) && project_ci_pipeline_editor_path(@project),
|
||||
"suggested-ci-templates" => suggested_ci_templates.to_json,
|
||||
"code-quality-page-path" => @project.present(current_user: current_user).add_code_quality_ci_yml_path,
|
||||
"ci-runner-settings-path" => project_settings_ci_cd_path(@project, ci_runner_templates: true, anchor: 'js-runners-settings') } }
|
||||
|
|
|
@ -33,8 +33,11 @@
|
|||
= render 'shared/access_tokens/form',
|
||||
type: type,
|
||||
path: project_settings_access_tokens_path(@project),
|
||||
project: @project,
|
||||
token: @project_access_token,
|
||||
scopes: @scopes,
|
||||
access_levels: ProjectMember.access_level_roles,
|
||||
default_access_level: Gitlab::Access::MAINTAINER,
|
||||
prefix: :project_access_token,
|
||||
help_path: help_page_path('user/project/settings/project_access_tokens', anchor: 'limiting-scopes-of-a-project-access-token')
|
||||
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
- if show_customize_homepage_banner?
|
||||
= content_for :customize_homepage_banner do
|
||||
.gl-display-none.gl-md-display-block{ class: "gl-pt-6! gl-pb-2! #{(container_class unless @no_container)} #{@content_class}" }
|
||||
.js-customize-homepage-banner{ data: { svg_path: image_path('illustrations/monitoring/getting_started.svg'),
|
||||
preferences_behavior_path: profile_preferences_path(anchor: 'behavior'),
|
||||
callouts_path: user_callouts_path,
|
||||
callouts_feature_id: UserCalloutsHelper::CUSTOMIZE_HOMEPAGE,
|
||||
track_label: 'home_page' } }
|
||||
|
||||
= render template: 'dashboard/projects/index'
|
|
@ -1,5 +1,5 @@
|
|||
- if session[:ask_for_usage_stats_consent]
|
||||
.ping-consent-message.gl-alert.gl-alert-info
|
||||
.service-ping-consent-message.gl-alert.gl-alert-info
|
||||
= sprite_icon('information-o', css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title')
|
||||
%button.js-close.gl-alert-dismiss{ type: 'button', 'aria-label' => _('Dismiss') }
|
||||
= sprite_icon('close', css_class: 'gl-icon')
|
||||
|
@ -8,7 +8,7 @@
|
|||
- settings_link = link_to _('your settings'), metrics_and_profiling_admin_application_settings_path(anchor: 'js-usage-settings'), class: 'gl-link'
|
||||
= s_('To help improve GitLab, we would like to periodically %{docs_link}. This can be changed at any time in %{settings_link}.').html_safe % { docs_link: docs_link, settings_link: settings_link }
|
||||
.gl-alert-actions.gl-mt-3
|
||||
- send_usage_data_path = admin_application_settings_path(application_setting: { version_check_enabled: 1, usage_ping_enabled: 1 })
|
||||
- send_service_data_path = admin_application_settings_path(application_setting: { version_check_enabled: 1, usage_ping_enabled: 1 })
|
||||
- not_now_path = admin_application_settings_path(application_setting: { version_check_enabled: 0, usage_ping_enabled: 0 })
|
||||
= link_to _("Send usage data"), send_usage_data_path, 'data-url' => admin_application_settings_path, method: :put, 'data-check-enabled': true, 'data-ping-enabled': true, class: 'js-usage-consent-action alert-link btn gl-button btn-info'
|
||||
= link_to _("Don't send usage data"), not_now_path, 'data-url' => admin_application_settings_path, method: :put, 'data-check-enabled': false, 'data-ping-enabled': false, class: 'js-usage-consent-action alert-link btn gl-button btn-default gl-ml-2'
|
||||
= link_to _("Send service data"), send_service_data_path, 'data-url' => admin_application_settings_path, method: :put, 'data-check-enabled': true, 'data-service-ping-enabled': true, class: 'js-service-ping-consent-action alert-link btn gl-button btn-info'
|
||||
= link_to _("Don't send service data"), not_now_path, 'data-url' => admin_application_settings_path, method: :put, 'data-check-enabled': false, 'data-service-ping-enabled': false, class: 'js-service-ping-consent-action alert-link btn gl-button btn-default gl-ml-2'
|
|
@ -1,6 +1,9 @@
|
|||
- title = local_assigns.fetch(:title, _('Add a %{type}') % { type: type })
|
||||
- prefix = local_assigns.fetch(:prefix, :personal_access_token)
|
||||
- help_path = local_assigns.fetch(:help_path)
|
||||
- project = local_assigns.fetch(:project, false)
|
||||
- access_levels = local_assigns.fetch(:access_levels, false)
|
||||
- default_access_level = local_assigns.fetch(:default_access_level, false)
|
||||
|
||||
%h5.gl-mt-0
|
||||
= title
|
||||
|
@ -29,6 +32,14 @@
|
|||
.js-access-tokens-expires-at
|
||||
= f.text_field :expires_at, class: 'datepicker gl-datepicker-input form-control gl-form-input', placeholder: 'YYYY-MM-DD', autocomplete: 'off', data: { js_name: 'expiresAt' }
|
||||
|
||||
- if project
|
||||
.row
|
||||
.form-group.col-md-6
|
||||
= label_tag :access_level, _("Select a role"), class: "label-bold"
|
||||
.select-wrapper
|
||||
= select_tag :"#{prefix}[access_level]", options_for_select(access_levels, default_access_level), class: "form-control project-access-select select-control", data: { qa_selector: 'access_token_access_level' }
|
||||
= sprite_icon('chevron-down', css_class: "gl-icon gl-absolute gl-top-3 gl-right-3 gl-text-gray-200")
|
||||
|
||||
.form-group
|
||||
%b{ :'aria-describedby' => 'select_scope_help_text' }
|
||||
= s_('Tokens|Select scopes')
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
= link_to _('New issue'), button_path, class: 'gl-button btn btn-confirm', id: 'new_issue_link'
|
||||
|
||||
- if show_import_button
|
||||
.js-csv-import-export-buttons{ data: { show_import_button: show_import_button.to_s, issuable_type: issuable_type, import_csv_issues_path: import_csv_namespace_project_issues_path, can_edit: can_edit.to_s, project_import_jira_path: project_import_jira_path(@project), max_attachment_size: number_to_human_size(Gitlab::CurrentSettings.max_attachment_size.megabytes), container_class: 'gl-display-inline-flex gl-vertical-align-middle', show_label: 'true' } }
|
||||
.js-csv-import-export-buttons{ data: { show_import_button: 'true', issuable_type: issuable_type, import_csv_issues_path: import_csv_namespace_project_issues_path, can_edit: can_edit.to_s, project_import_jira_path: project_import_jira_path(@project), max_attachment_size: number_to_human_size(Gitlab::CurrentSettings.max_attachment_size.megabytes), container_class: 'gl-display-inline-flex gl-vertical-align-middle', show_label: 'true' } }
|
||||
%hr
|
||||
%p.gl-text-center.gl-mb-0
|
||||
%strong
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
- auto_devops
|
||||
- backup_restore
|
||||
- boards
|
||||
- build_artifacts
|
||||
- chatops
|
||||
- cloud_native_installation
|
||||
- cluster_cost_management
|
||||
|
@ -83,12 +84,14 @@
|
|||
- mlops
|
||||
- mobile_signing_deployment
|
||||
- navigation
|
||||
- not_owned
|
||||
- omnibus_package
|
||||
- on_call_schedule_management
|
||||
- onboarding
|
||||
- package_registry
|
||||
- pages
|
||||
- performance_testing
|
||||
- pipeline_abuse_prevention
|
||||
- pipeline_authoring
|
||||
- planning_analytics
|
||||
- privacy_control_center
|
||||
|
@ -110,6 +113,7 @@
|
|||
- self_monitoring
|
||||
- serverless
|
||||
- service_desk
|
||||
- service_ping
|
||||
- sharding
|
||||
- snippets
|
||||
- source_code_management
|
||||
|
@ -127,7 +131,6 @@
|
|||
- value_stream_management
|
||||
- vulnerability_database
|
||||
- vulnerability_management
|
||||
- web_firewall
|
||||
- web_ide
|
||||
- wiki
|
||||
- workflow_automation
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: pipeline_empty_state_templates
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/57286
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/326299
|
||||
milestone: '13.11'
|
||||
type: experiment
|
||||
group: group::activation
|
||||
default_enabled: false
|
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddLastSyncedAtToLicenses < ActiveRecord::Migration[6.1]
|
||||
def change
|
||||
add_column :licenses, :last_synced_at, :datetime_with_timezone
|
||||
end
|
||||
end
|
|
@ -38,6 +38,12 @@ class FinalizePushEventPayloadsBigintConversion < ActiveRecord::Migration[6.1]
|
|||
execute "ALTER TABLE #{quote_table_name(TABLE_NAME)} RENAME COLUMN #{quote_column_name(:event_id_convert_to_bigint)} TO #{quote_column_name(:event_id)}"
|
||||
execute "ALTER TABLE #{quote_table_name(TABLE_NAME)} RENAME COLUMN #{quote_column_name(temp_name)} TO #{quote_column_name(:event_id_convert_to_bigint)}"
|
||||
|
||||
# We need to update the trigger function in order to make PostgreSQL to
|
||||
# regenerate the execution plan for it. This is to avoid type mismatch errors like
|
||||
# "type of parameter 15 (bigint) does not match that when preparing the plan (integer)"
|
||||
function_name = Gitlab::Database::UnidirectionalCopyTrigger.on_table(TABLE_NAME).name(:event_id, :event_id_convert_to_bigint)
|
||||
execute "ALTER FUNCTION #{quote_table_name(function_name)} RESET ALL"
|
||||
|
||||
# Swap defaults
|
||||
change_column_default TABLE_NAME, :event_id, nil
|
||||
change_column_default TABLE_NAME, :event_id_convert_to_bigint, 0
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
705c4cf981f1929f8e8e4d8a8a3c12613516d65e59c71ac79048224cd97c47cc
|
|
@ -14461,7 +14461,8 @@ CREATE TABLE licenses (
|
|||
data text NOT NULL,
|
||||
created_at timestamp without time zone,
|
||||
updated_at timestamp without time zone,
|
||||
cloud boolean DEFAULT false
|
||||
cloud boolean DEFAULT false,
|
||||
last_synced_at timestamp with time zone
|
||||
);
|
||||
|
||||
CREATE SEQUENCE licenses_id_seq
|
||||
|
|
|
@ -279,6 +279,23 @@ p.each do |project|
|
|||
end
|
||||
```
|
||||
|
||||
### Bulk update push rules for _all_ projects
|
||||
|
||||
For example, enable **Check whether the commit author is a GitLab user** and **Do not allow users to remove Git tags with `git push`** checkboxes, and create a filter for allowing commits from a specific e-mail domain only:
|
||||
|
||||
``` ruby
|
||||
Project.find_each do |p|
|
||||
pr = p.push_rule || PushRule.new(project: p)
|
||||
# Check whether the commit author is a GitLab user
|
||||
pr.member_check = true
|
||||
# Do not allow users to remove Git tags with `git push`
|
||||
pr.deny_delete_tag = true
|
||||
# Commit author's email
|
||||
pr.author_email_regex = '@domain\.com$'
|
||||
pr.save!
|
||||
end
|
||||
```
|
||||
|
||||
## Bulk update to change all the Jira integrations to Jira instance-level values
|
||||
|
||||
To change all Jira project to use the instance-level integration settings:
|
||||
|
|
|
@ -164,7 +164,7 @@ curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitla
|
|||
|
||||
### Add note to existing issue thread
|
||||
|
||||
Adds a new note to the thread. This can also [create a thread from a single comment](../user/discussions/#start-a-thread-by-replying-to-a-standard-comment).
|
||||
Adds a new note to the thread. This can also [create a thread from a single comment](../user/discussions/#create-a-thread-by-replying-to-a-standard-comment).
|
||||
|
||||
**WARNING**
|
||||
Notes can be added to other items than comments, such as system notes, making them threads.
|
||||
|
@ -581,7 +581,7 @@ curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitla
|
|||
### Add note to existing epic thread
|
||||
|
||||
Adds a new note to the thread. This can also
|
||||
[create a thread from a single comment](../user/discussions/#start-a-thread-by-replying-to-a-standard-comment).
|
||||
[create a thread from a single comment](../user/discussions/#create-a-thread-by-replying-to-a-standard-comment).
|
||||
|
||||
```plaintext
|
||||
POST /groups/:id/epics/:epic_id/discussions/:discussion_id/notes
|
||||
|
@ -966,7 +966,7 @@ curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab
|
|||
### Add note to existing merge request thread
|
||||
|
||||
Adds a new note to the thread. This can also
|
||||
[create a thread from a single comment](../user/discussions/#start-a-thread-by-replying-to-a-standard-comment).
|
||||
[create a thread from a single comment](../user/discussions/#create-a-thread-by-replying-to-a-standard-comment).
|
||||
|
||||
```plaintext
|
||||
POST /projects/:id/merge_requests/:merge_request_iid/discussions/:discussion_id/notes
|
||||
|
|
|
@ -8805,6 +8805,7 @@ Relationship between an epic and an issue.
|
|||
| <a id="epicissuemovedto"></a>`movedTo` | [`Issue`](#issue) | Updated Issue after it got moved to another project. |
|
||||
| <a id="epicissuenotes"></a>`notes` | [`NoteConnection!`](#noteconnection) | All notes on this noteable. (see [Connections](#connections)) |
|
||||
| <a id="epicissueparticipants"></a>`participants` | [`UserCoreConnection`](#usercoreconnection) | List of participants in the issue. (see [Connections](#connections)) |
|
||||
| <a id="epicissueprojectid"></a>`projectId` | [`Int!`](#int) | ID of the issue project. |
|
||||
| <a id="epicissuerelationpath"></a>`relationPath` | [`String`](#string) | URI path of the epic-issue relation. |
|
||||
| <a id="epicissuerelativeposition"></a>`relativePosition` | [`Int`](#int) | Relative position of the issue (used for positioning in epic tree and issue boards). |
|
||||
| <a id="epicissueseverity"></a>`severity` | [`IssuableSeverity`](#issuableseverity) | Severity level of the incident. |
|
||||
|
@ -9853,6 +9854,7 @@ Returns [`VulnerabilitySeveritiesCount`](#vulnerabilityseveritiescount).
|
|||
| <a id="issuemovedto"></a>`movedTo` | [`Issue`](#issue) | Updated Issue after it got moved to another project. |
|
||||
| <a id="issuenotes"></a>`notes` | [`NoteConnection!`](#noteconnection) | All notes on this noteable. (see [Connections](#connections)) |
|
||||
| <a id="issueparticipants"></a>`participants` | [`UserCoreConnection`](#usercoreconnection) | List of participants in the issue. (see [Connections](#connections)) |
|
||||
| <a id="issueprojectid"></a>`projectId` | [`Int!`](#int) | ID of the issue project. |
|
||||
| <a id="issuerelativeposition"></a>`relativePosition` | [`Int`](#int) | Relative position of the issue (used for positioning in epic tree and issue boards). |
|
||||
| <a id="issueseverity"></a>`severity` | [`IssuableSeverity`](#issuableseverity) | Severity level of the incident. |
|
||||
| <a id="issuesladueat"></a>`slaDueAt` | [`Time`](#time) | Timestamp of when the issue SLA expires. |
|
||||
|
|
|
@ -59,12 +59,13 @@ POST projects/:id/access_tokens
|
|||
| `id` | integer or string | yes | The ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) |
|
||||
| `name` | String | yes | The name of the project access token |
|
||||
| `scopes` | `Array[String]` | yes | [List of scopes](../user/project/settings/project_access_tokens.md#limiting-scopes-of-a-project-access-token) |
|
||||
| `access_level` | Integer | no | A valid access level. Default value is 40 (Maintainer). Other allowed values are 10 (Guest), 20 (Reporter), and 30 (Developer). |
|
||||
| `expires_at` | Date | no | The token expires at midnight UTC on that date |
|
||||
|
||||
```shell
|
||||
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" \
|
||||
--header "Content-Type:application/json" \
|
||||
--data '{ "name":"test_token", "scopes":["api", "read_repository"], "expires_at":"2021-01-31" }' \
|
||||
--data '{ "name":"test_token", "scopes":["api", "read_repository"], "expires_at":"2021-01-31", "access_level": 30 }' \
|
||||
"https://gitlab.example.com/api/v4/projects/<project_id>/access_tokens"
|
||||
```
|
||||
|
||||
|
@ -82,7 +83,7 @@ curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" \
|
|||
"id" : 58,
|
||||
"expires_at" : "2021-01-31",
|
||||
"token" : "D4y...Wzr",
|
||||
"access_level": 40
|
||||
"access_level": 30
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -115,7 +115,7 @@ pre-push:
|
|||
skip: false
|
||||
```
|
||||
|
||||
For more information, check out [this Lefthook documentation section](https://github.com/evilmartians/lefthook/blob/master/docs/full_guide.md#skipping-commands).
|
||||
For more information, check out [Lefthook documentation Skipping commands section](https://github.com/evilmartians/lefthook/blob/master/docs/full_guide.md#skipping-commands).
|
||||
|
||||
## Ruby, Rails, RSpec
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ feature is still under development, and is not ready for production use.
|
|||
|
||||
By default, GitLab is configured to use only one main database. To
|
||||
opt-in to use a main database, and CI database, modify the
|
||||
`config/database.yml` file to have a `primary` and a `ci` database
|
||||
`config/database.yml` file to have a `main` and a `ci` database
|
||||
configurations. For example, given a `config/database.yml` like below:
|
||||
|
||||
```yaml
|
||||
|
@ -48,7 +48,7 @@ Edit the `config/database.yml` to look like this:
|
|||
|
||||
```yaml
|
||||
development:
|
||||
primary:
|
||||
main:
|
||||
adapter: postgresql
|
||||
encoding: unicode
|
||||
database: gitlabhq_development
|
||||
|
@ -69,7 +69,7 @@ development:
|
|||
statement_timeout: 120s
|
||||
|
||||
test: &test
|
||||
primary:
|
||||
main:
|
||||
adapter: postgresql
|
||||
encoding: unicode
|
||||
database: gitlabhq_test
|
||||
|
@ -90,9 +90,6 @@ test: &test
|
|||
statement_timeout: 120s
|
||||
```
|
||||
|
||||
Note that we use `primary` in the `config/database.yml` to refer to the main
|
||||
database. This is to match the default name Rails has.
|
||||
|
||||
### Migrations
|
||||
|
||||
Any migrations that affect `Ci::BaseModel` models
|
||||
|
|
|
@ -392,8 +392,12 @@ end
|
|||
If a large number of background jobs get scheduled at once, queueing of jobs may
|
||||
occur while jobs wait for a worker node to be become available. This is normal
|
||||
and gives the system resilience by allowing it to gracefully handle spikes in
|
||||
traffic. Some jobs, however, are more sensitive to latency than others. Examples
|
||||
of these jobs include:
|
||||
traffic. Some jobs, however, are more sensitive to latency than others.
|
||||
|
||||
In general, latency-sensitive jobs perform operations that a user could
|
||||
reasonably expect to happen synchronously, rather than asynchronously in a
|
||||
background worker. A common example is a write following an action. Examples of
|
||||
these jobs include:
|
||||
|
||||
1. A job which updates a merge request following a push to a branch.
|
||||
1. A job which invalidates a cache of known branches for a project after a push
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 69 KiB |
Binary file not shown.
Before Width: | Height: | Size: 496 KiB |
|
@ -34,6 +34,50 @@ You can create comments in places like:
|
|||
|
||||
Each object can have as many as 5,000 comments.
|
||||
|
||||
## Create a thread by replying to a standard comment
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/30299) in GitLab 11.9.
|
||||
|
||||
When you reply to a standard comment, you create a thread.
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- You must have at least the [Guest role](../permissions.md#project-members-permissions).
|
||||
- You must be in an issue, merge request, or epic. Commits and snippets threads are not supported.
|
||||
|
||||
To create a thread by replying to a comment:
|
||||
|
||||
1. On the top right of the comment, select **{comment}** (**Reply to comment**).
|
||||
|
||||
![Reply to comment button](img/reply_to_comment_button.png)
|
||||
|
||||
The reply area is displayed.
|
||||
|
||||
1. Type your reply.
|
||||
1. Select **Comment** or **Add comment now** (depending on where in the UI you are replying).
|
||||
|
||||
The top comment is converted to a thread.
|
||||
|
||||
## Create a thread without replying to a comment
|
||||
|
||||
You can create a thread without replying to a standard comment.
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- You must have at least the [Guest role](../permissions.md#project-members-permissions).
|
||||
- You must be in an issue, commit, snippet, or merge request.
|
||||
|
||||
To create a thread:
|
||||
|
||||
1. Type a comment.
|
||||
1. Below the comment, to the right of the **Comment** button, select the down arrow (**{chevron-down}**).
|
||||
1. From the list, select **Start thread**.
|
||||
1. Select **Start thread** again.
|
||||
|
||||
A threaded comment is created.
|
||||
|
||||
![Thread comment](img/discussion_comment.png)
|
||||
|
||||
## Reply to a comment by sending email
|
||||
|
||||
If you have ["reply by email"](../../administration/reply_by_email.md) configured,
|
||||
|
@ -61,7 +105,7 @@ Thread resolution helps keep track of progress during planning or code review.
|
|||
|
||||
Every thread in merge requests, commits, commit diffs, and
|
||||
snippets is initially displayed as unresolved. They can then be individually resolved by anyone
|
||||
with at least Developer access to the project or by the author of the change being reviewed.
|
||||
with at least the Developer role to the project or by the author of the change being reviewed.
|
||||
If the thread has been resolved and a non-member un-resolves their own response,
|
||||
this also unresolves the discussion thread.
|
||||
If the non-member then resolves this same response, this resolves the discussion thread.
|
||||
|
@ -188,33 +232,19 @@ From now on, any threads on a diff are resolved by default if a push
|
|||
makes that diff section outdated. Threads on lines that don't change and
|
||||
top-level resolvable threads are not automatically resolved.
|
||||
|
||||
## Commit threads
|
||||
## Add a comment to a commit
|
||||
|
||||
You can add comments and threads to a particular commit under your
|
||||
project's **Repository > Commits**.
|
||||
You can add comments and threads to a particular commit.
|
||||
|
||||
1. On the top bar, select **Menu > Projects** and find your project.
|
||||
1. On the left sidebar, select **Repository > Commits**.
|
||||
1. Below the commits, in the **Comment** field, enter a comment.
|
||||
1. Select **Comment** or select the down arrow (**{chevron-down}**) to select **Start thread**.
|
||||
|
||||
WARNING:
|
||||
Threads created this way are lost if the commit ID changes after a
|
||||
force push.
|
||||
|
||||
## Threaded discussions
|
||||
|
||||
While resolvable threads are only available to merge request diffs,
|
||||
threads can also be added without a diff. You can start a specific
|
||||
thread which looks like a thread, on issues, commits, snippets, and
|
||||
merge requests.
|
||||
|
||||
To start a threaded discussion, select the **Comment** button toggle dropdown,
|
||||
select **Start thread**, and then select **Start thread** when you're ready to
|
||||
post the comment.
|
||||
|
||||
![Comment type toggle](img/comment_type_toggle.gif)
|
||||
|
||||
This posts a comment with a single thread to allow you to discuss specific
|
||||
comments in greater detail.
|
||||
|
||||
![Thread comment](img/discussion_comment.png)
|
||||
|
||||
## Image threads
|
||||
|
||||
Sometimes a thread is revolved around an image. With image threads,
|
||||
|
@ -320,27 +350,6 @@ After you select one of the filters in a given issue or merge request, GitLab sa
|
|||
your preference, so that it persists when you visit the same page again
|
||||
from any device you're logged into.
|
||||
|
||||
## Start a thread by replying to a standard comment
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/30299) in GitLab 11.9
|
||||
|
||||
To reply to a standard (non-thread) comment, you can use the **Reply to comment** button.
|
||||
|
||||
![Reply to comment button](img/reply_to_comment_button.png)
|
||||
|
||||
The **Reply to comment** button is only displayed if you have permissions to reply to an existing thread, or start a thread from a standard comment.
|
||||
|
||||
Selecting the **Reply to comment** button brings the reply area into focus and you can type your reply.
|
||||
|
||||
![Reply to comment feature](img/reply_to_comment.gif)
|
||||
|
||||
Replying to a non-thread comment converts the non-thread comment to a
|
||||
thread after the reply is submitted. This conversion is considered an edit
|
||||
to the original comment, so a note about when it was last edited appears underneath it.
|
||||
|
||||
This feature exists only for issues, merge requests, and epics. Commits, snippets, and merge request diff threads are
|
||||
not supported yet.
|
||||
|
||||
## Assign an issue to the commenting user
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/191455) in GitLab 13.1.
|
||||
|
|
|
@ -288,7 +288,7 @@ supports [GitLab Flavored Markdown](../../markdown.md#gitlab-flavored-markdown).
|
|||
After you write a comment, you can:
|
||||
|
||||
- Click **Comment** to publish your comment.
|
||||
- Choose **Start thread** from the dropdown list and start a new [thread](../../discussions/index.md#threaded-discussions)
|
||||
- Choose **Start thread** from the dropdown list and start a new [thread](../../discussions/index.md#create-a-thread-without-replying-to-a-comment)
|
||||
in that issue's main thread to discuss specific points. This invites other participants
|
||||
to reply directly to your thread, keeping related comments grouped together.
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ For examples of how you can use a project access token to authenticate with the
|
|||
1. Navigate to the project you would like to create an access token for.
|
||||
1. In the **Settings** menu choose **Access Tokens**.
|
||||
1. Choose a name and optional expiry date for the token.
|
||||
1. Choose the access level the token should have in the project.
|
||||
1. Choose the [desired scopes](#limiting-scopes-of-a-project-access-token).
|
||||
1. Click the **Create project access token** button.
|
||||
1. Save the project access token somewhere safe. Once you leave or refresh
|
||||
|
@ -42,7 +43,7 @@ For examples of how you can use a project access token to authenticate with the
|
|||
Project bot users are [GitLab-created service accounts](../../../subscriptions/self_managed/index.md#billable-users) and do not count as licensed seats.
|
||||
|
||||
For each project access token created, a bot user is created and added to the project with
|
||||
[Maintainer level permissions](../../permissions.md#project-members-permissions).
|
||||
the [specified level permissions](../../permissions.md#project-members-permissions).
|
||||
|
||||
For the bot:
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ module API
|
|||
class JobArtifacts < ::API::Base
|
||||
before { authenticate_non_get! }
|
||||
|
||||
feature_category :continuous_integration
|
||||
feature_category :build_artifacts
|
||||
|
||||
# EE::API::JobArtifacts would override the following helpers
|
||||
helpers do
|
||||
|
|
|
@ -58,6 +58,7 @@ module API
|
|||
requires :id, type: String, desc: "The #{source_type} ID"
|
||||
requires :name, type: String, desc: "Resource access token name"
|
||||
requires :scopes, type: Array[String], desc: "The permissions of the token"
|
||||
optional :access_level, type: Integer, desc: "The access level of the token in the project"
|
||||
optional :expires_at, type: Date, desc: "The expiration date of the token"
|
||||
end
|
||||
post ':id/access_tokens' do
|
||||
|
|
|
@ -3,8 +3,9 @@
|
|||
module Backup
|
||||
# Backup and restores repositories using gitaly-backup
|
||||
class GitalyBackup
|
||||
def initialize(progress)
|
||||
def initialize(progress, parallel: nil)
|
||||
@progress = progress
|
||||
@parallel = parallel
|
||||
end
|
||||
|
||||
def start(type)
|
||||
|
@ -19,8 +20,11 @@ module Backup
|
|||
raise Error, "unknown backup type: #{type}"
|
||||
end
|
||||
|
||||
args = []
|
||||
args += ['-parallel', @parallel.to_s] if type == :create && @parallel
|
||||
|
||||
@read_io, @write_io = IO.pipe
|
||||
@pid = Process.spawn(bin_path, command, '-path', backup_repos_path, in: @read_io, out: progress)
|
||||
@pid = Process.spawn(bin_path, command, '-path', backup_repos_path, *args, in: @read_io, out: @progress)
|
||||
end
|
||||
|
||||
def wait
|
||||
|
@ -48,9 +52,11 @@ module Backup
|
|||
}.merge(Gitlab::GitalyClient.connection_data(repository.storage)).to_json)
|
||||
end
|
||||
|
||||
private
|
||||
def parallel_enqueue?
|
||||
false
|
||||
end
|
||||
|
||||
attr_reader :progress
|
||||
private
|
||||
|
||||
def started?
|
||||
@pid.present?
|
||||
|
|
|
@ -44,6 +44,10 @@ module Backup
|
|||
end
|
||||
end
|
||||
|
||||
def parallel_enqueue?
|
||||
true
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :progress
|
||||
|
|
|
@ -12,7 +12,10 @@ module Backup
|
|||
def dump(max_concurrency:, max_storage_concurrency:)
|
||||
strategy.start(:create)
|
||||
|
||||
if max_concurrency <= 1 && max_storage_concurrency <= 1
|
||||
# gitaly-backup is designed to handle concurrency on its own. So we want
|
||||
# to avoid entering the buggy concurrency code here when gitaly-backup
|
||||
# is enabled.
|
||||
if (max_concurrency <= 1 && max_storage_concurrency <= 1) || !strategy.parallel_enqueue?
|
||||
return enqueue_consecutive
|
||||
end
|
||||
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
# To contribute improvements to CI/CD templates, please follow the Development guide at:
|
||||
# https://docs.gitlab.com/ee/development/cicd/templates.html
|
||||
# This specific template is located at:
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Grails.gitlab-ci.yml
|
||||
#
|
||||
# This file is a template demonstrating the `script` keyword.
|
||||
# Learn more about this keyword here: https://docs.gitlab.com/ee/ci/yaml/README.html#script
|
||||
|
||||
# After committing this template, visit CI/CD > Jobs to see the script output.
|
||||
|
||||
job:
|
||||
script:
|
||||
# provide a shell script as argument for this keyword.
|
||||
- echo "Hello World"
|
|
@ -298,7 +298,8 @@ namespace :gitlab do
|
|||
|
||||
def repository_backup_strategy
|
||||
if Feature.enabled?(:gitaly_backup)
|
||||
Backup::GitalyBackup.new(progress)
|
||||
max_concurrency = ENV['GITLAB_BACKUP_MAX_CONCURRENCY'].presence
|
||||
Backup::GitalyBackup.new(progress, parallel: max_concurrency)
|
||||
else
|
||||
Backup::GitalyRpcBackup.new(progress)
|
||||
end
|
||||
|
|
|
@ -839,6 +839,9 @@ msgid_plural "%{securityScanner} results are not available because a pipeline ha
|
|||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "%{service_ping_link_start}Learn more%{service_ping_link_end} about what information is shared with GitLab Inc."
|
||||
msgstr ""
|
||||
|
||||
msgid "%{size} %{unit}"
|
||||
msgstr ""
|
||||
|
||||
|
@ -972,9 +975,6 @@ msgstr ""
|
|||
msgid "%{total} warnings found: showing first %{warningsDisplayed}"
|
||||
msgstr ""
|
||||
|
||||
msgid "%{usage_ping_link_start}Learn more%{usage_ping_link_end} about what information is shared with GitLab Inc."
|
||||
msgstr ""
|
||||
|
||||
msgid "%{userName} (cannot merge)"
|
||||
msgstr ""
|
||||
|
||||
|
@ -11482,7 +11482,7 @@ msgstr ""
|
|||
msgid "Don't paste the private part of the GPG key. Paste the public part which begins with '-----BEGIN PGP PUBLIC KEY BLOCK-----'."
|
||||
msgstr ""
|
||||
|
||||
msgid "Don't send usage data"
|
||||
msgid "Don't send service data"
|
||||
msgstr ""
|
||||
|
||||
msgid "Don't show again"
|
||||
|
@ -12034,7 +12034,7 @@ msgstr ""
|
|||
msgid "Enable or disable the Pseudonymizer data collection."
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable or disable version check and usage ping."
|
||||
msgid "Enable or disable version check and service ping."
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable protected paths rate limit"
|
||||
|
@ -12052,6 +12052,9 @@ msgstr ""
|
|||
msgid "Enable reCAPTCHA, Invisible Captcha, Akismet and set IP limits. For reCAPTCHA, we currently only support %{recaptcha_v2_link_start}v2%{recaptcha_v2_link_end}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable service ping"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable shared runners for all projects and subgroups in this group."
|
||||
msgstr ""
|
||||
|
||||
|
@ -12076,9 +12079,6 @@ msgstr ""
|
|||
msgid "Enable unauthenticated request rate limit"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable usage ping"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable version check"
|
||||
msgstr ""
|
||||
|
||||
|
@ -14065,7 +14065,7 @@ msgstr ""
|
|||
msgid "For more information, see the File Hooks documentation."
|
||||
msgstr ""
|
||||
|
||||
msgid "For more information, see the documentation on %{deactivating_usage_ping_link_start}deactivating the usage ping%{deactivating_usage_ping_link_end}."
|
||||
msgid "For more information, see the documentation on %{deactivating_service_ping_link_start}deactivating service ping%{deactivating_service_ping_link_end}."
|
||||
msgstr ""
|
||||
|
||||
msgid "Forgot your password?"
|
||||
|
@ -23924,10 +23924,10 @@ msgstr ""
|
|||
msgid "Pipelines|Editor"
|
||||
msgstr ""
|
||||
|
||||
msgid "Pipelines|Get familiar with GitLab CI/CD syntax by starting with a simple pipeline that runs a “Hello world” script."
|
||||
msgid "Pipelines|Get familiar with GitLab CI/CD syntax by starting with a basic 3 stage CI/CD pipeline."
|
||||
msgstr ""
|
||||
|
||||
msgid "Pipelines|Get started with CI/CD"
|
||||
msgid "Pipelines|Get started with GitLab CI/CD"
|
||||
msgstr ""
|
||||
|
||||
msgid "Pipelines|GitLab CI/CD can automatically build, test, and deploy your code. Let GitLab take care of time consuming tasks, so you can spend more time creating."
|
||||
|
@ -24068,9 +24068,6 @@ msgstr ""
|
|||
msgid "Pipelines|parent"
|
||||
msgstr ""
|
||||
|
||||
msgid "Pipelines|“Hello world” with GitLab CI/CD"
|
||||
msgstr ""
|
||||
|
||||
msgid "Pipeline|Actions"
|
||||
msgstr ""
|
||||
|
||||
|
@ -29336,7 +29333,7 @@ msgstr ""
|
|||
msgid "Send report"
|
||||
msgstr ""
|
||||
|
||||
msgid "Send usage data"
|
||||
msgid "Send service data"
|
||||
msgstr ""
|
||||
|
||||
msgid "Sentry API URL"
|
||||
|
@ -29462,6 +29459,9 @@ msgstr ""
|
|||
msgid "Service URL"
|
||||
msgstr ""
|
||||
|
||||
msgid "Service ping is disabled, and cannot be configured through this form."
|
||||
msgstr ""
|
||||
|
||||
msgid "ServiceDesk|Enable Service Desk"
|
||||
msgstr ""
|
||||
|
||||
|
@ -32113,7 +32113,7 @@ msgstr ""
|
|||
msgid "The Advanced Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project."
|
||||
msgstr ""
|
||||
|
||||
msgid "The Compliance Dashboard gives you the ability to see a group's merge request activity by providing a high-level view for all projects in the group."
|
||||
msgid "The Compliance Report captures merged changes that violate compliance best practices."
|
||||
msgstr ""
|
||||
|
||||
msgid "The GitLab subscription service (customers.gitlab.com) is currently experiencing an outage. You can monitor the status and get updates at %{linkStart}status.gitlab.com%{linkEnd}."
|
||||
|
@ -32541,9 +32541,6 @@ msgstr ""
|
|||
msgid "The uploaded file was invalid. Supported file extensions are %{extensions}."
|
||||
msgstr ""
|
||||
|
||||
msgid "The usage ping is disabled, and cannot be configured through this form."
|
||||
msgstr ""
|
||||
|
||||
msgid "The user is being deleted."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -59,7 +59,7 @@
|
|||
"@gitlab/favicon-overlay": "2.0.0",
|
||||
"@gitlab/svgs": "1.201.0",
|
||||
"@gitlab/tributejs": "1.0.0",
|
||||
"@gitlab/ui": "30.0.0",
|
||||
"@gitlab/ui": "30.0.1",
|
||||
"@gitlab/visual-review-tools": "1.6.1",
|
||||
"@rails/actioncable": "6.1.3-2",
|
||||
"@rails/ujs": "6.1.3-2",
|
||||
|
|
|
@ -284,10 +284,6 @@ RSpec.describe Projects::PipelinesController do
|
|||
|
||||
subject { project.namespace }
|
||||
|
||||
context 'pipeline_empty_state_templates experiment' do
|
||||
it_behaves_like 'tracks assignment and records the subject', :pipeline_empty_state_templates, :namespace
|
||||
end
|
||||
|
||||
context 'code_quality_walkthrough experiment' do
|
||||
it_behaves_like 'tracks assignment and records the subject', :code_quality_walkthrough, :namespace
|
||||
end
|
||||
|
|
|
@ -61,6 +61,14 @@ RSpec.describe Projects::Settings::AccessTokensController do
|
|||
expect { subject }.not_to change { User.count }
|
||||
end
|
||||
end
|
||||
|
||||
context 'with custom access level' do
|
||||
let(:access_token_params) { { name: 'Nerd bot', scopes: ["api"], expires_at: Date.today + 1.month, access_level: 20 } }
|
||||
|
||||
subject { post :create, params: { namespace_id: project.namespace, project_id: project }.merge(project_access_token: access_token_params) }
|
||||
|
||||
it_behaves_like 'project access tokens available #create'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#revoke', :sidekiq_inline do
|
||||
|
|
|
@ -128,11 +128,31 @@ RSpec.describe RootController do
|
|||
end
|
||||
end
|
||||
|
||||
context 'who uses the default dashboard setting' do
|
||||
it 'renders the default dashboard' do
|
||||
get :index
|
||||
context 'who uses the default dashboard setting', :aggregate_failures do
|
||||
render_views
|
||||
|
||||
expect(response).to render_template 'dashboard/projects/index'
|
||||
context 'with customize homepage banner' do
|
||||
it 'renders the default dashboard' do
|
||||
get :index
|
||||
|
||||
expect(response).to render_template 'root/index'
|
||||
expect(response.body).to have_css('.js-customize-homepage-banner')
|
||||
end
|
||||
end
|
||||
|
||||
context 'without customize homepage banner' do
|
||||
before do
|
||||
Users::DismissUserCalloutService.new(
|
||||
container: nil, current_user: user, params: { feature_name: UserCalloutsHelper::CUSTOMIZE_HOMEPAGE }
|
||||
).execute
|
||||
end
|
||||
|
||||
it 'renders the default dashboard' do
|
||||
get :index
|
||||
|
||||
expect(response).to render_template 'root/index'
|
||||
expect(response.body).not_to have_css('.js-customize-homepage-banner')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -567,7 +567,7 @@ RSpec.describe 'Admin updates settings' do
|
|||
|
||||
wait_for_requests
|
||||
|
||||
expect(page).to have_selector '.js-usage-ping-payload'
|
||||
expect(page).to have_selector '.js-service-ping-payload'
|
||||
expect(page).to have_button 'Hide payload'
|
||||
expect(page).to have_content expected_payload_content
|
||||
end
|
||||
|
|
|
@ -18,12 +18,6 @@ RSpec.describe 'Dashboard Projects' do
|
|||
end
|
||||
end
|
||||
|
||||
it 'shows the customize banner', :js do
|
||||
visit dashboard_projects_path
|
||||
|
||||
expect(page).to have_content('Do you want to customize this page?')
|
||||
end
|
||||
|
||||
context 'when user has access to the project' do
|
||||
it 'shows role badge' do
|
||||
visit dashboard_projects_path
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'Root path' do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:project) { create(:project) }
|
||||
|
||||
before do
|
||||
project.add_developer(user)
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it 'shows the customize banner', :js do
|
||||
visit root_path
|
||||
|
||||
expect(page).to have_content('Do you want to customize this page?')
|
||||
end
|
||||
end
|
|
@ -783,7 +783,7 @@ RSpec.describe 'Pipelines', :js do
|
|||
end
|
||||
|
||||
it 'renders empty state' do
|
||||
expect(page).to have_content 'Build with confidence'
|
||||
expect(page).to have_content 'Use a sample CI/CD template'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -27,7 +27,7 @@ RSpec.describe 'Usage stats consent' do
|
|||
|
||||
expect(page).to have_content(message)
|
||||
|
||||
click_link 'Send usage data'
|
||||
click_link 'Send service data'
|
||||
|
||||
expect(page).not_to have_content(message)
|
||||
expect(page).to have_content('Application settings saved successfully')
|
||||
|
|
|
@ -1481,7 +1481,7 @@ describe('Api', () => {
|
|||
'Content-Type': 'application/json',
|
||||
};
|
||||
|
||||
describe('when usage data increment counter is called with feature flag disabled', () => {
|
||||
describe('when service data increment counter is called with feature flag disabled', () => {
|
||||
beforeEach(() => {
|
||||
gon.features = { ...gon.features, usageDataApi: false };
|
||||
});
|
||||
|
@ -1495,7 +1495,7 @@ describe('Api', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('when usage data increment counter is called', () => {
|
||||
describe('when service data increment counter is called', () => {
|
||||
beforeEach(() => {
|
||||
gon.features = { ...gon.features, usageDataApi: true };
|
||||
});
|
||||
|
@ -1526,7 +1526,7 @@ describe('Api', () => {
|
|||
window.gon.current_user_id = 1;
|
||||
});
|
||||
|
||||
describe('when usage data increment unique users is called with feature flag disabled', () => {
|
||||
describe('when service data increment unique users is called with feature flag disabled', () => {
|
||||
beforeEach(() => {
|
||||
gon.features = { ...gon.features, usageDataApi: false };
|
||||
});
|
||||
|
@ -1541,7 +1541,7 @@ describe('Api', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('when usage data increment unique users is called', () => {
|
||||
describe('when service data increment unique users is called', () => {
|
||||
beforeEach(() => {
|
||||
gon.features = { ...gon.features, usageDataApi: true };
|
||||
});
|
||||
|
|
|
@ -20,7 +20,7 @@ import {
|
|||
import {
|
||||
DESIGN_TRACKING_PAGE_NAME,
|
||||
DESIGN_SNOWPLOW_EVENT_TYPES,
|
||||
DESIGN_USAGE_PING_EVENT_TYPES,
|
||||
DESIGN_SERVICE_PING_EVENT_TYPES,
|
||||
} from '~/design_management/utils/tracking';
|
||||
import createFlash from '~/flash';
|
||||
import mockAllVersions from '../../mock_data/all_versions';
|
||||
|
@ -391,7 +391,7 @@ describe('Design management design index page', () => {
|
|||
});
|
||||
|
||||
describe('with usage_data_design_action enabled', () => {
|
||||
it('tracks design view usage ping', () => {
|
||||
it('tracks design view service ping', () => {
|
||||
createComponent(
|
||||
{ loading: true },
|
||||
{
|
||||
|
@ -402,13 +402,13 @@ describe('Design management design index page', () => {
|
|||
);
|
||||
expect(Api.trackRedisHllUserEvent).toHaveBeenCalledTimes(1);
|
||||
expect(Api.trackRedisHllUserEvent).toHaveBeenCalledWith(
|
||||
DESIGN_USAGE_PING_EVENT_TYPES.DESIGN_ACTION,
|
||||
DESIGN_SERVICE_PING_EVENT_TYPES.DESIGN_ACTION,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with usage_data_design_action disabled', () => {
|
||||
it("doesn't track design view usage ping", () => {
|
||||
it("doesn't track design view service ping", () => {
|
||||
createComponent({ loading: true });
|
||||
expect(Api.trackRedisHllUserEvent).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
|
|
@ -15,6 +15,7 @@ import {
|
|||
urlParams,
|
||||
} from 'jest/issues_list/mock_data';
|
||||
import createFlash from '~/flash';
|
||||
import { convertToGraphQLId } from '~/graphql_shared/utils';
|
||||
import CsvImportExportButtons from '~/issuable/components/csv_import_export_buttons.vue';
|
||||
import IssuableByEmail from '~/issuable/components/issuable_by_email.vue';
|
||||
import IssuableList from '~/issuable_list/components/issuable_list_root.vue';
|
||||
|
@ -54,19 +55,18 @@ describe('IssuesListApp component', () => {
|
|||
localVue.use(VueApollo);
|
||||
|
||||
const defaultProvide = {
|
||||
autocompleteUsersPath: 'autocomplete/users/path',
|
||||
calendarPath: 'calendar/path',
|
||||
canBulkUpdate: false,
|
||||
emptyStateSvgPath: 'empty-state.svg',
|
||||
exportCsvPath: 'export/csv/path',
|
||||
hasBlockedIssuesFeature: true,
|
||||
hasIssueWeightsFeature: true,
|
||||
hasIterationsFeature: true,
|
||||
hasProjectIssues: true,
|
||||
isSignedIn: false,
|
||||
issuesPath: 'path/to/issues',
|
||||
jiraIntegrationPath: 'jira/integration/path',
|
||||
newIssuePath: 'new/issue/path',
|
||||
projectLabelsPath: 'project/labels/path',
|
||||
projectPath: 'path/to/project',
|
||||
rssPath: 'rss/path',
|
||||
showNewIssueLink: true,
|
||||
|
@ -545,9 +545,13 @@ describe('IssuesListApp component', () => {
|
|||
});
|
||||
|
||||
it('renders all tokens', () => {
|
||||
const preloadedAuthors = [
|
||||
{ ...mockCurrentUser, id: convertToGraphQLId('User', mockCurrentUser.id) },
|
||||
];
|
||||
|
||||
expect(findIssuableList().props('searchTokens')).toMatchObject([
|
||||
{ type: TOKEN_TYPE_AUTHOR, preloadedAuthors: [mockCurrentUser] },
|
||||
{ type: TOKEN_TYPE_ASSIGNEE, preloadedAuthors: [mockCurrentUser] },
|
||||
{ type: TOKEN_TYPE_AUTHOR, preloadedAuthors },
|
||||
{ type: TOKEN_TYPE_ASSIGNEE, preloadedAuthors },
|
||||
{ type: TOKEN_TYPE_MILESTONE },
|
||||
{ type: TOKEN_TYPE_LABEL },
|
||||
{ type: TOKEN_TYPE_MY_REACTION },
|
||||
|
|
|
@ -12,6 +12,7 @@ import PipelineEditorMessages from '~/pipeline_editor/components/ui/pipeline_edi
|
|||
import { COMMIT_SUCCESS, COMMIT_FAILURE } from '~/pipeline_editor/constants';
|
||||
import getBlobContent from '~/pipeline_editor/graphql/queries/blob_content.graphql';
|
||||
import getCiConfigData from '~/pipeline_editor/graphql/queries/ci_config.graphql';
|
||||
import getTemplate from '~/pipeline_editor/graphql/queries/get_starter_template.query.graphql';
|
||||
import PipelineEditorApp from '~/pipeline_editor/pipeline_editor_app.vue';
|
||||
import PipelineEditorHome from '~/pipeline_editor/pipeline_editor_home.vue';
|
||||
import {
|
||||
|
@ -47,6 +48,7 @@ describe('Pipeline editor app component', () => {
|
|||
let mockApollo;
|
||||
let mockBlobContentData;
|
||||
let mockCiConfigData;
|
||||
let mockGetTemplate;
|
||||
|
||||
const createComponent = ({ blobLoading = false, options = {}, provide = {} } = {}) => {
|
||||
wrapper = shallowMount(PipelineEditorApp, {
|
||||
|
@ -81,6 +83,7 @@ describe('Pipeline editor app component', () => {
|
|||
const handlers = [
|
||||
[getBlobContent, mockBlobContentData],
|
||||
[getCiConfigData, mockCiConfigData],
|
||||
[getTemplate, mockGetTemplate],
|
||||
];
|
||||
|
||||
mockApollo = createMockApollo(handlers);
|
||||
|
@ -112,6 +115,7 @@ describe('Pipeline editor app component', () => {
|
|||
beforeEach(() => {
|
||||
mockBlobContentData = jest.fn();
|
||||
mockCiConfigData = jest.fn();
|
||||
mockGetTemplate = jest.fn();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
@ -318,4 +322,29 @@ describe('Pipeline editor app component', () => {
|
|||
expect(findEditorHome().exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when a template parameter is present in the URL', () => {
|
||||
const { location } = window;
|
||||
|
||||
beforeEach(() => {
|
||||
delete window.location;
|
||||
window.location = new URL('https://localhost?template=Android');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.location = location;
|
||||
});
|
||||
|
||||
it('renders the given template', async () => {
|
||||
await createComponentWithApollo();
|
||||
|
||||
expect(mockGetTemplate).toHaveBeenCalledWith({
|
||||
projectPath: mockProjectFullPath,
|
||||
templateName: 'Android',
|
||||
});
|
||||
|
||||
expect(findEmptyState().exists()).toBe(false);
|
||||
expect(findTextEditor().exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,14 +1,21 @@
|
|||
import '~/commons';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import EmptyState from '~/pipelines/components/pipelines_list/empty_state.vue';
|
||||
import PipelinesCiTemplates from '~/pipelines/components/pipelines_list/pipelines_ci_templates.vue';
|
||||
|
||||
describe('Pipelines Empty State', () => {
|
||||
let wrapper;
|
||||
|
||||
const findIllustration = () => wrapper.find('img');
|
||||
const findButton = () => wrapper.find('a');
|
||||
const pipelinesCiTemplates = () => wrapper.findComponent(PipelinesCiTemplates);
|
||||
|
||||
const createWrapper = (props = {}) => {
|
||||
wrapper = mount(EmptyState, {
|
||||
provide: {
|
||||
pipelineEditorPath: '',
|
||||
suggestedCiTemplates: [],
|
||||
},
|
||||
propsData: {
|
||||
emptyStateSvgPath: 'foo.svg',
|
||||
canSetCi: true,
|
||||
|
@ -27,27 +34,8 @@ describe('Pipelines Empty State', () => {
|
|||
wrapper = null;
|
||||
});
|
||||
|
||||
it('should render empty state SVG', () => {
|
||||
expect(findIllustration().attributes('src')).toBe('foo.svg');
|
||||
});
|
||||
|
||||
it('should render empty state header', () => {
|
||||
expect(wrapper.text()).toContain('Build with confidence');
|
||||
});
|
||||
|
||||
it('should render empty state information', () => {
|
||||
expect(wrapper.text()).toContain(
|
||||
'GitLab CI/CD can automatically build, test, and deploy your code. Let GitLab take care of time',
|
||||
'consuming tasks, so you can spend more time creating',
|
||||
);
|
||||
});
|
||||
|
||||
it('should render button with help path', () => {
|
||||
expect(findButton().attributes('href')).toBe('/help/ci/quick_start/index.md');
|
||||
});
|
||||
|
||||
it('should render button text', () => {
|
||||
expect(findButton().text()).toBe('Get started with CI/CD');
|
||||
it('should render the CI/CD templates', () => {
|
||||
expect(pipelinesCiTemplates()).toExist();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -1,30 +1,25 @@
|
|||
import '~/commons';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import ExperimentTracking from '~/experimentation/experiment_tracking';
|
||||
import { mockTracking } from 'helpers/tracking_helper';
|
||||
import PipelinesCiTemplate from '~/pipelines/components/pipelines_list/pipelines_ci_templates.vue';
|
||||
|
||||
const addCiYmlPath = "/-/new/main?commit_message='Add%20.gitlab-ci.yml'";
|
||||
const pipelineEditorPath = '/-/ci/editor';
|
||||
const suggestedCiTemplates = [
|
||||
{ name: 'Android', logo: '/assets/illustrations/logos/android.svg' },
|
||||
{ name: 'Bash', logo: '/assets/illustrations/logos/bash.svg' },
|
||||
{ name: 'C++', logo: '/assets/illustrations/logos/c_plus_plus.svg' },
|
||||
];
|
||||
|
||||
jest.mock('~/experimentation/experiment_tracking');
|
||||
|
||||
describe('Pipelines CI Templates', () => {
|
||||
let wrapper;
|
||||
|
||||
const GlEmoji = { template: '<img/>' };
|
||||
let trackingSpy;
|
||||
|
||||
const createWrapper = () => {
|
||||
return shallowMount(PipelinesCiTemplate, {
|
||||
provide: {
|
||||
addCiYmlPath,
|
||||
pipelineEditorPath,
|
||||
suggestedCiTemplates,
|
||||
},
|
||||
stubs: {
|
||||
GlEmoji,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -44,9 +39,9 @@ describe('Pipelines CI Templates', () => {
|
|||
wrapper = createWrapper();
|
||||
});
|
||||
|
||||
it('links to the hello world template', () => {
|
||||
it('links to the getting started template', () => {
|
||||
expect(findTestTemplateLinks().at(0).attributes('href')).toBe(
|
||||
addCiYmlPath.concat('&template=Hello-World'),
|
||||
pipelineEditorPath.concat('?template=Getting-Started'),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -68,7 +63,7 @@ describe('Pipelines CI Templates', () => {
|
|||
|
||||
it('links to the correct template', () => {
|
||||
expect(findTemplateLinks().at(0).attributes('href')).toBe(
|
||||
addCiYmlPath.concat('&template=Android'),
|
||||
pipelineEditorPath.concat('?template=Android'),
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -88,24 +83,25 @@ describe('Pipelines CI Templates', () => {
|
|||
describe('tracking', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = createWrapper();
|
||||
trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
|
||||
});
|
||||
|
||||
it('sends an event when template is clicked', () => {
|
||||
findTemplateLinks().at(0).vm.$emit('click');
|
||||
|
||||
expect(ExperimentTracking).toHaveBeenCalledWith('pipeline_empty_state_templates', {
|
||||
expect(trackingSpy).toHaveBeenCalledTimes(1);
|
||||
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'template_clicked', {
|
||||
label: 'Android',
|
||||
});
|
||||
expect(ExperimentTracking.prototype.event).toHaveBeenCalledWith('template_clicked');
|
||||
});
|
||||
|
||||
it('sends an event when Hello-World template is clicked', () => {
|
||||
it('sends an event when Getting-Started template is clicked', () => {
|
||||
findTestTemplateLinks().at(0).vm.$emit('click');
|
||||
|
||||
expect(ExperimentTracking).toHaveBeenCalledWith('pipeline_empty_state_templates', {
|
||||
label: 'Hello-World',
|
||||
expect(trackingSpy).toHaveBeenCalledTimes(1);
|
||||
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'template_clicked', {
|
||||
label: 'Getting-Started',
|
||||
});
|
||||
expect(ExperimentTracking.prototype.event).toHaveBeenCalledWith('template_clicked');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -12,6 +12,7 @@ import createFlash from '~/flash';
|
|||
import axios from '~/lib/utils/axios_utils';
|
||||
import NavigationControls from '~/pipelines/components/pipelines_list/nav_controls.vue';
|
||||
import PipelinesComponent from '~/pipelines/components/pipelines_list/pipelines.vue';
|
||||
import PipelinesCiTemplates from '~/pipelines/components/pipelines_list/pipelines_ci_templates.vue';
|
||||
import PipelinesTableComponent from '~/pipelines/components/pipelines_list/pipelines_table.vue';
|
||||
import { RAW_TEXT_WARNING } from '~/pipelines/constants';
|
||||
import Store from '~/pipelines/stores/pipelines_store';
|
||||
|
@ -82,6 +83,10 @@ describe('Pipelines', () => {
|
|||
const createComponent = (props = defaultProps) => {
|
||||
wrapper = extendedWrapper(
|
||||
mount(PipelinesComponent, {
|
||||
provide: {
|
||||
pipelineEditorPath: '',
|
||||
suggestedCiTemplates: [],
|
||||
},
|
||||
propsData: {
|
||||
store: new Store(),
|
||||
projectId: mockProjectId,
|
||||
|
@ -551,52 +556,74 @@ describe('Pipelines', () => {
|
|||
await waitForPromises();
|
||||
});
|
||||
|
||||
it('renders empty state', () => {
|
||||
expect(findEmptyState().text()).toContain('Build with confidence');
|
||||
expect(findEmptyState().text()).toContain(
|
||||
'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(
|
||||
'/help/ci/quick_start/index.md',
|
||||
);
|
||||
it('renders the CI/CD templates', () => {
|
||||
expect(wrapper.find(PipelinesCiTemplates)).toExist();
|
||||
});
|
||||
|
||||
describe('when the code_quality_walkthrough experiment is active', () => {
|
||||
beforeAll(() => {
|
||||
getExperimentData.mockImplementation((name) => name === 'code_quality_walkthrough');
|
||||
getExperimentVariant.mockReturnValue('candidate');
|
||||
});
|
||||
|
||||
it('renders another CTA button', () => {
|
||||
expect(findEmptyState().findComponent(GlButton).text()).toBe('Add a code quality job');
|
||||
expect(findEmptyState().findComponent(GlButton).attributes('href')).toBe(
|
||||
paths.codeQualityPagePath,
|
||||
);
|
||||
describe('the control state', () => {
|
||||
beforeAll(() => {
|
||||
getExperimentVariant.mockReturnValue('control');
|
||||
});
|
||||
|
||||
it('renders the CI/CD templates', () => {
|
||||
expect(wrapper.find(PipelinesCiTemplates)).toExist();
|
||||
});
|
||||
});
|
||||
|
||||
describe('the candidate state', () => {
|
||||
beforeAll(() => {
|
||||
getExperimentVariant.mockReturnValue('candidate');
|
||||
});
|
||||
|
||||
it('renders another CTA button', () => {
|
||||
expect(findEmptyState().findComponent(GlButton).text()).toBe('Add a code quality job');
|
||||
expect(findEmptyState().findComponent(GlButton).attributes('href')).toBe(
|
||||
paths.codeQualityPagePath,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the ci_runner_templates experiment is active', () => {
|
||||
beforeAll(() => {
|
||||
getExperimentData.mockImplementation((name) => name === 'ci_runner_templates');
|
||||
getExperimentVariant.mockReturnValue('candidate');
|
||||
});
|
||||
|
||||
it('renders two buttons', () => {
|
||||
expect(findEmptyState().findAllComponents(GlButton).length).toBe(2);
|
||||
expect(findEmptyState().findAllComponents(GlButton).at(0).text()).toBe(
|
||||
'Install GitLab Runners',
|
||||
);
|
||||
expect(findEmptyState().findAllComponents(GlButton).at(0).attributes('href')).toBe(
|
||||
paths.ciRunnerSettingsPath,
|
||||
);
|
||||
expect(findEmptyState().findAllComponents(GlButton).at(1).text()).toBe(
|
||||
'Learn about Runners',
|
||||
);
|
||||
expect(findEmptyState().findAllComponents(GlButton).at(1).attributes('href')).toBe(
|
||||
'/help/ci/quick_start/index.md',
|
||||
);
|
||||
describe('the control state', () => {
|
||||
beforeAll(() => {
|
||||
getExperimentVariant.mockReturnValue('control');
|
||||
});
|
||||
|
||||
it('renders the CI/CD templates', () => {
|
||||
expect(wrapper.find(PipelinesCiTemplates)).toExist();
|
||||
});
|
||||
});
|
||||
|
||||
describe('the candidate state', () => {
|
||||
beforeAll(() => {
|
||||
getExperimentVariant.mockReturnValue('candidate');
|
||||
});
|
||||
|
||||
it('renders two buttons', () => {
|
||||
expect(findEmptyState().findAllComponents(GlButton).length).toBe(2);
|
||||
expect(findEmptyState().findAllComponents(GlButton).at(0).text()).toBe(
|
||||
'Install GitLab Runners',
|
||||
);
|
||||
expect(findEmptyState().findAllComponents(GlButton).at(0).attributes('href')).toBe(
|
||||
paths.ciRunnerSettingsPath,
|
||||
);
|
||||
expect(findEmptyState().findAllComponents(GlButton).at(1).text()).toBe(
|
||||
'Learn about Runners',
|
||||
);
|
||||
expect(findEmptyState().findAllComponents(GlButton).at(1).attributes('href')).toBe(
|
||||
'/help/ci/quick_start/index.md',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -114,7 +114,7 @@ describe('Grouped test reports app', () => {
|
|||
setReports(newFailedTestReports);
|
||||
});
|
||||
|
||||
it('tracks usage ping metric when enabled', () => {
|
||||
it('tracks service ping metric when enabled', () => {
|
||||
mountComponent({ glFeatures: { usageDataITestingSummaryWidgetTotal: true } });
|
||||
findExpandButton().trigger('click');
|
||||
|
||||
|
@ -132,7 +132,7 @@ describe('Grouped test reports app', () => {
|
|||
expect(Api.trackRedisHllUserEvent).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('does not track usage ping metric when disabled', () => {
|
||||
it('does not track service ping metric when disabled', () => {
|
||||
mountComponent({ glFeatures: { usageDataITestingSummaryWidgetTotal: false } });
|
||||
findExpandButton().trigger('click');
|
||||
|
||||
|
|
|
@ -8,8 +8,8 @@ import {
|
|||
SUBMIT_CHANGES_MERGE_REQUEST_ERROR,
|
||||
TRACKING_ACTION_CREATE_COMMIT,
|
||||
TRACKING_ACTION_CREATE_MERGE_REQUEST,
|
||||
USAGE_PING_TRACKING_ACTION_CREATE_COMMIT,
|
||||
USAGE_PING_TRACKING_ACTION_CREATE_MERGE_REQUEST,
|
||||
SERVICE_PING_TRACKING_ACTION_CREATE_COMMIT,
|
||||
SERVICE_PING_TRACKING_ACTION_CREATE_MERGE_REQUEST,
|
||||
DEFAULT_FORMATTING_CHANGES_COMMIT_MESSAGE,
|
||||
DEFAULT_FORMATTING_CHANGES_COMMIT_DESCRIPTION,
|
||||
} from '~/static_site_editor/constants';
|
||||
|
@ -237,7 +237,7 @@ describe('submitContentChanges', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('sends the correct Usage Ping tracking event', () => {
|
||||
describe('sends the correct Service Ping tracking event', () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(Api, 'trackRedisCounterEvent').mockResolvedValue({ data: '' });
|
||||
});
|
||||
|
@ -245,7 +245,7 @@ describe('submitContentChanges', () => {
|
|||
it('for commiting changes', () => {
|
||||
return submitContentChanges(buildPayload()).then(() => {
|
||||
expect(Api.trackRedisCounterEvent).toHaveBeenCalledWith(
|
||||
USAGE_PING_TRACKING_ACTION_CREATE_COMMIT,
|
||||
SERVICE_PING_TRACKING_ACTION_CREATE_COMMIT,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -253,7 +253,7 @@ describe('submitContentChanges', () => {
|
|||
it('for creating a merge request', () => {
|
||||
return submitContentChanges(buildPayload()).then(() => {
|
||||
expect(Api.trackRedisCounterEvent).toHaveBeenCalledWith(
|
||||
USAGE_PING_TRACKING_ACTION_CREATE_MERGE_REQUEST,
|
||||
SERVICE_PING_TRACKING_ACTION_CREATE_MERGE_REQUEST,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -18,7 +18,7 @@ RSpec.describe GitlabSchema.types['Issue'] do
|
|||
confidential discussion_locked upvotes downvotes user_notes_count user_discussions_count web_path web_url relative_position
|
||||
emails_disabled subscribed time_estimate total_time_spent human_time_estimate human_total_time_spent closed_at created_at updated_at task_completion_status
|
||||
design_collection alert_management_alert severity current_user_todos moved moved_to
|
||||
create_note_email timelogs]
|
||||
create_note_email timelogs project_id]
|
||||
|
||||
fields.each do |field_name|
|
||||
expect(described_class).to have_graphql_field(field_name)
|
||||
|
|
|
@ -294,7 +294,6 @@ RSpec.describe IssuesHelper do
|
|||
|
||||
expected = {
|
||||
autocomplete_award_emojis_path: autocomplete_award_emojis_path,
|
||||
autocomplete_users_path: autocomplete_users_path(active: true, current_user: true, project_id: project.id, format: :json),
|
||||
calendar_path: '#',
|
||||
can_bulk_update: 'true',
|
||||
can_edit: 'true',
|
||||
|
@ -313,8 +312,6 @@ RSpec.describe IssuesHelper do
|
|||
max_attachment_size: number_to_human_size(Gitlab::CurrentSettings.max_attachment_size.megabytes),
|
||||
new_issue_path: new_project_issue_path(project, issue: { milestone_id: finder.milestones.first.id }),
|
||||
project_import_jira_path: project_import_jira_path(project),
|
||||
project_labels_path: project_labels_path(project, include_ancestor_groups: true, format: :json),
|
||||
project_milestones_path: project_milestones_path(project, format: :json),
|
||||
project_path: project.full_path,
|
||||
quick_actions_help_path: help_page_path('user/project/quick_actions'),
|
||||
reset_path: new_issuable_address_project_path(project, issuable_type: 'issue'),
|
||||
|
|
|
@ -97,7 +97,17 @@ RSpec.describe UserCalloutsHelper do
|
|||
allow(helper).to receive(:user_dismissed?).with(described_class::CUSTOMIZE_HOMEPAGE) { false }
|
||||
end
|
||||
|
||||
it { is_expected.to be true }
|
||||
context 'when user is on the default dashboard' do
|
||||
it { is_expected.to be true }
|
||||
end
|
||||
|
||||
context 'when user is not on the default dashboard' do
|
||||
before do
|
||||
user.dashboard = 'stars'
|
||||
end
|
||||
|
||||
it { is_expected.to be false }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user dismissed' do
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Backup::GitalyBackup do
|
||||
let(:parallel) { nil }
|
||||
let(:progress) do
|
||||
Tempfile.new('progress').tap do |progress|
|
||||
progress.unlink
|
||||
|
@ -13,7 +14,7 @@ RSpec.describe Backup::GitalyBackup do
|
|||
progress.close
|
||||
end
|
||||
|
||||
subject { described_class.new(progress) }
|
||||
subject { described_class.new(progress, parallel: parallel) }
|
||||
|
||||
context 'unknown' do
|
||||
it 'fails to start unknown' do
|
||||
|
@ -30,6 +31,8 @@ RSpec.describe Backup::GitalyBackup do
|
|||
project_snippet = create(:project_snippet, :repository, project: project)
|
||||
personal_snippet = create(:personal_snippet, :repository, author: project.owner)
|
||||
|
||||
expect(Process).to receive(:spawn).with(anything, 'create', '-path', anything, { in: anything, out: progress }).and_call_original
|
||||
|
||||
subject.start(:create)
|
||||
subject.enqueue(project, Gitlab::GlRepository::PROJECT)
|
||||
subject.enqueue(project, Gitlab::GlRepository::WIKI)
|
||||
|
@ -45,6 +48,17 @@ RSpec.describe Backup::GitalyBackup do
|
|||
expect(File).to exist(File.join(Gitlab.config.backup.path, 'repositories', project_snippet.disk_path + '.bundle'))
|
||||
end
|
||||
|
||||
context 'parallel option set' do
|
||||
let(:parallel) { 3 }
|
||||
|
||||
it 'passes parallel option through' do
|
||||
expect(Process).to receive(:spawn).with(anything, 'create', '-path', anything, '-parallel', '3', { in: anything, out: progress }).and_call_original
|
||||
|
||||
subject.start(:create)
|
||||
subject.wait
|
||||
end
|
||||
end
|
||||
|
||||
it 'raises when the exit code not zero' do
|
||||
expect(subject).to receive(:bin_path).and_return(Gitlab::Utils.which('false'))
|
||||
|
||||
|
@ -83,6 +97,8 @@ RSpec.describe Backup::GitalyBackup do
|
|||
copy_bundle_to_backup_path('personal_snippet_repo.bundle', personal_snippet.disk_path + '.bundle')
|
||||
copy_bundle_to_backup_path('project_snippet_repo.bundle', project_snippet.disk_path + '.bundle')
|
||||
|
||||
expect(Process).to receive(:spawn).with(anything, 'restore', '-path', anything, { in: anything, out: progress }).and_call_original
|
||||
|
||||
subject.start(:restore)
|
||||
subject.enqueue(project, Gitlab::GlRepository::PROJECT)
|
||||
subject.enqueue(project, Gitlab::GlRepository::WIKI)
|
||||
|
@ -100,6 +116,17 @@ RSpec.describe Backup::GitalyBackup do
|
|||
expect(collect_commit_shas.call(project_snippet.repository)).to eq(['6e44ba56a4748be361a841e759c20e421a1651a1'])
|
||||
end
|
||||
|
||||
context 'parallel option set' do
|
||||
let(:parallel) { 3 }
|
||||
|
||||
it 'does not pass parallel option through' do
|
||||
expect(Process).to receive(:spawn).with(anything, 'restore', '-path', anything, { in: anything, out: progress }).and_call_original
|
||||
|
||||
subject.start(:restore)
|
||||
subject.wait
|
||||
end
|
||||
end
|
||||
|
||||
it 'raises when the exit code not zero' do
|
||||
expect(subject).to receive(:bin_path).and_return(Gitlab::Utils.which('false'))
|
||||
|
||||
|
|
|
@ -4,7 +4,8 @@ require 'spec_helper'
|
|||
|
||||
RSpec.describe Backup::Repositories do
|
||||
let(:progress) { spy(:stdout) }
|
||||
let(:strategy) { spy(:strategy) }
|
||||
let(:parallel_enqueue) { true }
|
||||
let(:strategy) { spy(:strategy, parallel_enqueue?: parallel_enqueue) }
|
||||
|
||||
subject { described_class.new(progress, strategy: strategy) }
|
||||
|
||||
|
@ -80,6 +81,22 @@ RSpec.describe Backup::Repositories do
|
|||
end
|
||||
end
|
||||
|
||||
context 'concurrency with a strategy without parallel enqueueing support' do
|
||||
let(:parallel_enqueue) { false }
|
||||
|
||||
it 'enqueues all projects sequentially' do
|
||||
expect(Thread).not_to receive(:new)
|
||||
|
||||
expect(strategy).to receive(:start).with(:create)
|
||||
projects.each do |project|
|
||||
expect(strategy).to receive(:enqueue).with(project, Gitlab::GlRepository::PROJECT)
|
||||
end
|
||||
expect(strategy).to receive(:wait)
|
||||
|
||||
subject.dump(max_concurrency: 2, max_storage_concurrency: 2)
|
||||
end
|
||||
end
|
||||
|
||||
[4, 10].each do |max_storage_concurrency|
|
||||
context "max_storage_concurrency #{max_storage_concurrency}", quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/241701' do
|
||||
let(:storage_keys) { %w[default test_second_storage] }
|
||||
|
|
|
@ -5756,6 +5756,20 @@ RSpec.describe User do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#default_dashboard?' do
|
||||
it 'is the default dashboard' do
|
||||
user = build(:user)
|
||||
|
||||
expect(user.default_dashboard?).to be true
|
||||
end
|
||||
|
||||
it 'is not the default dashboard' do
|
||||
user = build(:user, dashboard: 'stars')
|
||||
|
||||
expect(user.default_dashboard?).to be false
|
||||
end
|
||||
end
|
||||
|
||||
describe '.dormant' do
|
||||
it 'returns dormant users' do
|
||||
freeze_time do
|
||||
|
|
|
@ -212,8 +212,9 @@ RSpec.describe API::ResourceAccessTokens do
|
|||
end
|
||||
|
||||
describe "POST projects/:id/access_tokens" do
|
||||
let(:params) { { name: "test", scopes: ["api"], expires_at: expires_at } }
|
||||
let(:params) { { name: "test", scopes: ["api"], expires_at: expires_at, access_level: access_level } }
|
||||
let(:expires_at) { 1.month.from_now }
|
||||
let(:access_level) { 20 }
|
||||
|
||||
subject(:create_token) { post api("/projects/#{project_id}/access_tokens", user), params: params }
|
||||
|
||||
|
@ -232,6 +233,7 @@ RSpec.describe API::ResourceAccessTokens do
|
|||
expect(response).to have_gitlab_http_status(:created)
|
||||
expect(json_response["name"]).to eq("test")
|
||||
expect(json_response["scopes"]).to eq(["api"])
|
||||
expect(json_response["access_level"]).to eq(20)
|
||||
expect(json_response["expires_at"]).to eq(expires_at.to_date.iso8601)
|
||||
expect(json_response["token"]).to be_present
|
||||
end
|
||||
|
@ -249,6 +251,21 @@ RSpec.describe API::ResourceAccessTokens do
|
|||
expect(json_response["expires_at"]).to eq(nil)
|
||||
end
|
||||
end
|
||||
|
||||
context "when 'access_level' is not set" do
|
||||
let(:access_level) { nil }
|
||||
|
||||
it 'creates a project access token with the default access level', :aggregate_failures do
|
||||
create_token
|
||||
|
||||
expect(response).to have_gitlab_http_status(:created)
|
||||
expect(json_response["name"]).to eq("test")
|
||||
expect(json_response["scopes"]).to eq(["api"])
|
||||
expect(json_response["access_level"]).to eq(40)
|
||||
expect(json_response["expires_at"]).to eq(expires_at.to_date.iso8601)
|
||||
expect(json_response["token"]).to be_present
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "with invalid params" do
|
||||
|
|
|
@ -79,15 +79,14 @@ RSpec.describe AuditEventService do
|
|||
context 'with IP address', :request_store do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
where(:from_caller, :from_context, :from_author_sign_in, :output) do
|
||||
'192.168.0.1' | '192.168.0.2' | '192.168.0.3' | '192.168.0.1'
|
||||
nil | '192.168.0.2' | '192.168.0.3' | '192.168.0.2'
|
||||
nil | nil | '192.168.0.3' | '192.168.0.3'
|
||||
where(:from_context, :from_author_sign_in, :output) do
|
||||
'192.168.0.2' | '192.168.0.3' | '192.168.0.2'
|
||||
nil | '192.168.0.3' | '192.168.0.3'
|
||||
end
|
||||
|
||||
with_them do
|
||||
let(:user) { create(:user, current_sign_in_ip: from_author_sign_in) }
|
||||
let(:audit_service) { described_class.new(user, user, with: 'standard', ip_address: from_caller) }
|
||||
let(:audit_service) { described_class.new(user, user, with: 'standard') }
|
||||
|
||||
before do
|
||||
allow(Gitlab::RequestContext.instance).to receive(:client_ip).and_return(from_context)
|
||||
|
|
|
@ -88,12 +88,28 @@ RSpec.describe ResourceAccessTokens::CreateService do
|
|||
end
|
||||
end
|
||||
|
||||
it 'adds the bot user as a maintainer in the resource' do
|
||||
response = subject
|
||||
access_token = response.payload[:access_token]
|
||||
bot_user = access_token.user
|
||||
context 'access level' do
|
||||
context 'when user does not specify an access level' do
|
||||
it 'adds the bot user as a maintainer in the resource' do
|
||||
response = subject
|
||||
access_token = response.payload[:access_token]
|
||||
bot_user = access_token.user
|
||||
|
||||
expect(resource.members.maintainers.map(&:user_id)).to include(bot_user.id)
|
||||
expect(resource.members.maintainers.map(&:user_id)).to include(bot_user.id)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user specifies an access level' do
|
||||
let_it_be(:params) { { access_level: Gitlab::Access::DEVELOPER } }
|
||||
|
||||
it 'adds the bot user with the specified access level in the resource' do
|
||||
response = subject
|
||||
access_token = response.payload[:access_token]
|
||||
bot_user = access_token.user
|
||||
|
||||
expect(resource.members.developers.map(&:user_id)).to include(bot_user.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'personal access token' do
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue