Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-11-04 12:10:55 +00:00
parent 9f0d276489
commit f2fd07aa1c
90 changed files with 1064 additions and 512 deletions

View File

@ -308,6 +308,10 @@ Rails/RakeEnvironment:
# Context on why it's disabled: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93419#note_1048223982
Enabled: false
# See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/96675#note_1094403693
Rails/WhereExists:
Enabled: false
# GitLab ###################################################################
Gitlab/ModuleWithInstanceVariables:

View File

@ -3920,7 +3920,6 @@ Layout/LineLength:
- 'spec/features/groups/settings/repository_spec.rb'
- 'spec/features/groups_spec.rb'
- 'spec/features/ide/static_object_external_storage_csp_spec.rb'
- 'spec/features/incidents/user_views_incident_spec.rb'
- 'spec/features/invites_spec.rb'
- 'spec/features/issuables/issuable_list_spec.rb'
- 'spec/features/issuables/markdown_references/internal_references_spec.rb'

View File

@ -1,30 +1,28 @@
---
# Cop supports --auto-correct.
Rails/NegateInclude:
# Offense count: 65
# Temporarily disabled due to too many offenses
Enabled: false
Details: grace period
Exclude:
- 'app/finders/projects_finder.rb'
- 'app/helpers/application_settings_helper.rb'
- 'app/helpers/projects_helper.rb'
- 'app/helpers/tree_helper.rb'
- 'app/models/concerns/timebox.rb'
- 'app/models/integrations/chat_message/pipeline_message.rb'
- 'app/models/integrations/field.rb'
- 'app/models/label.rb'
- 'app/models/merge_request.rb'
- 'app/models/milestone.rb'
- 'app/services/todo_service.rb'
- 'app/services/work_items/parent_links/create_service.rb'
- 'config/application.rb'
- 'config/initializers/1_settings.rb'
- 'danger/roulette/Dangerfile'
- 'ee/app/finders/security/pipeline_vulnerabilities_finder.rb'
- 'ee/app/models/ee/epic.rb'
- 'ee/app/models/ee/vulnerability.rb'
- 'ee/app/services/epic_issues/create_service.rb'
- 'ee/app/services/security/ingestion/tasks/ingest_remediations.rb'
- 'ee/app/services/security/security_orchestration_policies/validate_policy_service.rb'
- 'lib/api/maven_packages.rb'
- 'lib/generators/gitlab/usage_metric_generator.rb'
- 'lib/gitlab/background_migration/legacy_upload_mover.rb'
- 'lib/gitlab/ci/build/rules/rule/clause/exists.rb'
- 'lib/gitlab/ci/parsers/coverage/sax_document.rb'
@ -38,11 +36,10 @@ Rails/NegateInclude:
- 'lib/gitlab/task_helpers.rb'
- 'lib/gitlab/url_blocker.rb'
- 'lib/gitlab_edition.rb'
- 'qa/qa/page/merge_request/show.rb'
- 'qa/qa/runtime/ip_address.rb'
- 'qa/qa/support/run.rb'
- 'qa/qa/tools/delete_test_users.rb'
- 'qa/qa/vendor/jenkins/page/configure_job.rb'
- 'qa/qa/vendor/jenkins/page/last_job_console.rb'
- 'rubocop/cop/gitlab/feature_available_usage.rb'
- 'rubocop/cop/graphql/id_type.rb'
- 'rubocop/cop/migration/add_reference.rb'
@ -56,3 +53,4 @@ Rails/NegateInclude:
- 'spec/support/matchers/pushed_frontend_feature_flags_matcher.rb'
- 'spec/support/shared_contexts/markdown_golden_master_shared_examples.rb'
- 'spec/uploaders/object_storage_spec.rb'
- 'tooling/danger/specs.rb'

View File

@ -1,44 +0,0 @@
---
# Cop supports --auto-correct.
Rails/WhereExists:
# Offense count: 48
# Temporarily disabled due to too many offenses
Enabled: false
Exclude:
- 'app/models/application_setting/term.rb'
- 'app/models/ci/pipeline_artifact.rb'
- 'app/models/ci/ref.rb'
- 'app/models/clusters/agent.rb'
- 'app/models/concerns/has_wiki.rb'
- 'app/models/concerns/noteable.rb'
- 'app/models/container_repository.rb'
- 'app/models/design_management/design.rb'
- 'app/models/group.rb'
- 'app/models/group_deploy_token.rb'
- 'app/models/label.rb'
- 'app/models/lfs_object.rb'
- 'app/models/merge_request_diff.rb'
- 'app/models/namespace.rb'
- 'app/models/project.rb'
- 'app/models/protected_branch/push_access_level.rb'
- 'app/services/projects/transfer_service.rb'
- 'app/services/todos/destroy/unauthorized_features_service.rb'
- 'db/migrate/20210422195929_create_elastic_reindexing_slices.rb'
- 'ee/app/models/approval_merge_request_rule_source.rb'
- 'ee/app/models/concerns/ee/protected_ref_access.rb'
- 'ee/app/models/ee/epic.rb'
- 'ee/app/models/ee/group_member.rb'
- 'ee/app/models/ee/milestone_release.rb'
- 'ee/app/models/geo_node.rb'
- 'ee/app/models/merge_requests/external_status_check.rb'
- 'ee/app/models/merge_train.rb'
- 'ee/app/workers/concerns/elastic/indexing_control.rb'
- 'lib/gitlab/auth.rb'
- 'lib/gitlab/checks/matching_merge_request.rb'
- 'lib/gitlab/database/partitioning/detached_partition_dropper.rb'
- 'spec/lib/bulk_imports/projects/pipelines/snippets_repository_pipeline_spec.rb'
- 'spec/migrations/recreate_index_security_ci_builds_on_name_and_id_parser_features_spec.rb'
- 'spec/migrations/recreate_index_security_ci_builds_on_name_and_id_parser_with_new_features_spec.rb'
- 'spec/models/user_spec.rb'
- 'spec/services/clusters/cleanup/service_account_service_spec.rb'
- 'spec/services/clusters/destroy_service_spec.rb'

View File

@ -1 +1 @@
b55578ec476e8bc8ecd9775ee7e9960b52e0f6e0
f75740430e51520d3edcd22065285cec050d2b74

View File

@ -11,7 +11,7 @@ export default {
BoardSettingsSidebar,
BoardTopBar,
},
inject: ['disabled'],
inject: ['disabled', 'fullBoardId'],
computed: {
...mapGetters(['isSidebarOpen']),
},
@ -27,7 +27,7 @@ export default {
<template>
<div class="boards-app gl-relative" :class="{ 'is-compact': isSidebarOpen }">
<board-top-bar />
<board-content :disabled="disabled" />
<board-content :disabled="disabled" :board-id="fullBoardId" />
<board-settings-sidebar />
</div>
</template>

View File

@ -3,12 +3,24 @@ import { GlAlert } from '@gitlab/ui';
import { sortBy, throttle } from 'lodash';
import Draggable from 'vuedraggable';
import { mapState, mapGetters, mapActions } from 'vuex';
import { s__ } from '~/locale';
import { formatBoardLists } from 'ee_else_ce/boards/boards_util';
import BoardAddNewColumn from 'ee_else_ce/boards/components/board_add_new_column.vue';
import { defaultSortableOptions } from '~/sortable/constants';
import { DraggableItemTypes } from '../constants';
import {
DraggableItemTypes,
issuableTypes,
BoardType,
listsQuery,
} from 'ee_else_ce/boards/constants';
import BoardColumn from './board_column.vue';
export default {
i18n: {
fetchError: s__(
'Boards|An error occurred while fetching the board lists. Please reload the page.',
),
},
draggableItemTypes: DraggableItemTypes,
components: {
BoardAddNewColumn,
@ -19,26 +31,76 @@ export default {
EpicsSwimlanes: () => import('ee_component/boards/components/epics_swimlanes.vue'),
GlAlert,
},
inject: ['canAdminList'],
inject: ['canAdminList', 'boardType', 'fullPath', 'issuableType', 'isApolloBoard'],
props: {
disabled: {
type: Boolean,
required: true,
},
boardId: {
type: String,
required: true,
},
},
data() {
return {
boardHeight: null,
boardListsApollo: {},
apolloError: null,
updatedBoardId: this.boardId,
};
},
apollo: {
boardListsApollo: {
query() {
return listsQuery[this.issuableType].query;
},
variables() {
return this.queryVariables;
},
skip() {
return !this.isApolloBoard;
},
update(data) {
const { lists } = data[this.boardType].board;
return formatBoardLists(lists);
},
result() {
// this allows us to delay fetching lists when we switch a board to fetch the actual board lists
// instead of fetching lists for the "previous" board
this.updatedBoardId = this.boardId;
},
error() {
this.apolloError = this.$options.i18n.fetchError;
},
},
},
computed: {
...mapState(['boardLists', 'error', 'addColumnForm']),
...mapGetters(['isSwimlanesOn', 'isEpicBoard', 'isIssueBoard']),
...mapGetters(['isSwimlanesOn']),
isIssueBoard() {
return this.issuableType === issuableTypes.issue;
},
isEpicBoard() {
return this.issuableType === issuableTypes.epic;
},
addColumnFormVisible() {
return this.addColumnForm?.visible;
},
queryVariables() {
return {
...(this.isIssueBoard && {
isGroup: this.boardType === BoardType.group,
isProject: this.boardType === BoardType.project,
}),
fullPath: this.fullPath,
boardId: this.boardId,
filterParams: this.filterParams,
};
},
boardListsToUse() {
return sortBy([...Object.values(this.boardLists)], 'position');
const lists = this.isApolloBoard ? this.boardListsApollo : this.boardLists;
return sortBy([...Object.values(lists)], 'position');
},
canDragColumns() {
return this.canAdminList;
@ -59,6 +121,9 @@ export default {
return this.canDragColumns ? options : {};
},
errorToDisplay() {
return this.isApolloBoard ? this.apolloError : this.error;
},
},
mounted() {
this.setBoardHeight();
@ -88,8 +153,8 @@ export default {
<template>
<div v-cloak data-qa-selector="boards_list">
<gl-alert v-if="error" variant="danger" :dismissible="true" @dismiss="unsetError">
{{ error }}
<gl-alert v-if="errorToDisplay" variant="danger" :dismissible="true" @dismiss="unsetError">
{{ errorToDisplay }}
</gl-alert>
<component
:is="boardColumnWrapper"

View File

@ -53,6 +53,8 @@ function mountBoardApp(el) {
store,
apolloProvider,
provide: {
isApolloBoard: window.gon?.features?.apolloBoards,
fullBoardId: fullBoardId(boardId),
disabled: parseBoolean(el.dataset.disabled),
groupId: Number(groupId),
rootPath,

View File

@ -3,7 +3,7 @@
import $ from 'jquery';
import issuableEventHub from '~/issues/list/eventhub';
import LabelsSelect from '~/labels/labels_select';
import MilestoneSelect from '~/milestones/milestone_select';
import { mountMilestoneDropdown } from '~/sidebar/mount_sidebar';
import IssuableBulkUpdateActions from './issuable_bulk_update_actions';
const HIDDEN_CLASS = 'hidden';
@ -55,7 +55,7 @@ export default class IssuableBulkUpdateSidebar {
initDropdowns() {
new LabelsSelect();
new MilestoneSelect();
mountMilestoneDropdown();
// Checking IS_EE and using ee_else_ce is odd, but we do it here to satisfy
// the import/no-unresolved lint rule when FOSS_ONLY=1, even though at

View File

@ -29,7 +29,7 @@ export default {
dagDocPath: {
default: null,
},
emptySvgPath: {
emptyDagSvgPath: {
default: '',
},
pipelineIid: {
@ -213,7 +213,7 @@ export default {
/>
<gl-empty-state
v-else-if="hasNoDependentJobs"
:svg-path="emptySvgPath"
:svg-path="emptyDagSvgPath"
:title="$options.emptyStateTexts.title"
>
<template #description>

View File

@ -1,12 +1,13 @@
<script>
import { GlBadge, GlTabs, GlTab } from '@gitlab/ui';
import { __ } from '~/locale';
import { failedJobsTabName, jobsTabName, needsTabName, testReportTabName } from '../constants';
import PipelineGraphWrapper from './graph/graph_component_wrapper.vue';
import Dag from './dag/dag.vue';
import FailedJobsApp from './jobs/failed_jobs_app.vue';
import JobsApp from './jobs/jobs_app.vue';
import TestReports from './test_reports/test_reports.vue';
import {
failedJobsTabName,
jobsTabName,
needsTabName,
pipelineTabName,
testReportTabName,
} from '../constants';
export default {
i18n: {
@ -19,20 +20,16 @@ export default {
},
},
tabNames: {
pipeline: pipelineTabName,
needs: needsTabName,
jobs: jobsTabName,
failures: failedJobsTabName,
tests: testReportTabName,
},
components: {
Dag,
GlBadge,
GlTab,
GlTabs,
JobsApp,
FailedJobsApp,
PipelineGraphWrapper,
TestReports,
},
inject: [
'defaultTabValue',
@ -41,14 +38,27 @@ export default {
'totalJobCount',
'testsCount',
],
data() {
return {
activeTab: this.defaultTabValue,
};
},
computed: {
showFailedJobsTab() {
return this.failedJobsCount > 0;
},
},
watch: {
$route(to) {
this.activeTab = to.name;
},
},
methods: {
isActive(tabName) {
return tabName === this.defaultTabValue;
return tabName === this.activeTab;
},
navigateTo(tabName) {
this.$router.push({ name: tabName });
},
},
};
@ -59,10 +69,12 @@ export default {
<gl-tab
ref="pipelineTab"
:title="$options.i18n.tabs.pipelineTitle"
:active="isActive($options.tabNames.pipeline)"
data-testid="pipeline-tab"
lazy
@click="navigateTo($options.tabNames.pipeline)"
>
<pipeline-graph-wrapper />
<router-view />
</gl-tab>
<gl-tab
ref="dagTab"
@ -70,15 +82,21 @@ export default {
:active="isActive($options.tabNames.needs)"
data-testid="dag-tab"
lazy
@click="navigateTo($options.tabNames.needs)"
>
<dag />
<router-view />
</gl-tab>
<gl-tab :active="isActive($options.tabNames.jobs)" data-testid="jobs-tab" lazy>
<gl-tab
:active="isActive($options.tabNames.jobs)"
data-testid="jobs-tab"
lazy
@click="navigateTo($options.tabNames.jobs)"
>
<template #title>
<span class="gl-mr-2">{{ $options.i18n.tabs.jobsTitle }}</span>
<gl-badge size="sm" data-testid="builds-counter">{{ totalJobCount }}</gl-badge>
</template>
<jobs-app />
<router-view />
</gl-tab>
<gl-tab
v-if="showFailedJobsTab"
@ -86,19 +104,25 @@ export default {
:active="isActive($options.tabNames.failures)"
data-testid="failed-jobs-tab"
lazy
@click="navigateTo($options.tabNames.failures)"
>
<template #title>
<span class="gl-mr-2">{{ $options.i18n.tabs.failedJobsTitle }}</span>
<gl-badge size="sm" data-testid="failed-builds-counter">{{ failedJobsCount }}</gl-badge>
</template>
<failed-jobs-app :failed-jobs-summary="failedJobsSummary" />
<router-view :failed-jobs-summary="failedJobsSummary" />
</gl-tab>
<gl-tab :active="isActive($options.tabNames.tests)" data-testid="tests-tab" lazy>
<gl-tab
:active="isActive($options.tabNames.tests)"
data-testid="tests-tab"
lazy
@click="navigateTo($options.tabNames.tests)"
>
<template #title>
<span class="gl-mr-2">{{ $options.i18n.tabs.testsTitle }}</span>
<gl-badge size="sm" data-testid="tests-counter">{{ testsCount }}</gl-badge>
</template>
<test-reports />
<router-view />
</gl-tab>
<slot></slot>
</gl-tabs>

View File

@ -47,8 +47,7 @@ export const CHILD_VIEW = 'child';
// Pipeline tabs
export const TAB_QUERY_PARAM = 'tab';
export const pipelineTabName = 'graph';
export const needsTabName = 'dag';
export const jobsTabName = 'builds';
export const failedJobsTabName = 'failures';

View File

@ -1,3 +1,4 @@
import VueRouter from 'vue-router';
import { createAlert } from '~/flash';
import { __, s__ } from '~/locale';
import createDagApp from './pipeline_details_dag';
@ -32,9 +33,16 @@ export default async function initPipelineDetailsBundle() {
if (gon.features?.pipelineTabsVue) {
const { createAppOptions } = await import('ee_else_ce/pipelines/pipeline_tabs');
const { createPipelineTabs } = await import('./pipeline_tabs');
const { routes } = await import('ee_else_ce/pipelines/routes');
const router = new VueRouter({
mode: 'history',
base: dataset.pipelinePath,
routes,
});
try {
const appOptions = createAppOptions(SELECTORS.PIPELINE_TABS, apolloProvider);
const appOptions = createAppOptions(SELECTORS.PIPELINE_TABS, apolloProvider, router);
createPipelineTabs(appOptions);
} catch {
createAlert({

View File

@ -1,17 +1,17 @@
import Vue from 'vue';
import VueRouter from 'vue-router';
import Vuex from 'vuex';
import VueApollo from 'vue-apollo';
import PipelineTabs from 'ee_else_ce/pipelines/components/pipeline_tabs.vue';
import { removeParams, updateHistory } from '~/lib/utils/url_utility';
import { TAB_QUERY_PARAM } from '~/pipelines/constants';
import { parseBoolean } from '~/lib/utils/common_utils';
import createTestReportsStore from './stores/test_reports';
import { getPipelineDefaultTab, reportToSentry } from './utils';
Vue.use(VueApollo);
Vue.use(VueRouter);
Vue.use(Vuex);
export const createAppOptions = (selector, apolloProvider) => {
export const createAppOptions = (selector, apolloProvider, router) => {
const el = document.querySelector(selector);
if (!el) return null;
@ -40,6 +40,7 @@ export const createAppOptions = (selector, apolloProvider) => {
suiteEndpoint,
blobPath,
hasTestReport,
emptyDagSvgPath,
emptyStateImagePath,
artifactsExpiredImagePath,
isFullCodequalityReportAvailable,
@ -65,6 +66,7 @@ export const createAppOptions = (selector, apolloProvider) => {
}),
},
}),
router,
provide: {
canGenerateCodequalityReports: parseBoolean(canGenerateCodequalityReports),
codequalityReportDownloadPath,
@ -91,6 +93,7 @@ export const createAppOptions = (selector, apolloProvider) => {
suiteEndpoint,
blobPath,
hasTestReport,
emptyDagSvgPath,
emptyStateImagePath,
artifactsExpiredImagePath,
testsCount,
@ -107,12 +110,6 @@ export const createAppOptions = (selector, apolloProvider) => {
export const createPipelineTabs = (options) => {
if (!options) return;
updateHistory({
url: removeParams([TAB_QUERY_PARAM]),
title: document.title,
replace: true,
});
// eslint-disable-next-line no-new
new Vue(options);
};

View File

@ -0,0 +1,20 @@
import PipelineGraphWrapper from './components/graph/graph_component_wrapper.vue';
import Dag from './components/dag/dag.vue';
import FailedJobsApp from './components/jobs/failed_jobs_app.vue';
import JobsApp from './components/jobs/jobs_app.vue';
import TestReports from './components/test_reports/test_reports.vue';
import {
pipelineTabName,
needsTabName,
jobsTabName,
failedJobsTabName,
testReportTabName,
} from './constants';
export const routes = [
{ name: pipelineTabName, path: '/', component: PipelineGraphWrapper },
{ name: needsTabName, path: '/dag', component: Dag },
{ name: jobsTabName, path: '/builds', component: JobsApp },
{ name: failedJobsTabName, path: '/failures', component: FailedJobsApp },
{ name: testReportTabName, path: '/test_report', component: TestReports },
];

View File

@ -1,12 +1,7 @@
import * as Sentry from '@sentry/browser';
import { pickBy } from 'lodash';
import { getParameterValues } from '~/lib/utils/url_utility';
import {
NEEDS_PROPERTY,
SUPPORTED_FILTER_PARAMETERS,
TAB_QUERY_PARAM,
validPipelineTabNames,
} from './constants';
import { parseUrlPathname } from '~/lib/utils/url_utility';
import { NEEDS_PROPERTY, SUPPORTED_FILTER_PARAMETERS, validPipelineTabNames } from './constants';
/*
The following functions are the main engine in transforming the data as
received from the endpoint into the format the d3 graph expects.
@ -145,10 +140,12 @@ export const reportMessageToSentry = (component, message, context) => {
};
export const getPipelineDefaultTab = (url) => {
const [tabQueryValue] = getParameterValues(TAB_QUERY_PARAM, url);
const strippedUrl = parseUrlPathname(url);
const regexp = /\w*$/;
const [tabName] = strippedUrl.match(regexp);
if (tabQueryValue && validPipelineTabNames.includes(tabQueryValue)) {
return tabQueryValue;
if (tabName && validPipelineTabNames.includes(tabName)) {
return tabName;
}
return null;

View File

@ -0,0 +1,75 @@
<script>
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { IssuableType, WorkspaceType } from '~/issues/constants';
import { __ } from '~/locale';
import { IssuableAttributeType } from '../../constants';
import SidebarDropdown from '../sidebar_dropdown.vue';
const noMilestone = {
id: 0,
title: __('No milestone'),
};
const placeholderMilestone = {
id: -1,
title: __('Select milestone'),
};
export default {
issuableAttribute: IssuableAttributeType.Milestone,
components: {
SidebarDropdown,
},
props: {
attrWorkspacePath: {
required: true,
type: String,
},
issuableType: {
type: String,
required: true,
validator(value) {
return [IssuableType.Issue, IssuableType.MergeRequest].includes(value);
},
},
workspaceType: {
type: String,
required: true,
validator(value) {
return [WorkspaceType.group, WorkspaceType.project].includes(value);
},
},
},
data() {
return {
milestone: placeholderMilestone,
};
},
computed: {
value() {
return this.milestone.id === placeholderMilestone.id
? undefined
: getIdFromGraphQLId(this.milestone.id);
},
},
methods: {
handleChange(milestone) {
this.milestone = milestone.id === null ? noMilestone : milestone;
},
},
};
</script>
<template>
<div>
<input type="hidden" name="update[milestone_id]" :value="value" />
<sidebar-dropdown
:attr-workspace-path="attrWorkspacePath"
:current-attribute="milestone"
:issuable-attribute="$options.issuableAttribute"
:issuable-type="issuableType"
:workspace-type="workspaceType"
@change="handleChange"
/>
</div>
</template>

View File

@ -8,7 +8,7 @@ import {
GlSearchBoxByType,
} from '@gitlab/ui';
import { kebabCase, snakeCase } from 'lodash';
import { IssuableType } from '~/issues/constants';
import { IssuableType, WorkspaceType } from '~/issues/constants';
import { __ } from '~/locale';
import {
defaultEpicSort,
@ -73,6 +73,14 @@ export default {
return [IssuableType.Issue, IssuableType.MergeRequest].includes(value);
},
},
workspaceType: {
type: String,
required: false,
default: WorkspaceType.project,
validator(value) {
return [WorkspaceType.group, WorkspaceType.project].includes(value);
},
},
},
data() {
return {
@ -86,7 +94,7 @@ export default {
query() {
const { list } = this.issuableAttributeQuery;
const { query } = list[this.issuableType];
return query;
return query[this.workspaceType] || query;
},
variables() {
if (!this.isEpic) {

View File

@ -53,6 +53,7 @@ import updateIssueAssigneesMutation from '~/vue_shared/components/sidebar/querie
import updateMergeRequestAssigneesMutation from '~/vue_shared/components/sidebar/queries/update_mr_assignees.mutation.graphql';
import getEscalationStatusQuery from '~/sidebar/queries/escalation_status.query.graphql';
import updateEscalationStatusMutation from '~/sidebar/queries/update_escalation_status.mutation.graphql';
import groupMilestonesQuery from './queries/group_milestones.query.graphql';
import projectIssueMilestoneMutation from './queries/project_issue_milestone.mutation.graphql';
import projectIssueMilestoneQuery from './queries/project_issue_milestone.query.graphql';
import projectMilestonesQuery from './queries/project_milestones.query.graphql';
@ -241,10 +242,16 @@ export const issuableMilestoneQueries = {
export const milestonesQueries = {
[IssuableType.Issue]: {
query: projectMilestonesQuery,
query: {
[WorkspaceType.group]: groupMilestonesQuery,
[WorkspaceType.project]: projectMilestonesQuery,
},
},
[IssuableType.MergeRequest]: {
query: projectMilestonesQuery,
query: {
[WorkspaceType.group]: groupMilestonesQuery,
[WorkspaceType.project]: projectMilestonesQuery,
},
},
};

View File

@ -18,6 +18,7 @@ import CollapsedAssigneeList from '~/sidebar/components/assignees/collapsed_assi
import SidebarAssigneesWidget from '~/sidebar/components/assignees/sidebar_assignees_widget.vue';
import SidebarConfidentialityWidget from '~/sidebar/components/confidential/sidebar_confidentiality_widget.vue';
import SidebarDueDateWidget from '~/sidebar/components/date/sidebar_date_widget.vue';
import BulkUpdateMilestoneDropdown from '~/sidebar/components/milestone/bulk_update_milestone_dropdown.vue';
import SidebarParticipantsWidget from '~/sidebar/components/participants/sidebar_participants_widget.vue';
import SidebarReferenceWidget from '~/sidebar/components/reference/sidebar_reference_widget.vue';
import SidebarDropdownWidget from '~/sidebar/components/sidebar_dropdown_widget.vue';
@ -289,6 +290,31 @@ function mountMilestoneSelect() {
});
}
export function mountMilestoneDropdown() {
const el = document.querySelector('.js-milestone-dropdown');
if (!el) {
return null;
}
Vue.use(VueApollo);
return new Vue({
el,
name: 'BulkUpdateMilestoneDropdownRoot',
apolloProvider,
render(createElement) {
return createElement(BulkUpdateMilestoneDropdown, {
props: {
attrWorkspacePath: el.dataset.fullPath,
issuableType: isInIssuePage() ? IssuableType.Issue : IssuableType.MergeRequest,
workspaceType: el.dataset.workspaceType,
},
});
},
});
}
export function mountSidebarLabels() {
const el = document.querySelector('.js-sidebar-labels');

View File

@ -7,6 +7,7 @@ class Groups::BoardsController < Groups::ApplicationController
before_action do
push_frontend_feature_flag(:board_multi_select, group)
push_frontend_feature_flag(:apollo_boards, group)
push_frontend_feature_flag(:realtime_labels, group)
experiment(:prominent_create_board_btn, subject: current_user) do |e|
e.control {}

View File

@ -7,6 +7,7 @@ class Projects::BoardsController < Projects::ApplicationController
before_action :check_issues_available!
before_action do
push_frontend_feature_flag(:board_multi_select, project)
push_frontend_feature_flag(:apollo_boards, project)
push_frontend_feature_flag(:realtime_labels, project&.group)
experiment(:prominent_create_board_btn, subject: current_user) do |e|
e.control {}

View File

@ -140,21 +140,13 @@ class Projects::PipelinesController < Projects::ApplicationController
end
def builds
if Feature.enabled?(:pipeline_tabs_vue, project)
redirect_to pipeline_path(@pipeline, tab: 'builds')
else
render_show
end
render_show
end
def dag
respond_to do |format|
format.html do
if Feature.enabled?(:pipeline_tabs_vue, project)
redirect_to pipeline_path(@pipeline, tab: 'dag')
else
render_show
end
render_show
end
format.json do
render json: Ci::DagPipelineSerializer
@ -165,9 +157,7 @@ class Projects::PipelinesController < Projects::ApplicationController
end
def failures
if Feature.enabled?(:pipeline_tabs_vue, project)
redirect_to pipeline_path(@pipeline, tab: 'failures')
elsif @pipeline.failed_builds.present?
if @pipeline.failed_builds.present?
render_show
else
redirect_to pipeline_path(@pipeline)
@ -222,11 +212,7 @@ class Projects::PipelinesController < Projects::ApplicationController
def test_report
respond_to do |format|
format.html do
if Feature.enabled?(:pipeline_tabs_vue, project)
redirect_to pipeline_path(@pipeline, tab: 'test_report')
else
render_show
end
render_show
end
format.json do
render json: TestReportSerializer

View File

@ -19,6 +19,7 @@ module Projects
blob_path: project_blob_path(project, pipeline.sha),
has_test_report: pipeline.has_test_reports?,
empty_state_image_path: image_path('illustrations/empty-state/empty-test-cases-lg.svg'),
empty_dag_svg_path: image_path('illustrations/empty-state/empty-dag-md.svg'),
artifacts_expired_image_path: image_path('illustrations/pipeline.svg'),
tests_count: pipeline.test_report_summary.total[:count]
}

View File

@ -13,8 +13,7 @@ module AlertManagement
key: Settings.attr_encrypted_db_key_base_32,
algorithm: 'aes-256-gcm'
default_value_for(:endpoint_identifier, allows_nil: false) { SecureRandom.hex(8) }
default_value_for(:token) { generate_token }
attribute :endpoint_identifier, default: -> { SecureRandom.hex(8) }
validates :project, presence: true
validates :active, inclusion: { in: [true, false] }

View File

@ -639,14 +639,6 @@ class ApplicationSetting < ApplicationRecord
validates :inactive_projects_send_warning_email_after_months,
numericality: { only_integer: true, greater_than: 0, less_than: :inactive_projects_delete_after_months }
validates :cube_api_base_url,
addressable_url: { allow_localhost: true, allow_local_network: false },
allow_blank: true
validates :product_analytics_enabled,
presence: true,
allow_blank: true
attr_encrypted :asset_proxy_secret_key,
mode: :per_attribute_iv,
key: Settings.attr_encrypted_db_key_base_truncated,

View File

@ -3,11 +3,13 @@
class ProjectImportEntity < ProjectEntity
include ImportHelper
expose :import_source
expose :import_status
expose :human_import_status_name
expose :import_source, documentation: { type: 'string', example: 'source/source-repo' }
expose :import_status, documentation: {
type: 'string', example: 'scheduled', values: %w[scheduled started finished failed canceled]
}
expose :human_import_status_name, documentation: { type: 'string', example: 'canceled' }
expose :provider_link do |project, options|
expose :provider_link, documentation: { type: 'string', example: '/source/source-repo' } do |project, options|
provider_project_link_url(options[:provider_url], project[:import_source])
end
end

View File

@ -119,6 +119,7 @@
= render_if_exists 'admin/application_settings/feishu_integration'
= render 'admin/application_settings/third_party_offers'
= render 'admin/application_settings/snowplow'
= render_if_exists 'admin/application_settings/product_analytics' if Feature.enabled?(:cube_api_proxy)
= render 'admin/application_settings/error_tracking' if Feature.enabled?(:gitlab_error_tracking)
= render 'admin/application_settings/eks'
= render 'admin/application_settings/floc'

View File

@ -24,7 +24,7 @@
= c.body do
- enable_service_ping_link_url = help_page_path('user/admin_area/settings/usage_statistics', anchor: 'enable-or-disable-usage-statistics')
- enable_service_ping_link = '<a href="%{url}">'.html_safe % { url: enable_service_ping_link_url }
- generate_manually_link_url = help_page_path('administration/troubleshooting/gitlab_rails_cheat_sheet', anchor: 'generate-service-ping')
- generate_manually_link_url = help_page_path('development/service_ping/troubleshooting', anchor: 'generate-service-ping')
- generate_manually_link = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: generate_manually_link_url }
= html_escape(s_('%{enable_service_ping_link_start}Enable%{link_end} or %{generate_manually_link_start}generate%{link_end} Service Ping to preview and download service usage data payload.')) % { enable_service_ping_link_start: enable_service_ping_link, generate_manually_link_start: generate_manually_link, link_end: '</a>'.html_safe }

View File

@ -7,7 +7,7 @@
= gl_tab_link_to s_('Branches|Overview'), project_branches_path(@project), { item_active: @mode == 'overview', title: s_('Branches|Show overview of the branches') }
= gl_tab_link_to s_('Branches|Active'), project_branches_filtered_path(@project, state: 'active'), { title: s_('Branches|Show active branches') }
= gl_tab_link_to s_('Branches|Stale'), project_branches_filtered_path(@project, state: 'stale'), { title: s_('Branches|Show stale branches') }
= gl_tab_link_to s_('Branches|All'), project_branches_filtered_path(@project, state: 'all'), { item_active: !%w[overview active stale].include?(@mode), title: s_('Branches|Show all branches') }
= gl_tab_link_to s_('Branches|All'), project_branches_filtered_path(@project, state: 'all'), { item_active: %w[overview active stale].exclude?(@mode), title: s_('Branches|Show all branches') }
.nav-controls
#js-branches-sort-dropdown{ data: { project_branches_filtered_path: project_branches_path(@project, state: 'all'), sort_options: branches_sort_options_hash.to_json, mode: @mode } }

View File

@ -30,4 +30,4 @@
#js-pipeline-tabs{ data: js_pipeline_tabs_data(@project, @pipeline, @current_user) }
- else
= render "projects/pipelines/with_tabs", pipeline: @pipeline, stages: @stages, pipeline_has_errors: pipeline_has_errors
.js-pipeline-details-vue{ data: { metrics_path: namespace_project_ci_prometheus_metrics_histograms_path(namespace_id: @project.namespace, project_id: @project, format: :json), pipeline_project_path: @project.full_path, pipeline_iid: @pipeline.iid, graphql_resource_etag: graphql_etag_pipeline_path(@pipeline) } }
.js-pipeline-details-vue{ data: { metrics_path: namespace_project_ci_prometheus_metrics_histograms_path(namespace_id: @project.namespace, project_id: @project, format: :json), pipeline_project_path: @project.full_path, pipeline_iid: @pipeline.iid, graphql_resource_etag: graphql_etag_pipeline_path(@pipeline), pipeline_path: pipeline_path(@pipeline) } }

View File

@ -34,7 +34,7 @@
.title
= _('Milestone')
.filter-item
= dropdown_tag(_("Select milestone"), options: { title: _("Assign milestone"), toggle_class: "js-milestone-select js-extra-options js-filter-submit js-filter-bulk-update", filter: true, dropdown_class: "dropdown-menu-selectable dropdown-menu-milestone", placeholder: _("Search milestones"), data: { show_no: true, field_name: "update[milestone_id]", project_id: @project.id, use_id: true, default_label: _("Milestone") } })
.js-milestone-dropdown{ data: { full_path: @project.full_path, workspace_type: Namespaces::ProjectNamespace.sti_name.downcase } }
- if is_issue
= render_if_exists 'shared/issuable/iterations_dropdown', parent: @project.group
- if is_issue

View File

@ -0,0 +1,8 @@
---
name: apollo_boards
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/102719
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/381210
milestone: '15.6'
type: development
group: group::product planning
default_enabled: false

View File

@ -0,0 +1,8 @@
---
name: enable_new_sentry_clientside_integration
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/102650
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/344832
milestone: '15.6'
type: development
group: group::runner
default_enabled: false

View File

@ -0,0 +1,8 @@
---
name: enable_old_sentry_clientside_integration
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/102650
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/344832
milestone: '15.6'
type: development
group: group::runner
default_enabled: true

View File

@ -0,0 +1,8 @@
---
name: search_index_curation
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/98809
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/375274
milestone: '15.6'
type: development
group: group::global search
default_enabled: false

View File

@ -45,6 +45,8 @@ metadata:
description: Operations related to project hooks
- name: project_import_bitbucket
description: Operations related to import BitBucket projects
- name: project_import_github
description: Operations related to import GitHub projects
- name: release_links
description: Operations related to release assets (links)
- name: releases

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
class RemoveUsersForeignKeyToProjects < Gitlab::Database::Migration[2.0]
disable_ddl_transaction!
def up
with_lock_retries do
remove_foreign_key_if_exists :projects, column: :creator_id
end
end
def down
add_concurrent_foreign_key :projects, :users, column: :creator_id, on_delete: :nullify, validate: false
end
end

View File

@ -0,0 +1 @@
7ddb85c1acfd3fbeddbe96857d329ad09cd21210e6765ff36d4b9f516a7c10be

View File

@ -32626,9 +32626,6 @@ ALTER TABLE ONLY service_desk_settings
ALTER TABLE ONLY design_management_designs_versions
ADD CONSTRAINT fk_03c671965c FOREIGN KEY (design_id) REFERENCES design_management_designs(id) ON DELETE CASCADE;
ALTER TABLE ONLY projects
ADD CONSTRAINT fk_03ec10b0d3 FOREIGN KEY (creator_id) REFERENCES users(id) ON DELETE SET NULL NOT VALID;
ALTER TABLE ONLY issues
ADD CONSTRAINT fk_05f1e72feb FOREIGN KEY (author_id) REFERENCES users(id) ON DELETE SET NULL;

View File

@ -232,12 +232,9 @@ who are aware of the risks.
- [Troubleshooting Kubernetes](https://docs.gitlab.com/charts/troubleshooting/kubernetes_cheat_sheet.html)
- [Troubleshooting PostgreSQL](troubleshooting/postgresql.md)
- [Guide to test environments](troubleshooting/test_environments.md) (for Support Engineers)
- [GitLab Rails console commands](troubleshooting/gitlab_rails_cheat_sheet.md) (for Support Engineers)
- [Troubleshooting SSL](troubleshooting/ssl.md)
- Related links:
- [GitLab Developer Documentation](../development/index.md)
- [Repairing and recovering broken Git repositories](https://git.seveas.net/repairing-and-recovering-broken-git-repositories.html)
- [Testing with OpenSSL](https://www.feistyduck.com/library/openssl-cookbook/online/testing-with-openssl/index.html)
- [`strace` zine](https://wizardzines.com/zines/strace/)
- GitLab.com-specific resources:
- [Example group SAML and SCIM configurations](../user/group/saml_sso/example_saml_config.md)

View File

@ -1,91 +1,11 @@
---
stage: Systems
group: Distribution
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
redirect_to: 'index.md'
remove_date: '2023-02-01'
---
# GitLab Rails Console Cheat Sheet **(FREE SELF)**
This document was moved to [another location](index.md).
This is the GitLab Support Team's collection of information regarding the GitLab Rails
console, for use while troubleshooting. It is listed here for transparency,
and for users with experience with these tools. If you are currently
having an issue with GitLab, it is highly recommended that you first check
our guide on [our Rails console](../operations/rails_console.md),
and your [support options](https://about.gitlab.com/support/), before attempting to use
this information.
WARNING:
Some of these scripts could be damaging if not run correctly,
or under the right conditions. We highly recommend running them under the
guidance of a Support Engineer, or running them in a test environment with a
backup of the instance ready to be restored, just in case.
WARNING:
As GitLab changes, changes to the code are inevitable,
and so some scripts may not work as they once used to. These are not kept
up-to-date as these scripts/commands were added as they were found/needed. As
mentioned above, we recommend running these scripts under the supervision of a
Support Engineer, who can also verify that they continue to work as they
should and, if needed, update the script for the latest version of GitLab.
## Mirrors
### Find mirrors with "bad decrypt" errors
This content has been converted to a Rake task, see [verify database values can be decrypted using the current secrets](../raketasks/check.md#verify-database-values-can-be-decrypted-using-the-current-secrets).
### Transfer mirror users and tokens to a single service account
This content has been moved to [Troubleshooting Repository mirroring](../../user/project/repository/mirror/index.md#transfer-mirror-users-and-tokens-to-a-single-service-account-in-rails-console).
## Merge requests
## CI
This content has been moved to [Troubleshooting CI/CD](../../ci/troubleshooting.md).
## License
This content has been moved to [Activate GitLab EE with a license file or key](../../user/admin_area/license_file.md).
## Registry
### Registry Disk Space Usage by Project
Find this content in the [Container Registry troubleshooting documentation](../packages/container_registry.md#registry-disk-space-usage-by-project).
### Run the Cleanup policy now
Find this content in the [Container Registry troubleshooting documentation](../packages/container_registry.md#run-the-cleanup-policy-now).
## Sidekiq
This content has been moved to [Troubleshooting Sidekiq](../sidekiq/sidekiq_troubleshooting.md).
## Geo
### Reverify all uploads (or any SSF data type which is verified)
Moved to [Geo replication troubleshooting](../geo/replication/troubleshooting.md#reverify-all-uploads-or-any-ssf-data-type-which-is-verified).
### Artifacts
Moved to [Geo replication troubleshooting](../geo/replication/troubleshooting.md#find-failed-artifacts).
### Repository verification failures
Moved to [Geo replication troubleshooting](../geo/replication/troubleshooting.md#find-repository-verification-failures).
### Resync repositories
Moved to [Geo replication troubleshooting - Resync repository types except for project or project wiki repositories](../geo/replication/troubleshooting.md#repository-types-except-for-project-or-project-wiki-repositories).
Moved to [Geo replication troubleshooting - Resync project and project wiki repositories](../geo/replication/troubleshooting.md#resync-project-and-project-wiki-repositories).
### Blob types
Moved to [Geo replication troubleshooting](../geo/replication/troubleshooting.md#blob-types).
## Generate Service Ping
This content has been moved to [Service Ping Troubleshooting](../../development/service_ping/troubleshooting.md).
<!-- This redirect file can be deleted after 2023-02-01. -->
<!-- Redirects that point to other docs in the same project expire in three months. -->
<!-- Redirects that point to docs in a different project or site (for example, link is not relative and starts with `https:`) expire in one year. -->
<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->

View File

@ -9,19 +9,21 @@ info: To determine the technical writer assigned to the Stage/Group associated w
This page documents a collection of resources to help you troubleshoot a GitLab
installation.
This list is not necessarily comprehensive. If you don't find what you're looking
for in this list, you should search the documentation.
## Troubleshooting guides
- [SSL](ssl.md)
- [Geo](../geo/replication/troubleshooting.md)
- [GitLab Rails console cheat sheet](gitlab_rails_cheat_sheet.md)
- [Example group SAML and SCIM configurations](../../user/group/saml_sso/example_saml_config.md)
- [SAML](../../user/group/saml_sso/troubleshooting.md)
- [Kubernetes cheat sheet](https://docs.gitlab.com/charts/troubleshooting/kubernetes_cheat_sheet.html)
- [Linux cheat sheet](linux_cheat_sheet.md)
- [Parsing GitLab logs with `jq`](../logs/log_parsing.md)
- [Diagnostics tools](diagnostics_tools.md)
Some feature documentation pages also have a troubleshooting section at the end
that you can check for feature-specific help.
that you can check for feature-specific help, including helpful Rails commands.
If you need a testing environment to troubleshoot, see the
[apps for a testing environment](test_environments.md).

View File

@ -190,6 +190,30 @@ If you add a member to a group by using the [share a group with another group](.
- Remove the member from the shared group. You must be a group owner to do this.
- From the group's membership page, remove access from the entire shared group.
## Seat usage alerts
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/348481) in GitLab 15.2 [with a flag](../../administration/feature_flags.md) named `seat_flag_alerts`.
> - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/362041) in GitLab 15.4. Feature flag `seat_flag_alerts` removed.
If you have the Owner role of the top-level group, an alert notifies you
of your total seat usage.
The alert displays on group, subgroup, and project
pages, and only for top-level groups linked to subscriptions enrolled
in [quarterly subscription reconciliations](../quarterly_reconciliation.md).
After you dismiss the alert, it doesn't display until another seat is used.
The alert displays based on the following seat usage. You cannot configure the
amounts at which the alert displays.
| Seats in subscription | Seat usage |
|-----------------------|------------------------------------------------------------------------|
| 0-15 | One seat remaining in the subscription. |
| 16-25 | Two seats remaining in the subscription. |
| 26-99 | 10% of seats have been used. |
| 100-999 | 8% of seats have been used. |
| 1000+ | 5% of seats have been used |
## Upgrade your GitLab SaaS subscription tier
To upgrade your [GitLab tier](https://about.gitlab.com/pricing/):

View File

@ -187,6 +187,7 @@ module API
mount ::API::FreezePeriods
mount ::API::Keys
mount ::API::ImportBitbucketServer
mount ::API::ImportGithub
mount ::API::Metadata
mount ::API::MergeRequestDiffs
mount ::API::ProjectHooks
@ -261,7 +262,6 @@ module API
mount ::API::GroupVariables
mount ::API::Groups
mount ::API::HelmPackages
mount ::API::ImportGithub
mount ::API::Integrations
mount ::API::Integrations::JiraConnect::Subscriptions
mount ::API::Invitations

View File

@ -6,15 +6,18 @@ module API
include ::API::ProjectsRelationBuilder
include Gitlab::Utils::StrongMemoize
expose :default_branch_or_main, as: :default_branch, if: -> (project, options) { Ability.allowed?(options[:current_user], :download_code, project) }
expose :default_branch_or_main, documentation: { type: 'string', example: 'main' }, as: :default_branch, if: -> (project, options) { Ability.allowed?(options[:current_user], :download_code, project) }
# Avoids an N+1 query: https://github.com/mbleigh/acts-as-taggable-on/issues/91#issuecomment-168273770
expose :topic_names, as: :tag_list
expose :topic_names, as: :topics
expose :topic_names, as: :tag_list, documentation: { type: 'string', is_array: true, example: 'tag' }
expose :topic_names, as: :topics, documentation: { type: 'string', is_array: true, example: 'topic' }
expose :ssh_url_to_repo, :http_url_to_repo, :web_url, :readme_url
expose :ssh_url_to_repo, documentation: { type: 'string', example: 'git@gitlab.example.com:gitlab/gitlab.git' }
expose :http_url_to_repo, documentation: { type: 'string', example: 'https://gitlab.example.com/gitlab/gitlab.git' }
expose :web_url, documentation: { type: 'string', example: 'https://gitlab.example.com/gitlab/gitlab' }
expose :readme_url, documentation: { type: 'string', example: 'https://gitlab.example.com/gitlab/gitlab/blob/master/README.md' }
expose :license_url, if: :license do |project|
expose :license_url, if: :license, documentation: { type: 'string', example: 'https://gitlab.example.com/gitlab/gitlab/blob/master/LICENCE' } do |project|
license = project.repository.license_blob
if license
@ -26,13 +29,13 @@ module API
project.repository.license
end
expose :avatar_url do |project, options|
expose :avatar_url, documentation: { type: 'string', example: 'http://example.com/uploads/project/avatar/3/uploads/avatar.png' } do |project, options|
project.avatar_url(only_path: false)
end
expose :forks_count
expose :star_count
expose :last_activity_at
expose :forks_count, documentation: { type: 'integer', example: 1 }
expose :star_count, documentation: { type: 'integer', example: 1 }
expose :last_activity_at, documentation: { type: 'dateTime', example: '2013-09-30T13:46:02Z' }
expose :namespace, using: 'API::Entities::NamespaceBasic'
expose :custom_attributes, using: 'API::Entities::CustomAttribute', if: :with_custom_attributes

View File

@ -3,11 +3,11 @@
module API
module Entities
class BasicRepositoryStorageMove < Grape::Entity
expose :id
expose :created_at
expose :human_state_name, as: :state
expose :source_storage_name
expose :destination_storage_name
expose :id, documentation: { type: 'integer', example: 1 }
expose :created_at, documentation: { type: 'dateTime', example: '2020-05-07T04:27:17.234Z' }
expose :human_state_name, as: :state, documentation: { type: 'string', example: 'scheduled' }
expose :source_storage_name, documentation: { type: 'string', example: 'default' }
expose :destination_storage_name, documentation: { type: 'string', example: 'storage1' }
end
end
end

View File

@ -5,148 +5,148 @@ module API
class Project < BasicProjectDetails
include ::API::Helpers::RelatedResourcesHelpers
expose :container_registry_url, as: :container_registry_image_prefix, if: -> (_, _) { Gitlab.config.registry.enabled }
expose :container_registry_url, as: :container_registry_image_prefix, documentation: { type: 'string', example: 'registry.gitlab.example.com/gitlab/gitlab-client' }, if: -> (_, _) { Gitlab.config.registry.enabled }
expose :_links do
expose :self do |project|
expose :self, documentation: { type: 'string', example: 'https://gitlab.example.com/api/v4/projects/4' } do |project|
expose_url(api_v4_projects_path(id: project.id))
end
expose :issues, if: -> (project, options) { issues_available?(project, options) } do |project|
expose :issues, documentation: { type: 'string', example: 'https://gitlab.example.com/api/v4/projects/4/issues' }, if: -> (project, options) { issues_available?(project, options) } do |project|
expose_url(api_v4_projects_issues_path(id: project.id))
end
expose :merge_requests, if: -> (project, options) { mrs_available?(project, options) } do |project|
expose :merge_requests, documentation: { type: 'string', example: 'https://gitlab.example.com/api/v4/projects/4/merge_requests' }, if: -> (project, options) { mrs_available?(project, options) } do |project|
expose_url(api_v4_projects_merge_requests_path(id: project.id))
end
expose :repo_branches do |project|
expose :repo_branches, documentation: { type: 'string', example: 'https://gitlab.example.com/api/v4/projects/4/repository/branches' } do |project|
expose_url(api_v4_projects_repository_branches_path(id: project.id))
end
expose :labels do |project|
expose :labels, documentation: { type: 'string', example: 'https://gitlab.example.com/api/v4/projects/4/labels' } do |project|
expose_url(api_v4_projects_labels_path(id: project.id))
end
expose :events do |project|
expose :events, documentation: { type: 'string', example: 'https://gitlab.example.com/api/v4/projects/4/events' } do |project|
expose_url(api_v4_projects_events_path(id: project.id))
end
expose :members do |project|
expose :members, documentation: { type: 'string', example: 'https://gitlab.example.com/api/v4/projects/4/members' } do |project|
expose_url(api_v4_projects_members_path(id: project.id))
end
expose :cluster_agents do |project|
expose :cluster_agents, documentation: { type: 'string', example: 'https://gitlab.example.com/api/v4/projects/4/cluster_agents' } do |project|
expose_url(api_v4_projects_cluster_agents_path(id: project.id))
end
end
expose :packages_enabled
expose :empty_repo?, as: :empty_repo
expose :archived?, as: :archived
expose :visibility
expose :packages_enabled, documentation: { type: 'boolean' }
expose :empty_repo?, as: :empty_repo, documentation: { type: 'boolean' }
expose :archived?, as: :archived, documentation: { type: 'boolean' }
expose :visibility, documentation: { type: 'string', example: 'public' }
expose :owner, using: Entities::UserBasic, unless: ->(project, options) { project.group }
expose :resolve_outdated_diff_discussions
expose :resolve_outdated_diff_discussions, documentation: { type: 'boolean' }
expose :container_expiration_policy,
using: Entities::ContainerExpirationPolicy,
if: -> (project, _) { project.container_expiration_policy }
# Expose old field names with the new permissions methods to keep API compatible
# TODO: remove in API v5, replaced by *_access_level
expose(:issues_enabled) { |project, options| project.feature_available?(:issues, options[:current_user]) }
expose(:merge_requests_enabled) { |project, options| project.feature_available?(:merge_requests, options[:current_user]) }
expose(:wiki_enabled) { |project, options| project.feature_available?(:wiki, options[:current_user]) }
expose(:jobs_enabled) { |project, options| project.feature_available?(:builds, options[:current_user]) }
expose(:snippets_enabled) { |project, options| project.feature_available?(:snippets, options[:current_user]) }
expose(:container_registry_enabled) { |project, options| project.feature_available?(:container_registry, options[:current_user]) }
expose :service_desk_enabled
expose :service_desk_address, if: -> (project, options) do
expose(:issues_enabled, documentation: { type: 'boolean' }) { |project, options| project.feature_available?(:issues, options[:current_user]) }
expose(:merge_requests_enabled, documentation: { type: 'boolean' }) { |project, options| project.feature_available?(:merge_requests, options[:current_user]) }
expose(:wiki_enabled, documentation: { type: 'boolean' }) { |project, options| project.feature_available?(:wiki, options[:current_user]) }
expose(:jobs_enabled, documentation: { type: 'boolean' }) { |project, options| project.feature_available?(:builds, options[:current_user]) }
expose(:snippets_enabled, documentation: { type: 'boolean' }) { |project, options| project.feature_available?(:snippets, options[:current_user]) }
expose(:container_registry_enabled, documentation: { type: 'boolean' }) { |project, options| project.feature_available?(:container_registry, options[:current_user]) }
expose :service_desk_enabled, documentation: { type: 'boolean' }
expose :service_desk_address, documentation: { type: 'string', example: 'address@example.com' }, if: -> (project, options) do
Ability.allowed?(options[:current_user], :admin_issue, project)
end
expose(:can_create_merge_request_in) do |project, options|
expose(:can_create_merge_request_in, documentation: { type: 'boolean' }) do |project, options|
Ability.allowed?(options[:current_user], :create_merge_request_in, project)
end
expose(:issues_access_level) { |project, options| project_feature_string_access_level(project, :issues) }
expose(:repository_access_level) { |project, options| project_feature_string_access_level(project, :repository) }
expose(:merge_requests_access_level) { |project, options| project_feature_string_access_level(project, :merge_requests) }
expose(:forking_access_level) { |project, options| project_feature_string_access_level(project, :forking) }
expose(:wiki_access_level) { |project, options| project_feature_string_access_level(project, :wiki) }
expose(:builds_access_level) { |project, options| project_feature_string_access_level(project, :builds) }
expose(:snippets_access_level) { |project, options| project_feature_string_access_level(project, :snippets) }
expose(:pages_access_level) { |project, options| project_feature_string_access_level(project, :pages) }
expose(:operations_access_level) { |project, options| project_feature_string_access_level(project, :operations) }
expose(:analytics_access_level) { |project, options| project_feature_string_access_level(project, :analytics) }
expose(:container_registry_access_level) { |project, options| project_feature_string_access_level(project, :container_registry) }
expose(:security_and_compliance_access_level) { |project, options| project_feature_string_access_level(project, :security_and_compliance) }
expose(:releases_access_level) { |project, options| project_feature_string_access_level(project, :releases) }
expose(:issues_access_level, documentation: { type: 'string', example: 'enabled' }) { |project, options| project_feature_string_access_level(project, :issues) }
expose(:repository_access_level, documentation: { type: 'string', example: 'enabled' }) { |project, options| project_feature_string_access_level(project, :repository) }
expose(:merge_requests_access_level, documentation: { type: 'string', example: 'enabled' }) { |project, options| project_feature_string_access_level(project, :merge_requests) }
expose(:forking_access_level, documentation: { type: 'string', example: 'enabled' }) { |project, options| project_feature_string_access_level(project, :forking) }
expose(:wiki_access_level, documentation: { type: 'string', example: 'enabled' }) { |project, options| project_feature_string_access_level(project, :wiki) }
expose(:builds_access_level, documentation: { type: 'string', example: 'enabled' }) { |project, options| project_feature_string_access_level(project, :builds) }
expose(:snippets_access_level, documentation: { type: 'string', example: 'enabled' }) { |project, options| project_feature_string_access_level(project, :snippets) }
expose(:pages_access_level, documentation: { type: 'string', example: 'enabled' }) { |project, options| project_feature_string_access_level(project, :pages) }
expose(:operations_access_level, documentation: { type: 'string', example: 'enabled' }) { |project, options| project_feature_string_access_level(project, :operations) }
expose(:analytics_access_level, documentation: { type: 'string', example: 'enabled' }) { |project, options| project_feature_string_access_level(project, :analytics) }
expose(:container_registry_access_level, documentation: { type: 'string', example: 'enabled' }) { |project, options| project_feature_string_access_level(project, :container_registry) }
expose(:security_and_compliance_access_level, documentation: { type: 'string', example: 'enabled' }) { |project, options| project_feature_string_access_level(project, :security_and_compliance) }
expose(:releases_access_level, documentation: { type: 'string', example: 'enabled' }) { |project, options| project_feature_string_access_level(project, :releases) }
expose :emails_disabled
expose :shared_runners_enabled
expose :lfs_enabled?, as: :lfs_enabled
expose :creator_id
expose :emails_disabled, documentation: { type: 'boolean' }
expose :shared_runners_enabled, documentation: { type: 'boolean' }
expose :lfs_enabled?, as: :lfs_enabled, documentation: { type: 'boolean' }
expose :creator_id, documentation: { type: 'integer', example: 1 }
expose :forked_from_project, using: Entities::BasicProjectDetails, if: ->(project, options) do
project.forked? && Ability.allowed?(options[:current_user], :read_project, project.forked_from_project)
end
expose :mr_default_target_self, if: -> (project) { project.forked? }
expose :mr_default_target_self, if: -> (project) { project.forked? }, documentation: { type: 'boolean' }
expose :import_url, if: -> (project, options) { Ability.allowed?(options[:current_user], :admin_project, project) } do |project|
expose :import_url, documentation: { type: 'string', example: 'https://gitlab.com/gitlab/gitlab.git' }, if: -> (project, options) { Ability.allowed?(options[:current_user], :admin_project, project) } do |project|
project[:import_url]
end
expose :import_type, if: -> (project, options) { Ability.allowed?(options[:current_user], :admin_project, project) }
expose :import_status
expose :import_error, if: lambda { |_project, options| options[:user_can_admin_project] } do |project|
expose :import_type, documentation: { type: 'string', example: 'git' }, if: -> (project, options) { Ability.allowed?(options[:current_user], :admin_project, project) }
expose :import_status, documentation: { type: 'string', example: 'none' }
expose :import_error, documentation: { type: 'string', example: 'Import error' }, if: lambda { |_project, options| options[:user_can_admin_project] } do |project|
project.import_state&.last_error
end
expose :open_issues_count, if: lambda { |project, options| project.feature_available?(:issues, options[:current_user]) }
expose :runners_token, if: lambda { |_project, options| options[:user_can_admin_project] }
expose :ci_default_git_depth
expose :ci_forward_deployment_enabled
expose(:ci_job_token_scope_enabled) { |p, _| p.ci_outbound_job_token_scope_enabled? }
expose :ci_separated_caches
expose :ci_opt_in_jwt
expose :ci_allow_fork_pipelines_to_run_in_parent_project
expose :public_builds, as: :public_jobs
expose :build_git_strategy, if: lambda { |project, options| options[:user_can_admin_project] } do |project, options|
expose :open_issues_count, documentation: { type: 'integer', example: 1 }, if: lambda { |project, options| project.feature_available?(:issues, options[:current_user]) }
expose :runners_token, documentation: { type: 'string', example: 'b8547b1dc37721d05889db52fa2f02' }, if: lambda { |_project, options| options[:user_can_admin_project] }
expose :ci_default_git_depth, documentation: { type: 'integer', example: 20 }
expose :ci_forward_deployment_enabled, documentation: { type: 'boolean' }
expose(:ci_job_token_scope_enabled, documentation: { type: 'boolean' }) { |p, _| p.ci_outbound_job_token_scope_enabled? }
expose :ci_separated_caches, documentation: { type: 'boolean' }
expose :ci_opt_in_jwt, documentation: { type: 'boolean' }
expose :ci_allow_fork_pipelines_to_run_in_parent_project, documentation: { type: 'boolean' }
expose :public_builds, as: :public_jobs, documentation: { type: 'boolean' }
expose :build_git_strategy, documentation: { type: 'string', example: 'fetch' }, if: lambda { |project, options| options[:user_can_admin_project] } do |project, options|
project.build_allow_git_fetch ? 'fetch' : 'clone'
end
expose :build_timeout
expose :auto_cancel_pending_pipelines
expose :ci_config_path, if: -> (project, options) { Ability.allowed?(options[:current_user], :download_code, project) }
expose :shared_with_groups do |project, options|
expose :build_timeout, documentation: { type: 'integer', example: 3600 }
expose :auto_cancel_pending_pipelines, documentation: { type: 'string', example: 'enabled' }
expose :ci_config_path, documentation: { type: 'string', example: '' }, if: -> (project, options) { Ability.allowed?(options[:current_user], :download_code, project) }
expose :shared_with_groups, documentation: { is_array: true } do |project, options|
user = options[:current_user]
SharedGroupWithProject.represent(project.visible_group_links(for_user: user), options)
end
expose :only_allow_merge_if_pipeline_succeeds
expose :allow_merge_on_skipped_pipeline
expose :restrict_user_defined_variables
expose :request_access_enabled
expose :only_allow_merge_if_all_discussions_are_resolved
expose :remove_source_branch_after_merge
expose :printing_merge_request_link_enabled
expose :merge_method
expose :squash_option
expose :enforce_auth_checks_on_uploads
expose :suggestion_commit_message
expose :merge_commit_template
expose :squash_commit_template
expose :issue_branch_template
expose :only_allow_merge_if_pipeline_succeeds, documentation: { type: 'boolean' }
expose :allow_merge_on_skipped_pipeline, documentation: { type: 'boolean' }
expose :restrict_user_defined_variables, documentation: { type: 'boolean' }
expose :request_access_enabled, documentation: { type: 'boolean' }
expose :only_allow_merge_if_all_discussions_are_resolved, documentation: { type: 'boolean' }
expose :remove_source_branch_after_merge, documentation: { type: 'boolean' }
expose :printing_merge_request_link_enabled, documentation: { type: 'boolean' }
expose :merge_method, documentation: { type: 'string', example: 'merge' }
expose :squash_option, documentation: { type: 'string', example: 'default_off' }
expose :enforce_auth_checks_on_uploads, documentation: { type: 'boolean' }
expose :suggestion_commit_message, documentation: { type: 'string', example: 'Suggestion message' }
expose :merge_commit_template, documentation: { type: 'string', example: '%(title)' }
expose :squash_commit_template, documentation: { type: 'string', example: '%(source_branch)' }
expose :issue_branch_template, documentation: { type: 'string', example: '%(title)' }
expose :statistics, using: 'API::Entities::ProjectStatistics', if: -> (project, options) {
options[:statistics] && Ability.allowed?(options[:current_user], :read_statistics, project)
}
expose :auto_devops_enabled?, as: :auto_devops_enabled
expose :auto_devops_deploy_strategy do |project, options|
expose :auto_devops_enabled?, as: :auto_devops_enabled, documentation: { type: 'boolean' }
expose :auto_devops_deploy_strategy, documentation: { type: 'string', example: 'continuous' } do |project, options|
project.auto_devops.nil? ? 'continuous' : project.auto_devops.deploy_strategy
end
expose :autoclose_referenced_issues
expose :repository_storage, if: ->(project, options) {
expose :autoclose_referenced_issues, documentation: { type: 'boolean' }
expose :repository_storage, documentation: { type: 'string', example: 'default' }, if: ->(project, options) {
Ability.allowed?(options[:current_user], :change_repository_storage, project)
}
expose :keep_latest_artifacts_available?, as: :keep_latest_artifact
expose :runner_token_expiration_interval
expose :keep_latest_artifacts_available?, as: :keep_latest_artifact, documentation: { type: 'boolean' }
expose :runner_token_expiration_interval, documentation: { type: 'integer', example: 3600 }
# rubocop: disable CodeReuse/ActiveRecord
def self.preload_resource(project)

View File

@ -3,7 +3,11 @@
module API
module Entities
class ProjectGroupLink < Grape::Entity
expose :id, :project_id, :group_id, :group_access, :expires_at
expose :id, documentation: { type: 'integer', example: 1 }
expose :project_id, documentation: { type: 'integer', example: 1 }
expose :group_id, documentation: { type: 'integer', example: 1 }
expose :group_access, documentation: { type: 'integer', example: 10 }
expose :expires_at, documentation: { type: 'date', example: '2016-09-26' }
end
end
end

View File

@ -3,10 +3,13 @@
module API
module Entities
class ProjectIdentity < Grape::Entity
expose :id, :description
expose :name, :name_with_namespace
expose :path, :path_with_namespace
expose :created_at
expose :id, documentation: { type: 'integer', example: 1 }
expose :description, documentation: { type: 'string', example: 'desc' }
expose :name, documentation: { type: 'string', example: 'project1' }
expose :name_with_namespace, documentation: { type: 'string', example: 'John Doe / project1' }
expose :path, documentation: { type: 'string', example: 'project1' }
expose :path_with_namespace, documentation: { type: 'string', example: 'namespace1/project1' }
expose :created_at, documentation: { type: 'dateTime', example: '2020-05-07T04:27:17.016Z' }
end
end
end

View File

@ -5,12 +5,16 @@ module API
class ProjectRepositoryStorage < Grape::Entity
include Gitlab::Routing
expose :disk_path do |project|
expose :disk_path, documentation: {
type: 'string',
example: '@hashed/6b/86/6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b'
} do |project|
project.repository.disk_path
end
expose :id, as: :project_id
expose :repository_storage, :created_at
expose :id, as: :project_id, documentation: { type: 'integer', example: 1 }
expose :repository_storage, documentation: { type: 'string', example: 'default' }
expose :created_at, documentation: { type: 'dateTime', example: '2012-10-12T17:04:47Z' }
end
end
end

View File

@ -37,7 +37,15 @@ module API
desc 'Import a GitHub project' do
detail 'This feature was introduced in GitLab 11.3.4.'
success ::ProjectEntity
success code: 201, model: ::ProjectEntity
failure [
{ code: 400, message: 'Bad request' },
{ code: 401, message: 'Unauthorized' },
{ code: 403, message: 'Forbidden' },
{ code: 422, message: 'Unprocessable entity' },
{ code: 503, message: 'Service unavailable' }
]
tags ['project_import_github']
end
params do
requires :personal_access_token, type: String, desc: 'GitHub personal access token'
@ -58,6 +66,18 @@ module API
end
end
desc 'Cancel GitHub project import' do
detail 'This feature was introduced in GitLab 15.5'
success code: 200, model: ProjectImportEntity
failure [
{ code: 400, message: 'Bad request' },
{ code: 401, message: 'Unauthorized' },
{ code: 403, message: 'Forbidden' },
{ code: 404, message: 'Not found' },
{ code: 503, message: 'Service unavailable' }
]
tags ['project_import_github']
end
params do
requires :project_id, type: Integer, desc: 'ID of importing project to be canceled'
end

View File

@ -147,6 +147,12 @@ module Gitlab
'in the body of your migration class'
end
if !options.delete(:allow_partition) && partition?(table_name)
raise ArgumentError, 'add_concurrent_index can not be used on a partitioned ' \
'table. Please use add_concurrent_partitioned_index on the partitioned table ' \
'as we need to create indexes on each partition and an index on the parent table'
end
options = options.merge({ algorithm: :concurrently })
if index_exists?(table_name, column_name, **options)
@ -1284,6 +1290,14 @@ into similar problems in the future (e.g. when new tables are created).
end
# rubocop:enable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
def partition?(table_name)
if view_exists?(:postgres_partitions)
Gitlab::Database::PostgresPartition.partition_exists?(table_name)
else
Gitlab::Database::PostgresPartition.legacy_partition_exists?(table_name)
end
end
private
def multiple_columns(columns, separator: ', ')

View File

@ -40,7 +40,7 @@ module Gitlab
partitioned_table.postgres_partitions.order(:name).each do |partition|
partition_index_name = generated_index_name(partition.identifier, options[:name])
partition_options = options.merge(name: partition_index_name)
partition_options = options.merge(name: partition_index_name, allow_partition: true)
add_concurrent_index(partition.identifier, column_names, partition_options)
end

View File

@ -19,6 +19,20 @@ module Gitlab
scope :for_parent_table, ->(name) { where("parent_identifier = concat(current_schema(), '.', ?)", name).order(:name) }
def self.partition_exists?(table_name)
where("identifier = concat(current_schema(), '.', ?)", table_name).exists?
end
def self.legacy_partition_exists?(table_name)
result = connection.select_value(<<~SQL)
SELECT true FROM pg_class
WHERE relname = '#{table_name}'
AND relispartition = true;
SQL
!!result
end
def to_s
name
end

View File

@ -17,11 +17,18 @@ module Gitlab
gon.markdown_surround_selection = current_user&.markdown_surround_selection
gon.markdown_automatic_lists = current_user&.markdown_automatic_lists
if Gitlab.config.sentry.enabled
# Support for Sentry setup via configuration will be removed in 16.0
# in favor of Gitlab::CurrentSettings.
if Feature.enabled?(:enable_old_sentry_clientside_integration) && Gitlab.config.sentry.enabled
gon.sentry_dsn = Gitlab.config.sentry.clientside_dsn
gon.sentry_environment = Gitlab.config.sentry.environment
end
if Feature.enabled?(:enable_new_sentry_clientside_integration) && Gitlab::CurrentSettings.sentry_enabled
gon.sentry_dsn = Gitlab::CurrentSettings.sentry_clientside_dsn
gon.sentry_environment = Gitlab::CurrentSettings.sentry_environment
end
gon.recaptcha_api_server_url = ::Recaptcha.configuration.api_server_url
gon.recaptcha_sitekey = Gitlab::CurrentSettings.recaptcha_site_key
gon.gitlab_url = Gitlab.config.gitlab.url

View File

@ -2734,15 +2734,27 @@ msgstr ""
msgid "AdminSettings|CI/CD limits"
msgstr ""
msgid "AdminSettings|Clickhouse URL"
msgstr ""
msgid "AdminSettings|Configure Let's Encrypt"
msgstr ""
msgid "AdminSettings|Configure limits on the number of repositories users can download in a given time."
msgstr ""
msgid "AdminSettings|Configure product analytics to track events within your project applications."
msgstr ""
msgid "AdminSettings|Configure when inactive projects should be automatically deleted. %{linkStart}What are inactive projects?%{linkEnd}"
msgstr ""
msgid "AdminSettings|Cube API URL"
msgstr ""
msgid "AdminSettings|Cube API key"
msgstr ""
msgid "AdminSettings|Delete inactive projects"
msgstr ""
@ -2791,6 +2803,9 @@ msgstr ""
msgid "AdminSettings|Enable pipeline suggestion banner"
msgstr ""
msgid "AdminSettings|Enable product analytics"
msgstr ""
msgid "AdminSettings|Enable shared runners for new projects"
msgstr ""
@ -2836,6 +2851,18 @@ msgstr ""
msgid "AdminSettings|Instance runners expiration"
msgstr ""
msgid "AdminSettings|Jitsu administrator email"
msgstr ""
msgid "AdminSettings|Jitsu administrator password"
msgstr ""
msgid "AdminSettings|Jitsu host"
msgstr ""
msgid "AdminSettings|Jitsu project ID"
msgstr ""
msgid "AdminSettings|Keep the latest artifacts for all jobs in the latest successful pipelines"
msgstr ""
@ -2974,9 +3001,18 @@ msgstr ""
msgid "AdminSettings|Size and domain settings for Pages static sites."
msgstr ""
msgid "AdminSettings|The ID of the project in Jitsu. The project contains all analytics instances."
msgstr ""
msgid "AdminSettings|The URL of your Cube instance."
msgstr ""
msgid "AdminSettings|The default domain to use for Auto Review Apps and Auto Deploy stages in all projects."
msgstr ""
msgid "AdminSettings|The host of your Jitsu instance."
msgstr ""
msgid "AdminSettings|The latest artifacts for all jobs in the most recent successful pipelines in each project are stored and do not expire."
msgstr ""
@ -3001,6 +3037,15 @@ msgstr ""
msgid "AdminSettings|Use AWS OpenSearch Service with IAM credentials"
msgstr ""
msgid "AdminSettings|Used to connect Jitsu to the Clickhouse instance."
msgstr ""
msgid "AdminSettings|Used to generate short-lived API access tokens."
msgstr ""
msgid "AdminSettings|Used to retrieve dashboard data from the Cube instance."
msgstr ""
msgid "AdminSettings|Users and groups must accept the invitation before they're added to a group or project."
msgstr ""
@ -5332,9 +5377,6 @@ msgstr ""
msgid "Assign labels"
msgstr ""
msgid "Assign milestone"
msgstr ""
msgid "Assign myself"
msgstr ""
@ -30890,6 +30932,9 @@ msgstr ""
msgid "Proceed"
msgstr ""
msgid "Product analytics"
msgstr ""
msgid "ProductAnalytics|Audience"
msgstr ""
@ -36144,10 +36189,10 @@ msgstr ""
msgid "SecurityOrchestration|%{agent} for %{namespaces}"
msgstr ""
msgid "SecurityOrchestration|%{branches} %{plural}"
msgid "SecurityOrchestration|%{branches} and %{lastBranch} branches"
msgstr ""
msgid "SecurityOrchestration|%{branches} and %{lastBranch} %{plural}"
msgid "SecurityOrchestration|%{branches} branch"
msgstr ""
msgid "SecurityOrchestration|%{scanners}"
@ -36504,10 +36549,10 @@ msgstr ""
msgid "SecurityOrchestration|the %{branches}"
msgstr ""
msgid "SecurityOrchestration|the %{namespaces} %{plural}"
msgid "SecurityOrchestration|the %{namespaces} and %{lastNamespace} namespaces"
msgstr ""
msgid "SecurityOrchestration|the %{namespaces} and %{lastNamespace} %{plural}"
msgid "SecurityOrchestration|the %{namespaces} namespace"
msgstr ""
msgid "SecurityOrchestration|vulnerabilities"
@ -48842,11 +48887,6 @@ msgstr ""
msgid "my-topic"
msgstr ""
msgid "namespace"
msgid_plural "namespaces"
msgstr[0] ""
msgstr[1] ""
msgid "needs to be between 10 minutes and 1 month"
msgstr ""

View File

@ -98,12 +98,17 @@ module QA
let(:mrs) { fetch_mrs(imported_project, target_api_client) }
let(:issues) { fetch_issues(imported_project, target_api_client) }
let(:import_failures) { imported_group.import_details.sum([]) { |details| details[:failures] } }
before do
destination_group.add_member(user, Resource::Members::AccessLevel::MAINTAINER)
end
# rubocop:disable RSpec/InstanceVariable
after do |example|
# Log failures for easier debugging
Runtime::Logger.error("Import failures: #{import_failures}") if example.exception && !import_failures.empty?
next unless defined?(@import_time)
# save data for comparison notification creation
@ -112,7 +117,7 @@ module QA
{
importer: :gitlab,
import_time: @import_time,
errors: imported_group.import_details.sum([]) { |details| details[:failures] },
errors: import_failures,
source: {
name: "GitLab Source",
project_name: source_project.path_with_namespace,
@ -154,7 +159,7 @@ module QA
end
# rubocop:enable RSpec/InstanceVariable
it "migrates large gitlab group via api", testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/358842' do
it "migrates large gitlab group via api", testcase: "https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/358842" do
start = Time.now
# trigger import and log imported group path
@ -165,7 +170,11 @@ module QA
# wait for import to finish and save import time
logger.info("== Waiting for import to be finished ==")
expect { imported_group.import_status }.to eventually_eq('finished').within(import_wait_duration)
expect { imported_group.import_status }.not_to eventually_eq("started").within(import_wait_duration)
# finished status actually means success, don't wait for finished status explicitly
# because test would wait full duration if returned status is "failed"
expect(imported_group.import_status).to eq("finished")
@import_time = Time.now - start
aggregate_failures do

View File

@ -20,23 +20,11 @@ RSpec.describe Projects::PipelinesController do
end
shared_examples 'the show page' do |param|
it 'redirects to pipeline path with param' do
it 'renders the show template' do
get param, params: { namespace_id: project.namespace, project_id: project, id: pipeline }
expect(response).to redirect_to(pipeline_path(pipeline, tab: param))
end
context 'when the FF pipeline_tabs_vue is disabled' do
before do
stub_feature_flags(pipeline_tabs_vue: false)
end
it 'renders the show template' do
get param, params: { namespace_id: project.namespace, project_id: project, id: pipeline }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to render_template :show
end
expect(response).to have_gitlab_http_status(:ok)
expect(response).to render_template :show
end
end
@ -710,37 +698,25 @@ RSpec.describe Projects::PipelinesController do
describe 'GET failures' do
let(:pipeline) { create(:ci_pipeline, project: project) }
context 'with ff `pipeline_tabs_vue` disabled' do
context 'with failed jobs' do
before do
stub_feature_flags(pipeline_tabs_vue: false)
create(:ci_build, :failed, pipeline: pipeline, name: 'hello')
end
context 'with failed jobs' do
before do
create(:ci_build, :failed, pipeline: pipeline, name: 'hello')
end
it 'shows the page' do
get :failures, params: { namespace_id: project.namespace, project_id: project, id: pipeline }
it 'shows the page' do
get :failures, params: { namespace_id: project.namespace, project_id: project, id: pipeline }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to render_template :show
end
end
context 'without failed jobs' do
it 'redirects to the main pipeline page' do
get :failures, params: { namespace_id: project.namespace, project_id: project, id: pipeline }
expect(response).to redirect_to(pipeline_path(pipeline))
end
expect(response).to have_gitlab_http_status(:ok)
expect(response).to render_template :show
end
end
it 'redirects to the pipeline page with `failures` query param' do
get :failures, params: { namespace_id: project.namespace, project_id: project, id: pipeline }
context 'without failed jobs' do
it 'redirects to the main pipeline page' do
get :failures, params: { namespace_id: project.namespace, project_id: project, id: pipeline }
expect(response).to redirect_to(pipeline_path(pipeline, tab: 'failures'))
expect(response).to redirect_to(pipeline_path(pipeline))
end
end
end

View File

@ -80,7 +80,7 @@ RSpec.describe 'Database schema' do
project_error_tracking_settings: %w[sentry_project_id],
project_group_links: %w[group_id],
project_statistics: %w[namespace_id],
projects: %w[ci_id mirror_user_id],
projects: %w[creator_id ci_id mirror_user_id],
redirect_routes: %w[source_id],
repository_languages: %w[programming_language_id],
routes: %w[source_id],

View File

@ -22,6 +22,7 @@ RSpec.describe 'Issue board filters', :js do
let(:filter_submit) { find('.gl-search-box-by-click-search-button') }
before do
stub_feature_flags(apollo_boards: false)
project.add_maintainer(user)
sign_in(user)

View File

@ -19,6 +19,7 @@ RSpec.describe 'Project issue boards', :js do
context 'signed in user' do
before do
stub_feature_flags(apollo_boards: false)
project.add_maintainer(user)
project.add_maintainer(user2)
@ -29,7 +30,7 @@ RSpec.describe 'Project issue boards', :js do
context 'no lists' do
before do
visit_project_board_path_without_query_limit(project, board)
visit_project_board(project, board)
end
it 'creates default lists' do
@ -73,7 +74,7 @@ RSpec.describe 'Project issue boards', :js do
let_it_be(:issue10) { create(:labeled_issue, project: project, title: 'issue +', description: 'A+ great issue', labels: [a_plus]) }
before do
visit_project_board_path_without_query_limit(project, board)
visit_project_board(project, board)
end
it 'shows description tooltip on list title', :quarantine do
@ -124,7 +125,7 @@ RSpec.describe 'Project issue boards', :js do
it 'infinite scrolls list' do
create_list(:labeled_issue, 30, project: project, labels: [planning])
visit_project_board_path_without_query_limit(project, board)
visit_project_board(project, board)
page.within(find('.board:nth-child(2)')) do
expect(page.find('.board-header')).to have_content('38')
@ -203,7 +204,7 @@ RSpec.describe 'Project issue boards', :js do
expect(find('.board:nth-child(3) [data-testid="board-list-header"]')).to have_content(planning.title)
# Make sure list positions are preserved after a reload
visit_project_board_path_without_query_limit(project, board)
visit_project_board(project, board)
expect(find('.board:nth-child(2) [data-testid="board-list-header"]')).to have_content(development.title)
expect(find('.board:nth-child(3) [data-testid="board-list-header"]')).to have_content(planning.title)
@ -215,15 +216,19 @@ RSpec.describe 'Project issue boards', :js do
let_it_be(:list2) { create(:list, board: board, label: development, position: 1) }
it 'changes position of list' do
visit_project_board_path_without_query_limit(project, board)
inspect_requests(inject_headers: { 'X-GITLAB-DISABLE-SQL-QUERY-LIMIT' => 'https://gitlab.com/gitlab-org/gitlab/-/issues/323426' }) do
visit_project_board(project, board)
end
drag(list_from_index: 0, list_to_index: 1, selector: '.board-header')
expect(find('.board:nth-child(1) [data-testid="board-list-header"]')).to have_content(development.title)
expect(find('.board:nth-child(2) [data-testid="board-list-header"]')).to have_content(planning.title)
# Make sure list positions are preserved after a reload
visit_project_board_path_without_query_limit(project, board)
inspect_requests(inject_headers: { 'X-GITLAB-DISABLE-SQL-QUERY-LIMIT' => 'https://gitlab.com/gitlab-org/gitlab/-/issues/323426' }) do
# Make sure list positions are preserved after a reload
visit_project_board(project, board)
end
expect(find('.board:nth-child(1) [data-testid="board-list-header"]')).to have_content(development.title)
expect(find('.board:nth-child(2) [data-testid="board-list-header"]')).to have_content(planning.title)
@ -234,7 +239,9 @@ RSpec.describe 'Project issue boards', :js do
selector = '.board:not(.is-ghost) .board-header'
expect(page).to have_selector(selector, text: development.title, count: 1)
drag(list_from_index: 2, list_to_index: 1, selector: '.board-header', perform_drop: false)
inspect_requests(inject_headers: { 'X-GITLAB-DISABLE-SQL-QUERY-LIMIT' => 'https://gitlab.com/gitlab-org/gitlab/-/issues/323426' }) do
drag(list_from_index: 2, list_to_index: 1, selector: '.board-header', perform_drop: false)
end
expect(page).to have_selector(selector, text: development.title, count: 1)
end
@ -492,7 +499,7 @@ RSpec.describe 'Project issue boards', :js do
context 'keyboard shortcuts' do
before do
visit_project_board_path_without_query_limit(project, board)
visit_project_board(project, board)
wait_for_requests
end
@ -505,6 +512,7 @@ RSpec.describe 'Project issue boards', :js do
context 'signed out user' do
before do
stub_feature_flags(apollo_boards: false)
visit project_board_path(project, board)
wait_for_requests
end
@ -526,6 +534,7 @@ RSpec.describe 'Project issue boards', :js do
let_it_be(:user_guest) { create(:user) }
before do
stub_feature_flags(apollo_boards: false)
project.add_guest(user_guest)
sign_in(user_guest)
visit project_board_path(project, board)
@ -587,11 +596,9 @@ RSpec.describe 'Project issue boards', :js do
end
end
def visit_project_board_path_without_query_limit(project, board)
inspect_requests(inject_headers: { 'X-GITLAB-DISABLE-SQL-QUERY-LIMIT' => 'https://gitlab.com/gitlab-org/gitlab/-/issues/323426' }) do
visit project_board_path(project, board)
def visit_project_board(project, board)
visit project_board_path(project, board)
wait_for_requests
end
wait_for_requests
end
end

View File

@ -15,6 +15,7 @@ RSpec.describe 'Issue Boards', :js do
let!(:issue3) { create(:labeled_issue, project: project, title: 'testing 3', labels: [label], relative_position: 1) }
before do
stub_feature_flags(apollo_boards: false)
project.add_maintainer(user)
sign_in(user)

View File

@ -20,6 +20,7 @@ RSpec.describe 'Project issue boards sidebar labels', :js do
let(:card) { find('.board:nth-child(2)').first('.board-card') }
before do
stub_feature_flags(apollo_boards: false)
project.add_maintainer(user)
sign_in(user)

View File

@ -15,6 +15,7 @@ RSpec.describe 'Project issue boards sidebar', :js do
let_it_be(:issue, reload: true) { create(:issue, project: project, relative_position: 1) }
before do
stub_feature_flags(apollo_boards: false)
project.add_maintainer(user)
sign_in(user)

View File

@ -31,6 +31,7 @@ RSpec.describe 'User adds lists', :js do
with_them do
before do
stub_feature_flags(apollo_boards: false)
sign_in(user)
set_cookie('sidebar_collapsed', 'true')

View File

@ -44,6 +44,7 @@ RSpec.describe 'User visits issue boards', :js do
with_them do
before do
stub_feature_flags(apollo_boards: false)
visit board_path
wait_for_requests
@ -59,6 +60,7 @@ RSpec.describe 'User visits issue boards', :js do
end
context "project boards" do
stub_feature_flags(apollo_boards: false)
let_it_be(:board) { create_default(:board, project: project) }
let_it_be(:backlog_list) { create_default(:backlog_list, board: board) }
@ -68,6 +70,7 @@ RSpec.describe 'User visits issue boards', :js do
end
context "group boards" do
stub_feature_flags(apollo_boards: false)
let_it_be(:board) { create_default(:board, group: group) }
let_it_be(:backlog_list) { create_default(:backlog_list, board: board) }

View File

@ -19,6 +19,7 @@ RSpec.describe 'Group Issue Boards', :js do
let(:card) { find('.board:nth-child(1)').first('.board-card') }
before do
stub_feature_flags(apollo_boards: false)
sign_in(user)
visit group_board_path(group, board)

View File

@ -4,12 +4,16 @@ require "spec_helper"
RSpec.describe "User views incident" do
let_it_be(:project) { create(:project_empty_repo, :public) }
let_it_be(:user) { create(:user) }
let_it_be(:incident) { create(:incident, project: project, description: "# Description header\n\n**Lorem** _ipsum_ dolor sit [amet](https://example.com)", author: user) }
let_it_be(:note) { create(:note, noteable: incident, project: project, author: user) }
let_it_be(:guest) { create(:user) }
let_it_be(:developer) { create(:user) }
let_it_be(:user) { developer }
let(:author) { developer }
let(:description) { "# Description header\n\n**Lorem** _ipsum_ dolor sit [amet](https://example.com)" }
let(:incident) { create(:incident, project: project, description: description, author: author) }
before_all do
project.add_developer(user)
project.add_developer(developer)
project.add_guest(guest)
end
before do
@ -18,57 +22,61 @@ RSpec.describe "User views incident" do
visit(project_issues_incident_path(project, incident))
end
it { expect(page).to have_header_with_correct_id_and_link(1, "Description header", "description-header") }
specify do
expect(page).to have_header_with_correct_id_and_link(1, 'Description header', 'description-header')
end
it_behaves_like 'page meta description', ' Description header Lorem ipsum dolor sit amet'
describe 'user actions' do
it 'shows the merge request and incident actions', :js, :aggregate_failures do
expected_href = new_project_issue_path(project,
issuable_template: 'incident',
issue: { issue_type: 'incident' },
add_related_issue: incident.iid)
click_button 'Incident actions'
expect(page).to have_link('New related incident', href: new_project_issue_path(project, { issuable_template: 'incident', issue: { issue_type: 'incident' }, add_related_issue: incident.iid }))
expect(page).to have_link('New related incident', href: expected_href)
expect(page).to have_button('Create merge request')
expect(page).to have_button('Close incident')
end
context 'when user is a guest' do
before do
project.add_guest(user)
context 'when user is guest' do
let(:user) { guest }
login_as(user)
context 'and author' do
let(:author) { guest }
visit(project_issues_incident_path(project, incident))
it 'does not show the incident actions', :js do
expect(page).not_to have_button('Incident actions')
end
end
it 'does not show the incident actions', :js, :aggregate_failures do
expect(page).not_to have_button('Incident actions')
context 'and not author' do
it 'shows incident actions', :js do
click_button 'Incident actions'
expect(page).to have_link 'Report abuse'
end
end
end
end
context 'when the project is archived' do
before do
before_all do
project.update!(archived: true)
visit(project_issues_incident_path(project, incident))
end
it 'hides the merge request and incident actions', :aggregate_failures do
expect(page).not_to have_link('New incident')
expect(page).not_to have_button('Create merge request')
expect(page).not_to have_link('Close incident')
it 'does not show the incident actions', :js do
expect(page).not_to have_button('Incident actions')
end
end
describe 'user status' do
subject { visit(project_issues_incident_path(project, incident)) }
context 'when showing status of the author of the incident' do
it_behaves_like 'showing user status' do
let(:user_with_status) { user }
end
end
subject { visit(project_issues_incident_path(project, incident)) }
context 'when showing status of a user who commented on an incident', :js do
it_behaves_like 'showing user status' do
let(:user_with_status) { user }
end

View File

@ -417,7 +417,7 @@ RSpec.describe 'Issues > Labels bulk assignment' do
click_button 'Select milestone'
wait_for_requests
items.map do |item|
click_link item
click_button item
end
end

View File

@ -80,7 +80,7 @@ RSpec.describe 'Multiple issue updating from issues#index', :js do
click_button 'Edit issues'
check 'Select all'
click_button 'Select milestone'
click_link milestone.title
click_button milestone.title
click_update_issues_button
expect(page.find('.issue')).to have_content milestone.title
@ -97,7 +97,7 @@ RSpec.describe 'Multiple issue updating from issues#index', :js do
click_button 'Edit issues'
check 'Select all'
click_button 'Select milestone'
click_link 'No milestone'
click_button 'No milestone'
click_update_issues_button
expect(find('.issue:first-of-type')).not_to have_text milestone.title

View File

@ -130,7 +130,7 @@ RSpec.describe 'Merge requests > User mass updates', :js do
click_button 'Edit merge requests'
check 'Select all'
click_button 'Select milestone'
click_link text
click_button text
click_update_merge_requests_button
end

View File

@ -28,6 +28,7 @@ describe('BoardApp', () => {
store,
provide: {
...provide,
fullBoardId: 'gid://gitlab/Board/1',
},
});
};

View File

@ -1,15 +1,20 @@
import { GlAlert } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import Draggable from 'vuedraggable';
import Vuex from 'vuex';
import waitForPromises from 'helpers/wait_for_promises';
import createMockApollo from 'helpers/mock_apollo_helper';
import EpicsSwimlanes from 'ee_component/boards/components/epics_swimlanes.vue';
import getters from 'ee_else_ce/boards/stores/getters';
import boardListsQuery from 'ee_else_ce/boards/graphql/board_lists.query.graphql';
import BoardColumn from '~/boards/components/board_column.vue';
import BoardContent from '~/boards/components/board_content.vue';
import BoardContentSidebar from '~/boards/components/board_content_sidebar.vue';
import { mockLists } from '../mock_data';
import { mockLists, boardListsQueryResponse } from '../mock_data';
Vue.use(VueApollo);
Vue.use(Vuex);
const actions = {
@ -18,6 +23,7 @@ const actions = {
describe('BoardContent', () => {
let wrapper;
let fakeApollo;
window.gon = {};
const defaultState = {
@ -35,19 +41,33 @@ describe('BoardContent', () => {
});
};
const createComponent = ({ state, props = {}, canAdminList = true } = {}) => {
const createComponent = ({
state,
props = {},
canAdminList = true,
isApolloBoard = false,
issuableType = 'issue',
boardListQueryHandler = jest.fn().mockResolvedValue(boardListsQueryResponse),
} = {}) => {
fakeApollo = createMockApollo([[boardListsQuery, boardListQueryHandler]]);
const store = createStore({
...defaultState,
...state,
});
wrapper = shallowMount(BoardContent, {
apolloProvider: fakeApollo,
propsData: {
lists: mockLists,
disabled: false,
boardId: 'gid://gitlab/Board/1',
...props,
},
provide: {
canAdminList,
boardType: 'group',
fullPath: 'gitlab-org/gitlab',
issuableType,
isApolloBoard,
},
store,
});
@ -78,6 +98,7 @@ describe('BoardContent', () => {
afterEach(() => {
wrapper.destroy();
fakeApollo = null;
});
describe('default', () => {
@ -112,7 +133,7 @@ describe('BoardContent', () => {
describe('when issuableType is not issue', () => {
beforeEach(() => {
createComponent({ state: { issuableType: 'foo' } });
createComponent({ issuableType: 'foo' });
});
it('does not render BoardContentSidebar', () => {
@ -139,4 +160,19 @@ describe('BoardContent', () => {
expect(wrapper.findComponent(Draggable).exists()).toBe(false);
});
});
describe('when Apollo boards FF is on', () => {
beforeEach(async () => {
createComponent({ isApolloBoard: true });
await waitForPromises();
});
it('renders a BoardColumn component per list', () => {
expect(wrapper.findAllComponents(BoardColumn)).toHaveLength(mockLists.length);
});
it('renders BoardContentSidebar', () => {
expect(wrapper.findComponent(BoardContentSidebar).exists()).toBe(true);
});
});
});

View File

@ -433,8 +433,11 @@ export const mockList = {
label: null,
assignee: null,
milestone: null,
iteration: null,
loading: false,
issuesCount: 1,
maxIssueCount: 0,
__typename: 'BoardList',
};
export const mockLabelList = {
@ -449,11 +452,15 @@ export const mockLabelList = {
color: '#F0AD4E',
textColor: '#FFFFFF',
description: null,
descriptionHtml: null,
},
assignee: null,
milestone: null,
iteration: null,
loading: false,
issuesCount: 0,
maxIssueCount: 0,
__typename: 'BoardList',
};
export const mockMilestoneList = {
@ -844,6 +851,22 @@ export const mockGroupLabelsResponse = {
},
};
export const boardListsQueryResponse = {
data: {
group: {
id: 'gid://gitlab/Group/1',
board: {
id: 'gid://gitlab/Board/1',
hideBacklogList: false,
lists: {
nodes: mockLists,
},
},
__typename: 'Group',
},
},
};
export const boardListQueryResponse = (issuesCount = 20) => ({
data: {
boardList: {

View File

@ -2,10 +2,6 @@ import { shallowMount } from '@vue/test-utils';
import { GlTab } from '@gitlab/ui';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import PipelineTabs from '~/pipelines/components/pipeline_tabs.vue';
import PipelineGraphWrapper from '~/pipelines/components/graph/graph_component_wrapper.vue';
import Dag from '~/pipelines/components/dag/dag.vue';
import JobsApp from '~/pipelines/components/jobs/jobs_app.vue';
import TestReports from '~/pipelines/components/test_reports/test_reports.vue';
describe('The Pipeline Tabs', () => {
let wrapper;
@ -16,12 +12,6 @@ describe('The Pipeline Tabs', () => {
const findPipelineTab = () => wrapper.findByTestId('pipeline-tab');
const findTestsTab = () => wrapper.findByTestId('tests-tab');
const findDagApp = () => wrapper.findComponent(Dag);
const findFailedJobsApp = () => wrapper.findComponent(JobsApp);
const findJobsApp = () => wrapper.findComponent(JobsApp);
const findPipelineApp = () => wrapper.findComponent(PipelineGraphWrapper);
const findTestsApp = () => wrapper.findComponent(TestReports);
const findFailedJobsBadge = () => wrapper.findByTestId('failed-builds-counter');
const findJobsBadge = () => wrapper.findByTestId('builds-counter');
const findTestsBadge = () => wrapper.findByTestId('tests-counter');
@ -43,6 +33,7 @@ describe('The Pipeline Tabs', () => {
},
stubs: {
GlTab,
RouterView: true,
},
}),
);
@ -54,17 +45,16 @@ describe('The Pipeline Tabs', () => {
describe('Tabs', () => {
it.each`
tabName | tabComponent | appComponent
${'Pipeline'} | ${findPipelineTab} | ${findPipelineApp}
${'Dag'} | ${findDagTab} | ${findDagApp}
${'Jobs'} | ${findJobsTab} | ${findJobsApp}
${'Failed Jobs'} | ${findFailedJobsTab} | ${findFailedJobsApp}
${'Tests'} | ${findTestsTab} | ${findTestsApp}
`('shows $tabName tab with its associated component', ({ appComponent, tabComponent }) => {
tabName | tabComponent
${'Pipeline'} | ${findPipelineTab}
${'Dag'} | ${findDagTab}
${'Jobs'} | ${findJobsTab}
${'Failed Jobs'} | ${findFailedJobsTab}
${'Tests'} | ${findTestsTab}
`('shows $tabName tab', ({ tabComponent }) => {
createComponent();
expect(tabComponent().exists()).toBe(true);
expect(appComponent().exists()).toBe(true);
});
describe('with no failed jobs', () => {

View File

@ -1,5 +1,5 @@
import { createJobsHash, generateJobNeedsDict, getPipelineDefaultTab } from '~/pipelines/utils';
import { TAB_QUERY_PARAM, validPipelineTabNames } from '~/pipelines/constants';
import { validPipelineTabNames } from '~/pipelines/constants';
describe('utils functions', () => {
const jobName1 = 'build_1';
@ -173,18 +173,25 @@ describe('utils functions', () => {
describe('getPipelineDefaultTab', () => {
const baseUrl = 'http://gitlab.com/user/multi-projects-small/-/pipelines/332/';
it('returns null if there was no `tab` params', () => {
it('returns null if there is only the base url', () => {
expect(getPipelineDefaultTab(baseUrl)).toBe(null);
});
it('returns null if there was no valid tab param', () => {
expect(getPipelineDefaultTab(`${baseUrl}?${TAB_QUERY_PARAM}=invalid`)).toBe(null);
it('returns null if there was no valid last url part', () => {
expect(getPipelineDefaultTab(`${baseUrl}something`)).toBe(null);
});
it('returns the correct tab name if present', () => {
validPipelineTabNames.forEach((tabName) => {
expect(getPipelineDefaultTab(`${baseUrl}?${TAB_QUERY_PARAM}=${tabName}`)).toBe(tabName);
expect(getPipelineDefaultTab(`${baseUrl}${tabName}`)).toBe(tabName);
});
});
it('returns the right value even with query params', () => {
const [tabName] = validPipelineTabNames;
expect(getPipelineDefaultTab(`${baseUrl}${tabName}?query="something"&query2="else"`)).toBe(
tabName,
);
});
});
});

View File

@ -1,9 +1,7 @@
import { createAppOptions, createPipelineTabs } from '~/pipelines/pipeline_tabs';
import { updateHistory } from '~/lib/utils/url_utility';
import { createAppOptions } from '~/pipelines/pipeline_tabs';
jest.mock('~/lib/utils/url_utility', () => ({
removeParams: () => 'gitlab.com',
updateHistory: jest.fn(),
joinPaths: () => {},
setUrlFragment: () => {},
}));
@ -64,32 +62,4 @@ describe('~/pipelines/pipeline_tabs.js', () => {
expect(createAppOptions('foo', null)).toBe(null);
});
});
describe('createPipelineTabs', () => {
const title = 'Pipeline Tabs';
beforeAll(() => {
document.title = title;
});
afterAll(() => {
document.title = '';
});
it('calls `updateHistory` with correct params', () => {
createPipelineTabs({});
expect(updateHistory).toHaveBeenCalledWith({
title,
url: 'gitlab.com',
replace: true,
});
});
it("returns early if options aren't provided", () => {
createPipelineTabs();
expect(updateHistory).not.toHaveBeenCalled();
});
});
});

View File

@ -0,0 +1,74 @@
import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { IssuableType, WorkspaceType } from '~/issues/constants';
import { __ } from '~/locale';
import BulkUpdateMilestoneDropdown from '~/sidebar/components/milestone/bulk_update_milestone_dropdown.vue';
import SidebarDropdown from '~/sidebar/components/sidebar_dropdown.vue';
describe('BulkUpdateMilestoneDropdown component', () => {
let wrapper;
const propsData = {
attrWorkspacePath: 'full/path',
issuableType: IssuableType.Issue,
workspaceType: WorkspaceType.project,
};
const findHiddenInput = () => wrapper.find('input');
const findSidebarDropdown = () => wrapper.findComponent(SidebarDropdown);
const createComponent = () => {
wrapper = shallowMount(BulkUpdateMilestoneDropdown, { propsData });
};
beforeEach(() => {
createComponent();
});
it('renders SidebarDropdown', () => {
expect(findSidebarDropdown().props()).toMatchObject({
attrWorkspacePath: propsData.attrWorkspacePath,
issuableAttribute: BulkUpdateMilestoneDropdown.issuableAttribute,
issuableType: propsData.issuableType,
workspaceType: propsData.workspaceType,
});
});
it('renders hidden input', () => {
expect(findHiddenInput().attributes()).toEqual({
type: 'hidden',
name: 'update[milestone_id]',
value: undefined,
});
});
describe('when SidebarDropdown emits `change` event', () => {
describe('when valid milestone is emitted', () => {
it('updates the hidden input value', async () => {
const milestone = {
id: 'gid://gitlab/Milestone/52',
title: __('Milestone 52'),
};
findSidebarDropdown().vm.$emit('change', milestone);
await nextTick();
expect(findHiddenInput().attributes('value')).toBe(
getIdFromGraphQLId(milestone.id).toString(),
);
});
});
describe('when null milestone is emitted', () => {
it('updates the hidden input value to `0`', async () => {
const milestone = { id: null };
findSidebarDropdown().vm.$emit('change', milestone);
await nextTick();
expect(findHiddenInput().attributes('value')).toBe('0');
});
});
});
});

View File

@ -31,6 +31,7 @@ RSpec.describe Projects::PipelineHelper do
suite_endpoint: project_pipeline_test_path(project, pipeline, suite_name: 'suite', format: :json),
blob_path: project_blob_path(project, pipeline.sha),
has_test_report: pipeline.complete_and_has_reports?(Ci::JobArtifact.of_report_type(:test)),
empty_dag_svg_path: match_asset_path('illustrations/empty-state/empty-dag-md.svg'),
empty_state_image_path: match_asset_path('illustrations/empty-state/empty-test-cases-lg.svg'),
artifacts_expired_image_path: match_asset_path('illustrations/pipeline.svg'),
tests_count: pipeline.test_report_summary.total[:count]

View File

@ -389,6 +389,40 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
model.add_concurrent_index(:users, :foo)
end
context 'when targeting a partition table' do
let(:schema) { 'public' }
let(:name) { '_test_partition_01' }
let(:identifier) { "#{schema}.#{name}" }
before do
model.execute(<<~SQL)
CREATE TABLE public._test_partitioned_table (
id serial NOT NULL,
partition_id serial NOT NULL,
PRIMARY KEY (id, partition_id)
) PARTITION BY LIST(partition_id);
CREATE TABLE #{identifier} PARTITION OF public._test_partitioned_table
FOR VALUES IN (1);
SQL
end
context 'when allow_partition is true' do
it 'creates the index concurrently' do
expect(model).to receive(:add_index).with(:_test_partition_01, :foo, algorithm: :concurrently)
model.add_concurrent_index(:_test_partition_01, :foo, allow_partition: true)
end
end
context 'when allow_partition is not provided' do
it 'raises ArgumentError' do
expect { model.add_concurrent_index(:_test_partition_01, :foo) }
.to raise_error(ArgumentError, /use add_concurrent_partitioned_index/)
end
end
end
end
context 'inside a transaction' do
@ -2889,4 +2923,36 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
model.add_sequence(:test_table, :test_column, :test_table_id_seq, 1)
end
end
describe "#partition?" do
subject { model.partition?(table_name) }
let(:table_name) { 'ci_builds_metadata' }
context "when a partition table exist" do
context 'when the view postgres_partitions exists' do
it 'calls the view', :aggregate_failures do
expect(Gitlab::Database::PostgresPartition).to receive(:partition_exists?).with(table_name).and_call_original
expect(subject).to be_truthy
end
end
context 'when the view postgres_partitions does not exist' do
before do
allow(model).to receive(:view_exists?).and_return(false)
end
it 'does not call the view', :aggregate_failures do
expect(Gitlab::Database::PostgresPartition).to receive(:legacy_partition_exists?).with(table_name).and_call_original
expect(subject).to be_truthy
end
end
end
context "when a partition table does not exist" do
let(:table_name) { 'partition_does_not_exist' }
it { is_expected.to be_falsey }
end
end
end

View File

@ -65,8 +65,11 @@ RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::IndexHelpers do
end
def expect_add_concurrent_index_and_call_original(table, column, index)
expect(migration).to receive(:add_concurrent_index).ordered.with(table, column, { name: index })
.and_wrap_original { |_, table, column, options| connection.add_index(table, column, **options) }
expect(migration).to receive(:add_concurrent_index).ordered.with(table, column, { name: index, allow_partition: true })
.and_wrap_original do |_, table, column, options|
options.delete(:allow_partition)
connection.add_index(table, column, **options)
end
end
end
@ -91,7 +94,7 @@ RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::IndexHelpers do
it 'forwards them to the index helper methods', :aggregate_failures do
expect(migration).to receive(:add_concurrent_index)
.with(partition1_identifier, column_name, { name: partition1_index, where: 'x > 0', unique: true })
.with(partition1_identifier, column_name, { name: partition1_index, where: 'x > 0', unique: true, allow_partition: true })
expect(migration).to receive(:add_index)
.with(table_name, column_name, { name: index_name, where: 'x > 0', unique: true })

View File

@ -72,4 +72,36 @@ RSpec.describe Gitlab::Database::PostgresPartition, type: :model do
expect(find(identifier).condition).to eq("FOR VALUES FROM ('2020-01-01 00:00:00+00') TO ('2020-02-01 00:00:00+00')")
end
end
describe '.partition_exists?' do
subject { described_class.partition_exists?(table_name) }
context 'when the partition exists' do
let(:table_name) { "ci_builds_metadata" }
it { is_expected.to be_truthy }
end
context 'when the partition does not exist' do
let(:table_name) { 'partition_does_not_exist' }
it { is_expected.to be_falsey }
end
end
describe '.legacy_partition_exists?' do
subject { described_class.legacy_partition_exists?(table_name) }
context 'when the partition exists' do
let(:table_name) { "ci_builds_metadata" }
it { is_expected.to be_truthy }
end
context 'when the partition does not exist' do
let(:table_name) { 'partition_does_not_exist' }
it { is_expected.to be_falsey }
end
end
end

View File

@ -39,6 +39,72 @@ RSpec.describe Gitlab::GonHelper do
helper.add_gon_variables
end
end
describe 'sentry configuration' do
let(:legacy_clientside_dsn) { 'https://xxx@sentry-legacy.example.com/1' }
let(:clientside_dsn) { 'https://xxx@sentry.example.com/1' }
let(:environment) { 'production' }
context 'with enable_old_sentry_clientside_integration enabled' do
before do
stub_feature_flags(
enable_old_sentry_clientside_integration: true,
enable_new_sentry_clientside_integration: false
)
stub_config(sentry: { enabled: true, clientside_dsn: legacy_clientside_dsn, environment: environment })
end
it 'sets sentry dsn and environment from config' do
expect(gon).to receive(:sentry_dsn=).with(legacy_clientside_dsn)
expect(gon).to receive(:sentry_environment=).with(environment)
helper.add_gon_variables
end
end
context 'with enable_new_sentry_clientside_integration enabled' do
before do
stub_feature_flags(
enable_old_sentry_clientside_integration: false,
enable_new_sentry_clientside_integration: true
)
stub_application_setting(sentry_enabled: true)
stub_application_setting(sentry_clientside_dsn: clientside_dsn)
stub_application_setting(sentry_environment: environment)
end
it 'sets sentry dsn and environment from application settings' do
expect(gon).to receive(:sentry_dsn=).with(clientside_dsn)
expect(gon).to receive(:sentry_environment=).with(environment)
helper.add_gon_variables
end
end
context 'with enable_old_sentry_clientside_integration and enable_new_sentry_clientside_integration enabled' do
before do
stub_feature_flags(
enable_old_sentry_clientside_integration: true,
enable_new_sentry_clientside_integration: true
)
stub_config(sentry: { enabled: true, clientside_dsn: legacy_clientside_dsn, environment: environment })
stub_application_setting(sentry_enabled: true)
stub_application_setting(sentry_clientside_dsn: clientside_dsn)
stub_application_setting(sentry_environment: environment)
end
it 'sets sentry dsn and environment from application settings' do
expect(gon).to receive(:sentry_dsn=).with(clientside_dsn)
expect(gon).to receive(:sentry_environment=).with(environment)
helper.add_gon_variables
end
end
end
end
describe '#push_frontend_feature_flag' do

View File

@ -13,6 +13,11 @@ RSpec.describe AlertManagement::HttpIntegration do
it { is_expected.to belong_to(:project) }
end
describe 'default values' do
it { expect(described_class.new.endpoint_identifier).to be_present }
it { expect(described_class.new(endpoint_identifier: 'test').endpoint_identifier).to eq('test') }
end
describe 'validations' do
it { is_expected.to validate_presence_of(:project) }
it { is_expected.to validate_presence_of(:name) }
@ -124,10 +129,6 @@ RSpec.describe AlertManagement::HttpIntegration do
end
context 'when unsaved' do
context 'when unassigned' do
it_behaves_like 'valid token'
end
context 'when assigned' do
include_context 'assign token', 'random_token'

View File

@ -4089,7 +4089,6 @@
- './spec/features/incidents/user_creates_new_incident_spec.rb'
- './spec/features/incidents/user_filters_incidents_by_status_spec.rb'
- './spec/features/incidents/user_searches_incidents_spec.rb'
- './spec/features/incidents/user_views_incident_spec.rb'
- './spec/features/invites_spec.rb'
- './spec/features/issuables/issuable_list_spec.rb'
- './spec/features/issuables/markdown_references/internal_references_spec.rb'

View File

@ -3,6 +3,7 @@
RSpec.shared_examples 'multiple issue boards' do
context 'authorized user' do
before do
stub_feature_flags(apollo_boards: false)
parent.add_maintainer(user)
login_as(user)
@ -121,6 +122,7 @@ RSpec.shared_examples 'multiple issue boards' do
context 'unauthorized user' do
before do
stub_feature_flags(apollo_boards: false)
visit boards_path
wait_for_requests
end

View File

@ -5,6 +5,7 @@ RSpec.shared_examples 'multiple and scoped issue boards' do |route_definition|
context 'multiple issue boards' do
before do
stub_feature_flags(apollo_boards: false)
board_parent.add_reporter(user)
stub_licensed_features(multiple_group_issue_boards: true)
end

View File

@ -52,6 +52,7 @@ const (
geoGitProjectPattern = `^/[^-].+\.git/` // Prevent matching routes like /-/push_from_secondary
projectPattern = `^/([^/]+/){1,}[^/]+/`
apiProjectPattern = apiPattern + `v4/projects/[^/]+` // API: Projects can be encoded via group%2Fsubgroup%2Fproject
apiGroupPattern = apiPattern + `v4/groups/[^/]+`
apiTopicPattern = apiPattern + `v4/topics`
snippetUploadPattern = `^/uploads/personal_snippet`
userUploadPattern = `^/uploads/user`
@ -303,6 +304,7 @@ func configureRoutes(u *upstream) {
// we need to declare each routes until we have fixed all the routes on the rails codebase.
// Overall status can be seen at https://gitlab.com/groups/gitlab-org/-/epics/1802#current-status
u.route("POST", apiProjectPattern+`/wikis/attachments\z`, tempfileMultipartProxy),
u.route("POST", apiGroupPattern+`/wikis/attachments\z`, tempfileMultipartProxy),
u.route("POST", apiPattern+`graphql\z`, tempfileMultipartProxy),
u.route("POST", apiTopicPattern, tempfileMultipartProxy),
u.route("PUT", apiTopicPattern, tempfileMultipartProxy),

View File

@ -138,6 +138,8 @@ func TestAcceleratedUpload(t *testing.T) {
{"POST", `/api/v4/groups`, false},
{"PUT", `/api/v4/groups/5`, false},
{"PUT", `/api/v4/groups/group%2Fsubgroup`, false},
{"POST", `/api/v4/groups/1/wikis/attachments`, false},
{"POST", `/api/v4/groups/my%2Fsubgroup/wikis/attachments`, false},
{"POST", `/api/v4/users`, false},
{"PUT", `/api/v4/users/42`, false},
{"PUT", "/api/v4/projects/9001/packages/nuget/v1/files", true},