Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2024-11-04 15:23:00 +00:00
parent 62d12a8a8d
commit a075eff323
104 changed files with 1186 additions and 661 deletions

View File

@ -17,7 +17,6 @@ Gitlab/DocUrl:
- 'ee/app/graphql/types/vulnerability_state_enum.rb'
- 'ee/app/mailers/emails/user_cap.rb'
- 'ee/app/workers/concerns/elastic/migration_obsolete.rb'
- 'ee/lib/ee/gitlab/ci/pipeline/quota/size.rb'
- 'ee/lib/system_check/app/advanced_search_migrations_check.rb'
- 'ee/lib/tasks/gitlab/geo.rake'
- 'lib/feature.rb'

View File

@ -106,7 +106,6 @@ Gitlab/EeOnlyClass:
- 'ee/lib/ee/gitlab/checks/push_rules/commit_check.rb'
- 'ee/lib/ee/gitlab/checks/push_rules/file_size_check.rb'
- 'ee/lib/ee/gitlab/checks/push_rules/tag_check.rb'
- 'ee/lib/ee/gitlab/ci/pipeline/quota/size.rb'
- 'ee/lib/ee/gitlab/ci/variables/builder/scan_execution_policies.rb'
- 'ee/lib/ee/gitlab/import_export/after_export_strategies/custom_template_export_import_strategy.rb'
- 'ee/lib/ee/gitlab/namespace_storage_size_error_message.rb'

View File

@ -358,7 +358,6 @@ Gitlab/StrongMemoizeAttr:
- 'ee/lib/ee/gitlab/checks/diff_check.rb'
- 'ee/lib/ee/gitlab/ci/matching/runner_matcher.rb'
- 'ee/lib/ee/gitlab/ci/pipeline/chain/validate/external.rb'
- 'ee/lib/ee/gitlab/ci/pipeline/quota/size.rb'
- 'ee/lib/ee/gitlab/etag_caching/router/rails.rb'
- 'ee/lib/ee/gitlab/git_access.rb'
- 'ee/lib/ee/gitlab/gitaly_client/with_feature_flag_actors.rb'

View File

@ -7,7 +7,6 @@ Layout/ExtraSpacing:
- 'ee/app/models/merge_request/predictions.rb'
- 'ee/app/services/dependencies/export_serializers/group_dependencies_service.rb'
- 'ee/app/workers/pull_mirrors/reenable_configuration_worker.rb'
- 'ee/lib/ee/gitlab/ci/pipeline/quota/size.rb'
- 'ee/lib/gitlab/usage/metrics/instrumentations/license_metric.rb'
- 'ee/spec/controllers/projects/settings/merge_requests_controller_spec.rb'
- 'ee/spec/requests/api/internal/base_spec.rb'

View File

@ -43,7 +43,6 @@ Layout/LineContinuationSpacing:
- 'ee/lib/api/ldap_group_links.rb'
- 'ee/lib/api/vulnerability_findings.rb'
- 'ee/lib/ee/gitlab/auth/ldap/access.rb'
- 'ee/lib/ee/gitlab/ci/pipeline/quota/size.rb'
- 'ee/lib/ee/gitlab/git_access.rb'
- 'ee/lib/tasks/gitlab/geo.rake'
- 'ee/spec/controllers/groups/group_members_controller_spec.rb'

View File

@ -178,7 +178,6 @@ Layout/LineEndStringConcatenationIndentation:
- 'ee/lib/ee/api/helpers/groups_helpers.rb'
- 'ee/lib/ee/gitlab/auth/ldap/access.rb'
- 'ee/lib/ee/gitlab/auth/o_auth/user.rb'
- 'ee/lib/ee/gitlab/ci/pipeline/quota/size.rb'
- 'ee/lib/ee/gitlab/ci/yaml_processor.rb'
- 'ee/lib/ee/gitlab/git_access.rb'
- 'ee/lib/ee/gitlab/namespace_storage_size_error_message.rb'

View File

@ -1,13 +1,15 @@
<!-- eslint-disable vue/multi-word-component-names -->
<script>
import { getDocument, GlobalWorkerOptions } from 'pdfjs-dist/legacy/build/pdf';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import Page from './page/index.vue';
GlobalWorkerOptions.workerSrc = process.env.PDF_JS_WORKER_PUBLIC_PATH;
let pdfjs;
let getDocument;
let GlobalWorkerOptions;
export default {
components: { Page },
mixins: [glFeatureFlagsMixin()],
props: {
pdf: {
type: [String, Uint8Array],
@ -35,13 +37,26 @@ export default {
if (this.hasPDF) this.load();
},
methods: {
load() {
async loadPDFJS() {
pdfjs = this.glFeatures.upgradePdfjs
? // eslint-disable-next-line import/extensions
await import('pdfjs-dist-v4/legacy/build/pdf.mjs')
: await import('pdfjs-dist-v3/legacy/build/pdf');
({ getDocument, GlobalWorkerOptions } = pdfjs);
GlobalWorkerOptions.workerSrc = this.glFeatures.upgradePdfjs
? process.env.PDF_JS_WORKER_V4_PUBLIC_PATH
: process.env.PDF_JS_WORKER_V3_PUBLIC_PATH;
},
async load() {
await this.loadPDFJS();
this.pages = [];
return getDocument({
url: this.document,
cMapUrl: '/assets/webpack/pdfjs/cmaps/',
cMapUrl: this.glFeatures.upgradePdfjs
? process.env.PDF_JS_CMAPS_V4_PUBLIC_PATH
: process.env.PDF_JS_CMAPS_V3_PUBLIC_PATH,
cMapPacked: true,
isEvalSupported: false,
isEvalSupported: this.glFeatures.upgradePdfjs,
})
.promise.then(this.renderPages)
.then((pages) => {

View File

@ -111,7 +111,7 @@ export default {
</script>
<template>
<div class="gl-border-t">
<div class="gl-border-t gl-border-t-section">
<div class="well-segment">
<refs-list
:has-containing-refs="hasContainingBranches"

View File

@ -6,7 +6,7 @@ import {
GlFormGroup,
GlFormInput,
GlFormTextarea,
GlToggle,
GlFormCheckbox,
} from '@gitlab/ui';
import { createAlert } from '~/alert';
import axios from '~/lib/utils/axios_utils';
@ -33,7 +33,7 @@ export default {
GlFormGroup,
GlFormInput,
GlFormTextarea,
GlToggle,
GlFormCheckbox,
},
i18n: {
DIR_LABEL,
@ -165,12 +165,9 @@ export default {
>
<gl-form-input v-model="target" :disabled="loading" name="branch_name" />
</gl-form-group>
<gl-toggle
v-if="showCreateNewMrToggle"
v-model="createNewMr"
:disabled="loading"
:label="$options.i18n.TOGGLE_CREATE_MR_LABEL"
/>
<gl-form-checkbox v-if="showCreateNewMrToggle" v-model="createNewMr" :disabled="loading">
{{ $options.i18n.TOGGLE_CREATE_MR_LABEL }}
</gl-form-checkbox>
<gl-alert v-if="!canPushCode" variant="info" :dismissible="false" class="gl-mt-3">
{{ $options.i18n.NEW_BRANCH_IN_FORK }}
</gl-alert>

View File

@ -5,9 +5,9 @@ import {
GlFormGroup,
GlFormInput,
GlFormTextarea,
GlToggle,
GlButton,
GlAlert,
GlFormCheckbox,
} from '@gitlab/ui';
import FileIcon from '~/vue_shared/components/file_icon.vue';
import { createAlert } from '~/alert';
@ -39,11 +39,11 @@ export default {
GlFormGroup,
GlFormInput,
GlFormTextarea,
GlToggle,
GlButton,
UploadDropzone,
GlAlert,
FileIcon,
GlFormCheckbox,
},
i18n: {
COMMIT_LABEL,
@ -250,12 +250,9 @@ export default {
>
<gl-form-input v-model="target" :disabled="loading" name="branch_name" />
</gl-form-group>
<gl-toggle
v-if="showCreateNewMrToggle"
v-model="createNewMr"
:disabled="loading"
:label="$options.i18n.TOGGLE_CREATE_MR_LABEL"
/>
<gl-form-checkbox v-if="showCreateNewMrToggle" v-model="createNewMr" :disabled="loading">
{{ $options.i18n.TOGGLE_CREATE_MR_LABEL }}
</gl-form-checkbox>
<gl-alert v-if="!canPushCode" variant="info" :dismissible="false" class="gl-mt-3">
{{ $options.i18n.NEW_BRANCH_IN_FORK }}
</gl-alert>

View File

@ -1,26 +0,0 @@
import { s__ } from '~/locale';
const headerLabel = s__('GlobalSearch|Archived');
const checkboxLabel = s__('GlobalSearch|Include archived');
export const TRACKING_NAMESPACE = 'search:archived:select';
export const TRACKING_LABEL_CHECKBOX = 'checkbox';
const scopes = [
'projects',
'issues',
'merge_requests',
'notes',
'blobs',
'commits',
'milestones',
'wiki_blobs',
];
const filterParam = 'include_archived';
export const archivedFilterData = {
headerLabel,
checkboxLabel,
scopes,
filterParam,
};

View File

@ -5,8 +5,13 @@ import { mapState, mapActions } from 'vuex';
import { s__ } from '~/locale';
import Tracking from '~/tracking';
import { parseBoolean } from '~/lib/utils/common_utils';
import { archivedFilterData, TRACKING_NAMESPACE, TRACKING_LABEL_CHECKBOX } from './data';
import {
ARCHIVED_TRACKING_NAMESPACE,
ARCHIVED_TRACKING_LABEL_CHECKBOX,
ARCHIVED_TRACKING_LABEL_CHECKBOX_LABEL,
LABEL_DEFAULT_CLASSES,
INCLUDE_ARCHIVED_FILTER_PARAM,
} from '../../constants';
export default {
name: 'ArchivedFilter',
@ -19,6 +24,8 @@ export default {
},
i18n: {
tooltip: s__('GlobalSearch|Include search results from archived projects'),
headerLabel: s__('GlobalSearch|Archived'),
checkboxLabel: s__('GlobalSearch|Include archived'),
},
computed: {
...mapState(['urlQuery']),
@ -28,7 +35,10 @@ export default {
},
set(value) {
const includeArchived = [...value].pop() ?? false;
this.setQuery({ key: archivedFilterData.filterParam, value: includeArchived?.toString() });
this.setQuery({
key: INCLUDE_ARCHIVED_FILTER_PARAM,
value: includeArchived?.toString(),
});
this.trackSelectCheckbox(includeArchived);
},
},
@ -36,20 +46,20 @@ export default {
methods: {
...mapActions(['setQuery']),
trackSelectCheckbox(value) {
Tracking.event(TRACKING_NAMESPACE, TRACKING_LABEL_CHECKBOX, {
label: archivedFilterData.checkboxLabel,
Tracking.event(ARCHIVED_TRACKING_NAMESPACE, ARCHIVED_TRACKING_LABEL_CHECKBOX, {
label: ARCHIVED_TRACKING_LABEL_CHECKBOX_LABEL,
property: value,
});
},
},
archivedFilterData,
LABEL_DEFAULT_CLASSES,
};
</script>
<template>
<gl-form-checkbox-group v-model="selectedFilter">
<div class="gl-mb-2 gl-text-sm gl-font-bold" data-testid="archived-filter-title">
{{ $options.archivedFilterData.headerLabel }}
{{ $options.i18n.headerLabel }}
</div>
<gl-form-checkbox
class="gl-inline-flex gl-w-full gl-grow gl-justify-between"
@ -57,7 +67,7 @@ export default {
:value="true"
>
<span v-gl-tooltip="$options.i18n.tooltip" data-testid="label">
{{ $options.archivedFilterData.checkboxLabel }}
{{ $options.i18n.checkboxLabel }}
</span>
</gl-form-checkbox>
</gl-form-checkbox-group>

View File

@ -1,38 +0,0 @@
import Tracking from '~/tracking';
export const TRACKING_CATEGORY = 'Language filters';
export const TRACKING_LABEL_FILTERS = 'Filters';
export const TRACKING_LABEL_MAX = 'Max Shown';
export const TRACKING_LABEL_SHOW_MORE = 'Show More';
export const TRACKING_LABEL_APPLY = 'Apply Filters';
export const TRACKING_LABEL_RESET = 'Reset Filters';
export const TRACKING_LABEL_ALL = 'All Filters';
export const TRACKING_PROPERTY_MAX = `More than filters to show`;
export const TRACKING_ACTION_CLICK = 'search:agreggations:language:click';
export const TRACKING_ACTION_SHOW = 'search:agreggations:language:show';
// select is imported and used in checkbox_filter.vue
export const TRACKING_ACTION_SELECT = 'search:agreggations:language:select';
export const trackShowMore = () =>
Tracking.event(TRACKING_ACTION_CLICK, TRACKING_LABEL_SHOW_MORE, {
label: TRACKING_LABEL_ALL,
});
export const trackShowHasOverMax = () =>
Tracking.event(TRACKING_ACTION_SHOW, TRACKING_LABEL_FILTERS, {
label: TRACKING_LABEL_MAX,
property: TRACKING_PROPERTY_MAX,
});
export const trackSubmitQuery = () =>
Tracking.event(TRACKING_ACTION_CLICK, TRACKING_LABEL_APPLY, {
label: TRACKING_CATEGORY,
});
export const trackResetQuery = () =>
Tracking.event(TRACKING_ACTION_CLICK, TRACKING_LABEL_RESET, {
label: TRACKING_CATEGORY,
});

View File

@ -1,36 +0,0 @@
import { __ } from '~/locale';
const header = __('Confidentiality');
const filters = {
ANY: {
label: __('Any'),
value: null,
},
CONFIDENTIAL: {
label: __('Confidential'),
value: 'yes',
},
NOT_CONFIDENTIAL: {
label: __('Not confidential'),
value: 'no',
},
};
const scopes = {
ISSUES: 'issues',
};
const filterByScope = {
[scopes.ISSUES]: [filters.ANY, filters.CONFIDENTIAL, filters.NOT_CONFIDENTIAL],
};
const filterParam = 'confidential';
export const confidentialFilterData = {
header,
filters,
scopes,
filterByScope,
filterParam,
};

View File

@ -1,18 +1,41 @@
<script>
import { __ } from '~/locale';
import {
SCOPE_ISSUES,
CONFIDENTAL_FILTER_PARAM,
CONFIDENTIAL_FILTERS,
} from '~/search/sidebar/constants';
import RadioFilter from '../shared/radio_filter.vue';
import { confidentialFilterData } from './data';
export default {
name: 'ConfidentialityFilter',
components: {
RadioFilter,
},
confidentialFilterData,
i18n: {
header: __('Confidentiality'),
},
computed: {
filtersArray() {
return {
[SCOPE_ISSUES]: [
CONFIDENTIAL_FILTERS.ANY,
CONFIDENTIAL_FILTERS.CONFIDENTIAL,
CONFIDENTIAL_FILTERS.NOT_CONFIDENTIAL,
],
};
},
},
CONFIDENTAL_FILTER_PARAM,
};
</script>
<template>
<div>
<radio-filter :filter-data="$options.confidentialFilterData" />
<radio-filter
:filters-array="filtersArray"
:header="$options.i18n.header"
:filter-param="$options.CONFIDENTAL_FILTER_PARAM"
/>
</div>
</template>

View File

@ -5,12 +5,13 @@ import { mapState, mapActions } from 'vuex';
import { s__ } from '~/locale';
import { InternalEvents } from '~/tracking';
import { parseBoolean } from '~/lib/utils/common_utils';
import { EVENT_CLICK_ZOEKT_INCLUDE_FORKS_ON_SEARCH_RESULTS_PAGE } from '~/search/sidebar/constants';
import {
EVENT_CLICK_ZOEKT_INCLUDE_FORKS_ON_SEARCH_RESULTS_PAGE,
INCLUDE_FORKED_FILTER_PARAM,
} from '~/search/sidebar/constants';
const trackingMixin = InternalEvents.mixin();
export const INCLUDE_FORKED_FILTER_PARAM = 'include_forked';
export default {
name: 'ForksFilter',
components: {

View File

@ -1,15 +0,0 @@
import { __ } from '~/locale';
export const FIRST_DROPDOWN_INDEX = 0;
export const SEARCH_BOX_INDEX = 0;
export const SEARCH_INPUT_DESCRIPTION = 'label-search-input-description';
export const SEARCH_RESULTS_DESCRIPTION = 'label-search-results-description';
export const LABEL_FILTER_HEADER = __('Labels');
export const LABEL_FILTER_PARAM = 'label_name';
export const LABEL_AGREGATION_NAME = 'labels';

View File

@ -19,16 +19,15 @@ import { slugify } from '~/lib/utils/text_utility';
import DropdownKeyboardNavigation from '~/vue_shared/components/dropdown_keyboard_navigation.vue';
import { I18N } from '~/vue_shared/global_search/constants';
import LabelDropdownItems from './label_dropdown_items.vue';
import {
FIRST_DROPDOWN_INDEX,
SEARCH_BOX_INDEX,
SEARCH_RESULTS_DESCRIPTION,
SEARCH_INPUT_DESCRIPTION,
LABEL_FILTER_PARAM,
SEARCH_RESULTS_DESCRIPTION,
LABEL_FILTER_HEADER,
} from './data';
LABEL_FILTER_PARAM,
} from '../../constants';
import LabelDropdownItems from './label_dropdown_items.vue';
import { trackSelectCheckbox, trackOpenDropdown } from './tracking';

View File

@ -7,9 +7,7 @@ import { intersection } from 'lodash';
import Tracking from '~/tracking';
import { NAV_LINK_COUNT_DEFAULT_CLASSES, LABEL_DEFAULT_CLASSES } from '../../constants';
import { formatSearchResultCount } from '../../../store/utils';
export const TRACKING_LABEL_SET = 'set';
export const TRACKING_LABEL_CHECKBOX = 'checkbox';
import { TRACKING_LABEL_SET, TRACKING_LABEL_CHECKBOX } from './tracking';
export default {
name: 'CheckboxFilter',
@ -26,6 +24,10 @@ export default {
type: String,
required: true,
},
queryParam: {
type: String,
required: true,
},
},
computed: {
...mapGetters(['queryLanguageFilters']),
@ -40,7 +42,7 @@ export default {
return intersection(this.flatDataFilterValues, this.queryLanguageFilters);
},
async set(value) {
this.setQuery({ key: this.filtersData?.filterParam, value });
this.setQuery({ key: this.queryParam, value });
await Vue.nextTick();
this.trackSelectCheckbox();

View File

@ -1,18 +0,0 @@
import { s__ } from '~/locale';
export const DEFAULT_ITEM_LENGTH = 10;
export const MAX_ITEM_LENGTH = 100;
const header = s__('GlobalSearch|Language');
const scopes = {
BLOBS: 'blobs',
};
const filterParam = 'language';
export const languageFilterData = {
header,
scopes,
filterParam,
};

View File

@ -4,11 +4,14 @@ import { GlButton, GlAlert } from '@gitlab/ui';
import { mapState, mapActions, mapGetters } from 'vuex';
import { s__, sprintf } from '~/locale';
import { convertFiltersData } from '../../utils';
import {
LANGUAGE_DEFAULT_ITEM_LENGTH,
LANGUAGE_MAX_ITEM_LENGTH,
LANGUAGE_FILTER_PARAM,
} from '../../constants';
import CheckboxFilter from './checkbox_filter.vue';
import { trackShowMore, trackShowHasOverMax, TRACKING_ACTION_SELECT } from './tracking';
import { DEFAULT_ITEM_LENGTH, MAX_ITEM_LENGTH, languageFilterData } from './data';
export default {
name: 'LanguageFilter',
components: {
@ -23,8 +26,11 @@ export default {
},
i18n: {
showMore: s__('GlobalSearch|Show more'),
showingMax: sprintf(s__('GlobalSearch|Showing top %{maxItems}'), { maxItems: MAX_ITEM_LENGTH }),
showingMax: sprintf(s__('GlobalSearch|Showing top %{maxItems}'), {
maxItems: LANGUAGE_MAX_ITEM_LENGTH,
}),
loadError: s__('GlobalSearch|Aggregations load error.'),
headerLabel: s__('GlobalSearch|Language'),
},
computed: {
...mapState(['aggregations']),
@ -40,15 +46,15 @@ export default {
return this.languageAggregationBuckets;
}
if (this.showAll) {
return this.trimBuckets(MAX_ITEM_LENGTH);
return this.trimBuckets(LANGUAGE_MAX_ITEM_LENGTH);
}
return this.trimBuckets(DEFAULT_ITEM_LENGTH);
return this.trimBuckets(LANGUAGE_DEFAULT_ITEM_LENGTH);
},
hasShowMore() {
return this.languageAggregationBuckets.length > DEFAULT_ITEM_LENGTH;
return this.languageAggregationBuckets.length > LANGUAGE_DEFAULT_ITEM_LENGTH;
},
hasOverMax() {
return this.languageAggregationBuckets.length > MAX_ITEM_LENGTH;
return this.languageAggregationBuckets.length > LANGUAGE_MAX_ITEM_LENGTH;
},
},
async created() {
@ -68,15 +74,15 @@ export default {
return this.languageAggregationBuckets.slice(0, length);
},
},
LANGUAGE_FILTER_PARAM,
TRACKING_ACTION_SELECT,
languageFilterData,
};
</script>
<template>
<div v-if="hasBuckets" class="language-filter-checkbox">
<div class="gl-mb-2 gl-text-sm gl-font-bold">
{{ $options.languageFilterData.header }}
{{ $options.i18n.headerLabel }}
</div>
<div
v-if="!aggregations.error"
@ -86,6 +92,7 @@ export default {
<checkbox-filter
:filters-data="filtersData"
:tracking-namespace="$options.TRACKING_ACTION_SELECT"
:query-param="$options.LANGUAGE_FILTER_PARAM"
/>
<span v-if="showAll && hasOverMax" data-testid="has-over-max-text">{{
$options.i18n.showingMax

View File

@ -1,5 +1,5 @@
import Tracking from '~/tracking';
import { MAX_ITEM_LENGTH } from './data';
import { LANGUAGE_DEFAULT_ITEM_LENGTH } from '../../constants';
export const TRACKING_CATEGORY = 'Language filters';
export const TRACKING_LABEL_FILTERS = 'Filters';
@ -9,7 +9,7 @@ export const TRACKING_LABEL_SHOW_MORE = 'Show More';
export const TRACKING_LABEL_APPLY = 'Apply Filters';
export const TRACKING_LABEL_RESET = 'Reset Filters';
export const TRACKING_LABEL_ALL = 'All Filters';
export const TRACKING_PROPERTY_MAX = `More than ${MAX_ITEM_LENGTH} filters to show`;
export const TRACKING_PROPERTY_MAX = `More than ${LANGUAGE_DEFAULT_ITEM_LENGTH} filters to show`;
export const TRACKING_ACTION_CLICK = 'search:agreggations:language:click';
export const TRACKING_ACTION_SHOW = 'search:agreggations:language:show';
@ -27,3 +27,6 @@ export const trackShowHasOverMax = () =>
label: TRACKING_LABEL_MAX,
property: TRACKING_PROPERTY_MAX,
});
export const TRACKING_LABEL_SET = 'set';
export const TRACKING_LABEL_CHECKBOX = 'checkbox';

View File

@ -4,9 +4,13 @@ import { isEmpty } from 'lodash';
import { mapState, mapActions, mapGetters } from 'vuex';
import { s__ } from '~/locale';
import { visitUrl, setUrlParams } from '~/lib/utils/url_utility';
import { ANY_OPTION, GROUP_DATA, PROJECT_DATA } from '~/search/sidebar/constants';
import {
ANY_OPTION,
GROUP_DATA,
PROJECT_DATA,
INCLUDE_ARCHIVED_FILTER_PARAM,
} from '~/search/sidebar/constants';
import SearchableDropdown from './shared/searchable_dropdown.vue';
import { archivedFilterData } from './archived_filter/data';
export default {
name: 'ProjectFilter',
@ -64,7 +68,7 @@ export default {
[PROJECT_DATA.queryParam]: project.id,
nav_source: null,
scope: this.currentScope,
[archivedFilterData.filterParam]: null,
[INCLUDE_ARCHIVED_FILTER_PARAM]: null,
};
visitUrl(setUrlParams(queryParams));

View File

@ -11,36 +11,44 @@ export default {
GlFormRadio,
},
props: {
filterData: {
filtersArray: {
type: Object,
required: true,
},
header: {
type: String,
required: true,
},
filterParam: {
type: String,
required: true,
},
},
computed: {
...mapState(['query']),
...mapGetters(['currentScope']),
ANY() {
return this.filterData.filters.ANY;
const AnyIndex = this.filtersArray[this.currentScope].findIndex(
(item) => item.value === null,
);
return this.filtersArray[this.currentScope][AnyIndex];
},
initialFilter() {
return this.query[this.filterData.filterParam];
return this.query[this.filterParam];
},
filter() {
return this.initialFilter || this.ANY.value;
},
filtersArray() {
return this.filterData.filterByScope[this.currentScope];
},
selectedFilter: {
get() {
if (this.filtersArray.some(({ value }) => value === this.filter)) {
if (this.filtersArray[this.currentScope].some(({ value }) => value === this.filter)) {
return this.filter;
}
return this.ANY.value;
},
set(value) {
this.setQuery({ key: this.filterData.filterParam, value });
this.setQuery({ key: this.filterParam, value });
},
},
},
@ -48,7 +56,7 @@ export default {
...mapActions(['setQuery']),
radioLabel(filter) {
return filter.value === this.ANY.value
? sprintf(__('Any %{header}'), { header: this.filterData.header.toLowerCase() })
? sprintf(__('Any %{header}'), { header: this.header.toLowerCase() })
: filter.label;
},
},
@ -58,10 +66,10 @@ export default {
<template>
<div>
<div class="gl-mb-2 gl-text-sm gl-font-bold">
{{ filterData.header }}
{{ header }}
</div>
<gl-form-radio-group v-model="selectedFilter">
<gl-form-radio v-for="f in filtersArray" :key="f.value" :value="f.value">
<gl-form-radio v-for="f in filtersArray[currentScope]" :key="f.value" :value="f.value">
{{ radioLabel(f) }}
</gl-form-radio>
</gl-form-radio-group>

View File

@ -11,14 +11,13 @@ import { BRANCH_REF_TYPE_ICON } from '~/ref/constants';
import {
SEARCH_ICON,
EVENT_SELECT_SOURCE_BRANCH_FILTER_ON_MERGE_REQUEST_PAGE,
SOURCE_BRANCH_PARAM,
NOT_SOURCE_BRANCH_PARAM,
SOURCE_BRANCH_ENDPOINT_PATH,
} from '../../constants';
const trackingMixin = InternalEvents.mixin();
export const SOURCE_BRANCH_PARAM = 'source_branch';
export const NOT_SOURCE_BRANCH_PARAM = 'not[source_branch]';
export const SOURCE_BRANCH_ENDPOINT_PATH = '/-/autocomplete/merge_request_source_branches.json';
export default {
name: 'SourceBranchFilter',
components: {

View File

@ -1,42 +0,0 @@
import { __ } from '~/locale';
const header = __('Status');
const filters = {
ANY: {
label: __('Any'),
value: null,
},
OPEN: {
label: __('Open'),
value: 'opened',
},
CLOSED: {
label: __('Closed'),
value: 'closed',
},
MERGED: {
label: __('Merged'),
value: 'merged',
},
};
const scopes = {
ISSUES: 'issues',
MERGE_REQUESTS: 'merge_requests',
};
const filterByScope = {
[scopes.ISSUES]: [filters.ANY, filters.OPEN, filters.CLOSED],
[scopes.MERGE_REQUESTS]: [filters.ANY, filters.OPEN, filters.MERGED, filters.CLOSED],
};
const filterParam = 'state';
export const statusFilterData = {
header,
filters,
scopes,
filterByScope,
filterParam,
};

View File

@ -1,16 +1,42 @@
<script>
import { __ } from '~/locale';
import {
SCOPE_ISSUES,
SCOPE_MERGE_REQUESTS,
STATE_FILTER_PARAM,
STATE_FILTERS,
} from '~/search/sidebar/constants';
import RadioFilter from '../shared/radio_filter.vue';
import { statusFilterData } from './data';
export default {
name: 'StatusFilter',
components: {
RadioFilter,
},
statusFilterData,
i18n: {
header: __('Status'),
},
computed: {
filtersArray() {
return {
[SCOPE_ISSUES]: [STATE_FILTERS.ANY, STATE_FILTERS.OPEN, STATE_FILTERS.CLOSED],
[SCOPE_MERGE_REQUESTS]: [
STATE_FILTERS.ANY,
STATE_FILTERS.OPEN,
STATE_FILTERS.MERGED,
STATE_FILTERS.CLOSED,
],
};
},
},
STATE_FILTER_PARAM,
};
</script>
<template>
<radio-filter :filter-data="$options.statusFilterData" />
<radio-filter
:filters-array="filtersArray"
:header="$options.i18n.header"
:filter-param="$options.STATE_FILTER_PARAM"
/>
</template>

View File

@ -24,6 +24,10 @@ export const TRACKING_ACTION_CLICK = 'search:filters:click';
export const TRACKING_LABEL_APPLY = 'Apply Filters';
export const TRACKING_LABEL_RESET = 'Reset Filters';
export const ARCHIVED_TRACKING_NAMESPACE = 'search:archived:select';
export const ARCHIVED_TRACKING_LABEL_CHECKBOX = 'checkbox';
export const ARCHIVED_TRACKING_LABEL_CHECKBOX_LABEL = 'Include archived';
export const SEARCH_TYPE_BASIC = 'basic';
export const SEARCH_TYPE_ADVANCED = 'advanced';
export const SEARCH_TYPE_ZOEKT = 'zoekt';
@ -57,3 +61,59 @@ export const EVENT_SELECT_SOURCE_BRANCH_FILTER = 'select_source_branch_filter';
export const EVENT_SELECT_SOURCE_BRANCH_FILTER_ON_MERGE_REQUEST_PAGE =
'select_source_branch_filter_on_merge_request_page';
export const LANGUAGE_DEFAULT_ITEM_LENGTH = 10;
export const LANGUAGE_MAX_ITEM_LENGTH = 100;
export const INCLUDE_ARCHIVED_FILTER_PARAM = 'include_archived';
export const CONFIDENTAL_FILTER_PARAM = 'confidential';
export const LABEL_FILTER_PARAM = 'label_name';
export const INCLUDE_FORKED_FILTER_PARAM = 'include_forked';
export const LANGUAGE_FILTER_PARAM = 'language';
export const SOURCE_BRANCH_PARAM = 'source_branch';
export const NOT_SOURCE_BRANCH_PARAM = 'not[source_branch]';
// label filter data
export const FIRST_DROPDOWN_INDEX = 0;
export const SEARCH_BOX_INDEX = 0;
export const SEARCH_INPUT_DESCRIPTION = 'label-search-input-description';
export const SEARCH_RESULTS_DESCRIPTION = 'label-search-results-description';
export const LABEL_FILTER_HEADER = __('Labels');
export const LABEL_AGREGATION_NAME = 'labels';
export const SOURCE_BRANCH_ENDPOINT_PATH = '/-/autocomplete/merge_request_source_branches.json';
export const CONFIDENTIAL_FILTERS = {
ANY: {
label: __('Any'),
value: null,
},
CONFIDENTIAL: {
label: __('Confidential'),
value: 'yes',
},
NOT_CONFIDENTIAL: {
label: __('Not confidential'),
value: 'no',
},
};
export const STATE_FILTER_PARAM = 'state';
export const STATE_FILTERS = {
ANY: {
label: __('Any'),
value: null,
},
OPEN: {
label: __('Open'),
value: 'opened',
},
CLOSED: {
label: __('Closed'),
value: 'closed',
},
MERGED: {
label: __('Merged'),
value: 'merged',
},
};

View File

@ -1,5 +1,3 @@
import { languageFilterData } from '~/search/sidebar/components/language_filter/data';
export const convertFiltersData = (rawBuckets) =>
rawBuckets.reduce(
(acc, bucket) => ({
@ -13,5 +11,5 @@ export const convertFiltersData = (rawBuckets) =>
},
},
}),
{ ...languageFilterData, filters: {} },
{ filters: {} },
);

View File

@ -5,8 +5,7 @@ import axios from '~/lib/utils/axios_utils';
import { visitUrl, setUrlParams, getNormalizedURL, updateHistory } from '~/lib/utils/url_utility';
import { logError } from '~/lib/logger';
import { __ } from '~/locale';
import { LABEL_FILTER_PARAM } from '~/search/sidebar/components/label_filter/data';
import { SCOPE_BLOB, SEARCH_TYPE_ZOEKT } from '~/search/sidebar/constants';
import { SCOPE_BLOB, SEARCH_TYPE_ZOEKT, LABEL_FILTER_PARAM } from '~/search/sidebar/constants';
import {
GROUPS_LOCAL_STORAGE_KEY,
PROJECTS_LOCAL_STORAGE_KEY,

View File

@ -1,14 +1,14 @@
import { statusFilterData } from '~/search/sidebar/components/status_filter/data';
import { confidentialFilterData } from '~/search/sidebar/components/confidentiality_filter/data';
import { languageFilterData } from '~/search/sidebar/components/language_filter/data';
import { LABEL_FILTER_PARAM } from '~/search/sidebar/components/label_filter/data';
import { archivedFilterData } from '~/search/sidebar/components/archived_filter/data';
import { INCLUDE_FORKED_FILTER_PARAM } from '~/search/sidebar/components/forks_filter/index.vue';
import { s__ } from '~/locale';
import {
CONFIDENTAL_FILTER_PARAM,
INCLUDE_ARCHIVED_FILTER_PARAM,
LABEL_FILTER_PARAM,
INCLUDE_FORKED_FILTER_PARAM,
LANGUAGE_FILTER_PARAM,
SOURCE_BRANCH_PARAM,
NOT_SOURCE_BRANCH_PARAM,
} from '~/search/sidebar/components/source_branch_filter/index.vue';
STATE_FILTER_PARAM,
} from '~/search/sidebar/constants';
export const MAX_FREQUENT_ITEMS = 5;
@ -19,11 +19,11 @@ export const GROUPS_LOCAL_STORAGE_KEY = 'global-search-frequent-groups';
export const PROJECTS_LOCAL_STORAGE_KEY = 'global-search-frequent-projects';
export const SIDEBAR_PARAMS = [
statusFilterData.filterParam,
confidentialFilterData.filterParam,
languageFilterData.filterParam,
STATE_FILTER_PARAM,
CONFIDENTAL_FILTER_PARAM,
LANGUAGE_FILTER_PARAM,
LABEL_FILTER_PARAM,
archivedFilterData.filterParam,
INCLUDE_ARCHIVED_FILTER_PARAM,
INCLUDE_FORKED_FILTER_PARAM,
SOURCE_BRANCH_PARAM,
NOT_SOURCE_BRANCH_PARAM,

View File

@ -1,16 +1,16 @@
import { findKey, intersection, difference } from 'lodash';
import { languageFilterData } from '~/search/sidebar/components/language_filter/data';
import {
LABEL_FILTER_PARAM,
LABEL_AGREGATION_NAME,
} from '~/search/sidebar/components/label_filter/data';
import {
formatSearchResultCount,
addCountOverLimit,
injectRegexSearch,
} from '~/search/store/utils';
import { SCOPE_BLOB } from '~/search/sidebar/constants';
import {
SCOPE_BLOB,
LABEL_FILTER_PARAM,
LABEL_AGREGATION_NAME,
LANGUAGE_FILTER_PARAM,
} from '~/search/sidebar/constants';
import { GROUPS_LOCAL_STORAGE_KEY, PROJECTS_LOCAL_STORAGE_KEY, ICON_MAP } from './constants';
const queryLabelFilters = (state) => state?.query?.[LABEL_FILTER_PARAM] || [];
@ -28,7 +28,7 @@ const unappliedNewLabelKeys = (state) => {
);
};
export const queryLanguageFilters = (state) => state.query[languageFilterData.filterParam] || [];
export const queryLanguageFilters = (state) => state.query[LANGUAGE_FILTER_PARAM] || [];
export const frequentGroups = (state) => {
return state.frequentItems[GROUPS_LOCAL_STORAGE_KEY];
@ -40,9 +40,8 @@ export const frequentProjects = (state) => {
export const languageAggregationBuckets = (state) => {
return (
state.aggregations.data.find(
(aggregation) => aggregation.name === languageFilterData.filterParam,
)?.buckets || []
state.aggregations.data.find((aggregation) => aggregation.name === LANGUAGE_FILTER_PARAM)
?.buckets || []
);
};

View File

@ -2,8 +2,7 @@ import { isEqual, orderBy, isEmpty } from 'lodash';
import AccessorUtilities from '~/lib/utils/accessor';
import { formatNumber } from '~/locale';
import { joinPaths, queryToObject, objectToQuery, getBaseURL } from '~/lib/utils/url_utility';
import { languageFilterData } from '~/search/sidebar/components/language_filter/data';
import { LABEL_AGREGATION_NAME } from '~/search/sidebar/components/label_filter/data';
import { LABEL_AGREGATION_NAME, LANGUAGE_FILTER_PARAM } from '~/search/sidebar/constants';
import {
MAX_FREQUENT_ITEMS,
MAX_FREQUENCY,
@ -13,8 +12,6 @@ import {
LS_REGEX_HANDLE,
} from './constants';
const LANGUAGE_AGGREGATION_NAME = languageFilterData.filterParam;
function extractKeys(object, keyList) {
return Object.fromEntries(keyList.map((key) => [key, object[key]]));
}
@ -141,7 +138,7 @@ export const getAggregationsUrl = () => {
};
const sortLanguages = (state, entries) => {
const queriedLanguages = state.query?.[LANGUAGE_AGGREGATION_NAME] || [];
const queriedLanguages = state.query?.[LANGUAGE_FILTER_PARAM] || [];
if (!Array.isArray(queriedLanguages) || !queriedLanguages.length) {
return entries;
@ -160,7 +157,7 @@ const getUniqueNamesOnly = (items) => {
export const prepareSearchAggregations = (state, aggregationData) =>
aggregationData.map((item) => {
if (item?.name === LANGUAGE_AGGREGATION_NAME) {
if (item?.name === LANGUAGE_FILTER_PARAM) {
return {
...item,
buckets: sortLanguages(state, item.buckets),

View File

@ -2,7 +2,7 @@ import SearchAndSortBar from './search_and_sort_bar.vue';
export default {
component: SearchAndSortBar,
title: 'usage_quotas/components/search_bar',
title: 'usage_quotas/search_bar',
};
const Template = (_, { argTypes }) => ({

View File

@ -112,14 +112,12 @@ input[type='file'] {
}
.info-well {
background: $gray-10;
color: $gl-text-color;
border: 1px solid $border-color;
border-radius: 4px;
@apply gl-bg-subtle gl-text-default gl-border gl-border-section gl-rounded-base;
margin-bottom: 16px;
.ref-list {
border-color: $gray-100;
border-color: var(--gl-border-color-section);
}
.well-segment {
@ -133,7 +131,7 @@ input[type='file'] {
}
&:not(:first-of-type) {
border-top: 1px solid $gray-100;
@apply gl-border-t gl-border-t-section;
}
p,

View File

@ -1,8 +1,5 @@
.info-well {
background: $gray-10;
color: $gl-text-color;
border: 1px solid $gray-100;
border-radius: $gl-border-radius-base;
@apply gl-bg-subtle gl-text-default gl-border gl-border-section gl-rounded-base;
.icon-container {
display: inline-block;

View File

@ -44,6 +44,7 @@ class Projects::BlobController < Projects::ApplicationController
before_action do
push_frontend_feature_flag(:explain_code_chat, current_user)
push_frontend_feature_flag(:upgrade_pdfjs, current_user)
push_licensed_feature(:file_locks) if @project.licensed_feature_available?(:file_locks)
end

View File

@ -19,6 +19,7 @@ class Projects::TreeController < Projects::ApplicationController
before_action do
push_frontend_feature_flag(:explain_code_chat, current_user)
push_frontend_feature_flag(:upgrade_pdfjs, current_user)
push_licensed_feature(:file_locks) if @project.licensed_feature_available?(:file_locks)
end

View File

@ -38,6 +38,7 @@ class ProjectsController < Projects::ApplicationController
before_action do
push_frontend_feature_flag(:remove_monitor_metrics, @project)
push_frontend_feature_flag(:explain_code_chat, current_user)
push_frontend_feature_flag(:upgrade_pdfjs, current_user)
push_frontend_feature_flag(:issue_email_participants, @project)
push_frontend_feature_flag(:edit_branch_rules, @project)
# TODO: We need to remove the FF eventually when we rollout page_specific_styles

View File

@ -0,0 +1,10 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Merge request approval policy `policy_tuning`",
"type": "object",
"properties": {
"unblock_rules_using_execution_policies": {
"type": "boolean"
}
}
}

View File

@ -30,6 +30,6 @@
= f.label :scopes
= render 'shared/tokens/scopes_form', prefix: 'doorkeeper_application', token: application, scopes: @scopes, f: f
.gl-mt-5
.settings-sticky-footer.gl-flex.gl-gap-3
= f.submit _('Save application'), pajamas_button: true, data: { testid: 'save-application-button' }
= link_button_to _('Cancel'), admin_applications_path

View File

@ -1,10 +1,10 @@
- add_to_breadcrumbs _("Applications"), admin_applications_path
- breadcrumb_title @application.name
- add_to_breadcrumbs @application.name, admin_application_path(@application)
- breadcrumb_title _("Edit")
- page_title _("Edit"), @application.name, _("Applications")
= render 'web_ide_oauth_application_callout', application: @application
%h1.page-title.gl-text-size-h-display
= _('Edit application')
= render ::Layouts::PageHeadingComponent.new(_('Edit application'))
- @url = admin_application_path(@application)
= render 'form', application: @application

View File

@ -1,7 +1,7 @@
- add_to_breadcrumbs _("Applications"), admin_applications_path
- breadcrumb_title _("Add new application")
- page_title _("Add new application")
%h1.page-title.gl-text-size-h-display
= _("Add new application")
= render ::Layouts::PageHeadingComponent.new(_("Add new application"))
- @url = admin_applications_path
= render 'form', application: @application

View File

@ -1,7 +1,7 @@
- page_title @application.name, _("Applications")
- add_to_breadcrumbs _("Applications"), admin_applications_path
- page_title @application.name
%h1.page-title.gl-text-size-h-display
Application: #{@application.name}
= render ::Layouts::PageHeadingComponent.new(_('Application: %{application_name}') % { application_name: @application.name })
= render 'shared/doorkeeper/applications/show',
edit_path: edit_admin_application_path(@application),

View File

@ -2,7 +2,6 @@
- add_to_breadcrumbs @label.name, admin_labels_path
- breadcrumb_title _("Edit")
- page_title _("Edit"), @label.name, _("Labels")
%h1.page-title.gl-text-size-h-display
= _('Edit label')
= render ::Layouts::PageHeadingComponent.new(_('Edit label'))
= render 'shared/labels/form', url: admin_label_path(@label), back_path: admin_labels_path

View File

@ -1,4 +1,6 @@
- add_to_breadcrumbs _("Labels"), admin_labels_path
- breadcrumb_title _("New label")
- page_title _("New Label")
%h1.page-title.gl-text-size-h-display
= _('New Label')
= render ::Layouts::PageHeadingComponent.new(_('New label'))
= render 'shared/labels/form', url: admin_labels_path, back_path: admin_labels_path

View File

@ -37,7 +37,7 @@
%span.gl-text-primary= n_('parent', 'parents', @commit.parents.count)
- @commit.parents.each do |parent|
= link_to parent.short_id, project_commit_path(@project, parent), class: "commit-sha"
#js-commit-branches-and-tags.gl-border-t{ data: { full_path: @project.full_path, commit_sha: @commit.short_id } }
#js-commit-branches-and-tags.gl-border-t.gl-border-t-section{ data: { full_path: @project.full_path, commit_sha: @commit.short_id } }
.well-segment
= sprite_icon('branch', css_class: "gl-ml-2 gl-mr-3")
= gl_loading_icon(inline: true, css_class: 'gl-align-middle')

View File

@ -40,12 +40,12 @@
= render "shared/tokens/scopes_list", token: @application
.gl-flex.gl-justify-between
.gl-flex.gl-gap-3
= render 'shared/doorkeeper/applications/delete_form', path: delete_path
%div
- if @created
= link_button_to _('Continue'), index_path, class: 'gl-mr-3', variant: :confirm
= link_button_to _('Edit'), edit_path
= render 'shared/doorkeeper/applications/delete_form', path: delete_path
-# Create a hidden field to save the ID of application created
= hidden_field_tag(:id_of_application, @application.id, data: { testid: 'id-of-application-field' })

View File

@ -24,6 +24,7 @@ const plugins = [
// See: https://gitlab.com/gitlab-org/gitlab/-/issues/336216
'@babel/plugin-transform-nullish-coalescing-operator',
'lodash',
'@babel/plugin-transform-class-static-block',
];
// Jest is running in node environment

View File

@ -5,5 +5,5 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/159610
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/483039
milestone: '17.5'
group: group::static analysis
type: wip
default_enabled: false
type: beta
default_enabled: true

View File

@ -6,4 +6,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/465776
milestone: '17.2'
group: group::static analysis
type: beta
default_enabled: false
default_enabled: true

View File

@ -0,0 +1,9 @@
---
name: upgrade_pdfjs
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/462822
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/165557
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/491426
milestone: '17.5'
group: group::source code
type: gitlab_com_derisk
default_enabled: false

75
config/pdfjs.constants.js Normal file
View File

@ -0,0 +1,75 @@
const path = require('path');
const ROOT_PATH = path.resolve(__dirname, '..');
const WEBPACK_OUTPUT_PATH = path.join(ROOT_PATH, 'public/assets/webpack');
const WEBPACK_PUBLIC_PATH = '/assets/webpack/';
const PDFJS_PACKAGE_V3 = 'pdfjs-dist-v3';
const PDFJS_PACKAGE_V4 = 'pdfjs-dist-v4';
const PDF_JS_V3_VERSION = require('pdfjs-dist-v3/package.json').version;
const PDF_JS_V4_VERSION = require('pdfjs-dist-v4/package.json').version;
const PDF_JS_WORKER_V3_FILE_NAME = 'pdf.worker.min.js';
const PDF_JS_WORKER_V4_FILE_NAME = 'pdf.worker.min.mjs';
const PDF_JS_WORKER_V3_PATH = path.join('pdfjs', PDF_JS_V3_VERSION, '/');
const PDF_JS_WORKER_V4_PATH = path.join('pdfjs', PDF_JS_V4_VERSION, '/');
const PDF_JS_WORKER_V3_OUTPUT_PATH = path.join(WEBPACK_OUTPUT_PATH, PDF_JS_WORKER_V3_PATH);
const PDF_JS_WORKER_V4_OUTPUT_PATH = path.join(WEBPACK_OUTPUT_PATH, PDF_JS_WORKER_V4_PATH);
const PDF_JS_WORKER_V3_PUBLIC_PATH = path.join(
WEBPACK_PUBLIC_PATH,
PDF_JS_WORKER_V3_PATH,
PDF_JS_WORKER_V3_FILE_NAME,
);
const PDF_JS_WORKER_V4_PUBLIC_PATH = path.join(
WEBPACK_PUBLIC_PATH,
PDF_JS_WORKER_V4_PATH,
PDF_JS_WORKER_V4_FILE_NAME,
);
const PDF_JS_CMAPS_V3_PATH = path.join('pdfjs', PDF_JS_V3_VERSION, 'cmaps/');
const PDF_JS_CMAPS_V4_PATH = path.join('pdfjs', PDF_JS_V4_VERSION, 'cmaps/');
const PDF_JS_CMAPS_V3_OUTPUT_PATH = path.join(WEBPACK_OUTPUT_PATH, PDF_JS_CMAPS_V3_PATH);
const PDF_JS_CMAPS_V4_OUTPUT_PATH = path.join(WEBPACK_OUTPUT_PATH, PDF_JS_CMAPS_V4_PATH);
const PDF_JS_CMAPS_V3_PUBLIC_PATH = path.join(WEBPACK_PUBLIC_PATH, PDF_JS_CMAPS_V3_PATH);
const PDF_JS_CMAPS_V4_PUBLIC_PATH = path.join(WEBPACK_PUBLIC_PATH, PDF_JS_CMAPS_V4_PATH);
const pdfJsCopyFilesPatterns = [
{
from: path.join(ROOT_PATH, 'node_modules', PDFJS_PACKAGE_V4, 'cmaps'),
to: PDF_JS_CMAPS_V4_OUTPUT_PATH,
},
{
from: path.join(ROOT_PATH, 'node_modules', PDFJS_PACKAGE_V3, 'cmaps'),
to: PDF_JS_CMAPS_V3_OUTPUT_PATH,
},
{
from: path.join(
ROOT_PATH,
'node_modules',
PDFJS_PACKAGE_V4,
'legacy',
'build',
PDF_JS_WORKER_V4_FILE_NAME,
),
to: PDF_JS_WORKER_V4_OUTPUT_PATH,
},
{
from: path.join(
ROOT_PATH,
'node_modules',
PDFJS_PACKAGE_V3,
'legacy',
'build',
PDF_JS_WORKER_V3_FILE_NAME,
),
to: PDF_JS_WORKER_V3_OUTPUT_PATH,
},
];
module.exports = {
pdfJsCopyFilesPatterns,
PDF_JS_WORKER_V3_PUBLIC_PATH,
PDF_JS_WORKER_V4_PUBLIC_PATH,
PDF_JS_CMAPS_V3_PUBLIC_PATH,
PDF_JS_CMAPS_V4_PUBLIC_PATH,
};

View File

@ -41,11 +41,15 @@ const {
WEBPACK_OUTPUT_PATH,
WEBPACK_PUBLIC_PATH,
SOURCEGRAPH_PUBLIC_PATH,
PDF_JS_WORKER_PUBLIC_PATH,
PDF_JS_CMAPS_PUBLIC_PATH,
GITLAB_WEB_IDE_PUBLIC_PATH,
copyFilesPatterns,
} = require('./webpack.constants');
const {
PDF_JS_WORKER_V3_PUBLIC_PATH,
PDF_JS_WORKER_V4_PUBLIC_PATH,
PDF_JS_CMAPS_V3_PUBLIC_PATH,
PDF_JS_CMAPS_V4_PUBLIC_PATH,
} = require('./pdfjs.constants');
const { generateEntries } = require('./webpack.helpers');
const createIncrementalWebpackCompiler = require('./helpers/incremental_webpack_compiler');
@ -303,7 +307,7 @@ module.exports = {
},
resolve: {
extensions: ['.js'],
extensions: ['.mjs', '.js'],
alias,
},
@ -312,20 +316,25 @@ module.exports = {
rules: [
{
type: 'javascript/auto',
exclude: /pdfjs-dist/,
exclude: /pdfjs-dist-v[34]/,
test: /\.mjs$/,
use: [],
},
{
test: /(pdfjs).*\.js?$/,
test: /(pdfjs).*\.m?js?$/,
type: 'javascript/auto',
include: /node_modules/,
use: [
{
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env', { targets: { esmodules: true }, modules: 'commonjs' }],
],
plugins: [
'@babel/plugin-transform-optional-chaining',
'@babel/plugin-transform-logical-assignment-operators',
'@babel/plugin-transform-classes',
],
...defaultJsOptions,
},
@ -736,6 +745,14 @@ module.exports = {
});
}),
new webpack.ContextReplacementPlugin(/^\.$/, (context) => {
if (/\/node_modules\/pdfjs-dist-v[34]/.test(context.context)) {
for (const d of context.dependencies) {
if (d.critical) d.critical = false;
}
}
}),
!IS_JH &&
new webpack.NormalModuleReplacementPlugin(/^jh_component\/(.*)\.vue/, (resource) => {
// eslint-disable-next-line no-param-reassign
@ -860,10 +877,12 @@ module.exports = {
// This is used by Sourcegraph because these assets are loaded dnamically
'process.env.SOURCEGRAPH_PUBLIC_PATH': JSON.stringify(SOURCEGRAPH_PUBLIC_PATH),
'process.env.GITLAB_WEB_IDE_PUBLIC_PATH': JSON.stringify(GITLAB_WEB_IDE_PUBLIC_PATH),
'process.env.PDF_JS_WORKER_PUBLIC_PATH': JSON.stringify(PDF_JS_WORKER_PUBLIC_PATH),
'process.env.PDF_JS_CMAPS_PUBLIC_PATH': JSON.stringify(PDF_JS_CMAPS_PUBLIC_PATH),
'window.IS_VITE': JSON.stringify(false),
...(IS_PRODUCTION ? {} : { LIVE_RELOAD: DEV_SERVER_LIVERELOAD }),
'process.env.PDF_JS_WORKER_V3_PUBLIC_PATH': JSON.stringify(PDF_JS_WORKER_V3_PUBLIC_PATH),
'process.env.PDF_JS_WORKER_V4_PUBLIC_PATH': JSON.stringify(PDF_JS_WORKER_V4_PUBLIC_PATH),
'process.env.PDF_JS_CMAPS_V3_PUBLIC_PATH': JSON.stringify(PDF_JS_CMAPS_V3_PUBLIC_PATH),
'process.env.PDF_JS_CMAPS_V4_PUBLIC_PATH': JSON.stringify(PDF_JS_CMAPS_V4_PUBLIC_PATH),
}),
/* Pikaday has a optional dependency to moment.

View File

@ -11,47 +11,20 @@ const SOURCEGRAPH_PUBLIC_PATH = path.join(WEBPACK_PUBLIC_PATH, SOURCEGRAPH_PATH)
const GITLAB_WEB_IDE_VERSION = require('@gitlab/web-ide/package.json').version;
const { pdfJsCopyFilesPatterns } = require('./pdfjs.constants');
const GITLAB_WEB_IDE_PATH = path.join('gitlab-vscode', GITLAB_WEB_IDE_VERSION, '/');
const GITLAB_WEB_IDE_OUTPUT_PATH = path.join(WEBPACK_OUTPUT_PATH, GITLAB_WEB_IDE_PATH);
const GITLAB_WEB_IDE_PUBLIC_PATH = path.join(WEBPACK_PUBLIC_PATH, GITLAB_WEB_IDE_PATH);
const PDF_JS_VERSION = require('pdfjs-dist/package.json').version;
const PDF_JS_WORKER_FILE_NAME = 'pdf.worker.min.js';
const PDF_JS_WORKER_PATH = path.join('pdfjs', PDF_JS_VERSION, '/');
const PDF_JS_WORKER_OUTPUT_PATH = path.join(WEBPACK_OUTPUT_PATH, PDF_JS_WORKER_PATH);
const PDF_JS_WORKER_PUBLIC_PATH = path.join(
WEBPACK_PUBLIC_PATH,
PDF_JS_WORKER_PATH,
PDF_JS_WORKER_FILE_NAME,
);
const PDF_JS_CMAPS_PATH = path.join('pdfjs', PDF_JS_VERSION, 'cmaps/');
const PDF_JS_CMAPS_OUTPUT_PATH = path.join(WEBPACK_OUTPUT_PATH, PDF_JS_CMAPS_PATH);
const PDF_JS_CMAPS_PUBLIC_PATH = path.join(WEBPACK_PUBLIC_PATH, PDF_JS_CMAPS_PATH);
const IS_EE = require('./helpers/is_ee_env');
const IS_JH = require('./helpers/is_jh_env');
const SOURCEGRAPH_PACKAGE = '@sourcegraph/code-host-integration';
const GITLAB_WEB_IDE_PACKAGE = '@gitlab/web-ide';
const PDFJS_PACKAGE = 'pdfjs-dist';
const copyFilesPatterns = [
{
from: path.join(ROOT_PATH, 'node_modules', PDFJS_PACKAGE, 'cmaps'),
to: PDF_JS_CMAPS_OUTPUT_PATH,
},
{
from: path.join(
ROOT_PATH,
'node_modules',
PDFJS_PACKAGE,
'legacy',
'build',
PDF_JS_WORKER_FILE_NAME,
),
to: PDF_JS_WORKER_OUTPUT_PATH,
},
...pdfJsCopyFilesPatterns,
{
from: path.join(ROOT_PATH, 'node_modules', SOURCEGRAPH_PACKAGE, '/'),
to: SOURCEGRAPH_OUTPUT_PATH,
@ -71,8 +44,6 @@ module.exports = {
ROOT_PATH,
WEBPACK_OUTPUT_PATH,
WEBPACK_PUBLIC_PATH,
PDF_JS_WORKER_PUBLIC_PATH,
PDF_JS_CMAPS_PUBLIC_PATH,
SOURCEGRAPH_PUBLIC_PATH,
GITLAB_WEB_IDE_PUBLIC_PATH,
copyFilesPatterns,

View File

@ -0,0 +1,10 @@
# frozen_string_literal: true
class AddPolicyTuningToPolicies < Gitlab::Database::Migration[2.2]
enable_lock_retries!
milestone '17.6'
def change
add_column :scan_result_policies, :policy_tuning, :jsonb, null: false, default: {}
end
end

View File

@ -0,0 +1 @@
1f0ac3229c91252bffb7fb11d3ea610c11e4f56dc62affa6d9eb1be3e22c0d19

View File

@ -18978,6 +18978,7 @@ CREATE TABLE scan_result_policies (
commits smallint,
send_bot_message jsonb DEFAULT '{}'::jsonb NOT NULL,
fallback_behavior jsonb DEFAULT '{}'::jsonb NOT NULL,
policy_tuning jsonb DEFAULT '{}'::jsonb NOT NULL,
CONSTRAINT age_value_null_or_positive CHECK (((age_value IS NULL) OR (age_value >= 0))),
CONSTRAINT check_scan_result_policies_rule_idx_positive CHECK (((rule_idx IS NULL) OR (rule_idx >= 0)))
);

View File

@ -313,6 +313,7 @@ It is also possible to specify a [custom CI/CD configuration file for a specific
## Set CI/CD limits
> - **Maximum number of active pipelines per project** setting [removed](https://gitlab.com/gitlab-org/gitlab/-/issues/368195) in GitLab 16.0.
> - **Maximum number of jobs in a single pipeline** setting [moved](https://gitlab.com/gitlab-org/gitlab/-/issues/287669) from GitLab Enterprise Edition to GitLab Community Edition in 17.6.
You can configure some [CI/CD limits](../../administration/instance_limits.md#cicd-limits)
from the **Admin** area:

View File

@ -57,7 +57,7 @@ Example response:
"commit": {
"id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c",
"short_id": "7b5c3cc",
"created_at": "2012-06-28T03:44:20-07:00",
"created_at": "2024-06-28T03:44:20-07:00",
"parent_ids": [
"4ad91d3c1144c406e50c7b33bae684bd6837faf8"
],
@ -65,11 +65,12 @@ Example response:
"message": "add projects API",
"author_name": "John Smith",
"author_email": "john@example.com",
"authored_date": "2012-06-27T05:51:39-07:00",
"authored_date": "2024-06-27T05:51:39-07:00",
"committer_name": "John Smith",
"committer_email": "john@example.com",
"committed_date": "2012-06-28T03:44:20-07:00",
"committed_date": "2024-06-28T03:44:20-07:00",
"trailers": {},
"extended_trailers": {},
"web_url": "https://gitlab.example.com/my-group/my-project/-/commit/7b5c3cc8be40ee161ae89a06bba6229da1032a0c"
}
},

View File

@ -88,7 +88,7 @@ In each example, replace:
### Use a CI/CD job
You can use a CI/CD job with a pipeline triggers token to trigger pipelines when another pipeline
You can use a CI/CD job with a pipeline trigger token to trigger pipelines when another pipeline
runs.
For example, to trigger a pipeline on the `main` branch of `project-B` when a tag

View File

@ -110,6 +110,39 @@ Chat is not aware of pipelines or commits. However, you can use
[root cause analysis](#troubleshoot-failed-cicd-jobs-with-root-cause-analysis)
to troubleshoot the jobs in your pipeline.
## Ask about a specific commit
DETAILS:
**Tier:** Ultimate with GitLab Duo Enterprise - [Start a trial](https://about.gitlab.com/solutions/gitlab-duo-pro/sales/?type=free-trial)
**Offering:** GitLab.com, Self-managed, GitLab Dedicated
**Editors:** GitLab UI
**LLM:** Anthropic [Claude 3.5 Sonnet](https://console.cloud.google.com/vertex-ai/publishers/anthropic/model-garden/claude-3-5-sonnet)
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/468460) in GitLab 17.6.
You can ask about a specific GitLab commit. For example:
- `Generate a summary for the commit identified with this link: <link to your commit>`
- `How can I improve the description of this commit?`
- When you are viewing a commit in GitLab, you can ask `Generate a summary of the current commit.`
## Ask about a specific pipeline job
DETAILS:
**Tier:** Ultimate with GitLab Duo Enterprise - [Start a trial](https://about.gitlab.com/solutions/gitlab-duo-pro/sales/?type=free-trial)
**Offering:** GitLab.com, Self-managed, GitLab Dedicated
**Editors:** GitLab UI
**LLM:** Anthropic [Claude 3.5 Sonnet](https://console.cloud.google.com/vertex-ai/publishers/anthropic/model-garden/claude-3-5-sonnet)
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/468461) in GitLab 17.6.
You can ask about a specific GitLab pipeline job. For example:
- `Generate a summary for the pipeline job identified via this link: <link to your pipeline job>`
- `Can you suggest ways to fix this failed pipeline job?`
- `What are the main steps executed in this pipeline job?`
- When you are viewing a pipeline job in GitLab, you can ask `Generate a summary of the current pipeline job.`
## Explain selected code
DETAILS:

View File

@ -46,6 +46,8 @@ In the GitLab UI, GitLab Duo Chat knows about these areas:
| Issues | From the issue, ask about `this issue`, `this`, or the URL. From any UI area, ask about the URL. |
| Code files | From the single file, ask about `this code` or `this file`. From any UI area, ask about the URL. |
| Merge requests | From the merge request, ask about `this merge request`, `this`, or the URL. For more information, see [Ask about a specific merge request](examples.md#ask-about-a-specific-merge-request). |
| Commits | From the commit, ask about `this commit` or `this`. From any UI area, ask about the URL. |
| Pipeline jobs | From the pipeline job, ask about `this pipeline job` or `this`. From any UI area, ask about the URL. |
In the IDEs, GitLab Duo Chat knows about these areas:

View File

@ -218,6 +218,7 @@ module.exports = (path, options = {}) => {
'monaco-marker-data-provider',
'monaco-worker-manager',
'fast-mersenne-twister',
'pdfjs-dist-v4',
'prosemirror-markdown',
'marked',
'fault',
@ -256,7 +257,7 @@ module.exports = (path, options = {}) => {
transform: {
'^.+\\.(gql|graphql)$': './spec/frontend/__helpers__/graphql_transformer.js',
'^.+_worker\\.js$': './spec/frontend/__helpers__/web_worker_transformer.js',
'^.+\\.js$': 'babel-jest',
'^.+\\.m?js$': 'babel-jest',
'^.+\\.vue$': VUE_JEST_TRANSFORMER,
'spec/frontend/editor/schema/ci/yaml_tests/.+\\.(yml|yaml)$':
'./spec/frontend/__helpers__/yaml_transformer.js',

View File

@ -6,12 +6,40 @@ module Gitlab
module Chain
module Limit
class Size < Chain::Base
include ::Gitlab::Ci::Pipeline::Chain::Helpers
def initialize(*)
super
@limit = Gitlab::Ci::Pipeline::Quota::Size
.new(project.namespace, pipeline, command)
end
def perform!
# to be overridden in EE
if limit.exceeded?
limit.log_error!(log_attrs)
error(limit.message, failure_reason: :size_limit_exceeded)
elsif limit.log_exceeded_limit?
limit.log_error!(log_attrs)
end
end
def break?
false # to be overridden in EE
limit.exceeded?
end
private
attr_reader :limit
def log_attrs
{
jobs_count: pipeline.statuses.count,
pipeline_source: pipeline.source,
plan: project.actual_plan_name,
project_id: project.id,
project_full_path: project.full_path
}
end
end
end
@ -19,5 +47,3 @@ module Gitlab
end
end
end
Gitlab::Ci::Pipeline::Chain::Limit::Size.prepend_mod_with('Gitlab::Ci::Pipeline::Chain::Limit::Size')

View File

@ -0,0 +1,54 @@
# frozen_string_literal: true
module Gitlab
module Ci
module Pipeline
module Quota
class Size < ::Gitlab::Ci::Limit
include ::Gitlab::Utils::StrongMemoize
include ActionView::Helpers::TextHelper
LOGGABLE_JOBS_COUNT = 2000 # log large pipelines to determine a future global pipeline size limit
def initialize(namespace, pipeline, command)
@namespace = namespace
@pipeline = pipeline
@command = command
end
def enabled?
ci_pipeline_size_limit > 0
end
def exceeded?
return false unless enabled?
seeds_size > ci_pipeline_size_limit
end
def log_exceeded_limit?
seeds_size > LOGGABLE_JOBS_COUNT
end
def message
doc_link = Rails.application.routes.url_helpers.help_page_url('ci/debugging.md',
anchor: 'pipeline-with-many-jobs-fails-to-start')
"The number of jobs has exceeded the limit of #{ci_pipeline_size_limit}. " \
"Try splitting the configuration with parent-child-pipelines #{doc_link}"
end
private
def ci_pipeline_size_limit
@namespace.actual_limits.ci_pipeline_size
end
strong_memoize_attr :ci_pipeline_size_limit
def seeds_size
@command.pipeline_seed.size
end
end
end
end
end
end

View File

@ -5762,9 +5762,6 @@ msgstr ""
msgid "Allows projects to track errors using an Opstrace integration."
msgstr ""
msgid "Almost there"
msgstr ""
msgid "Almost there..."
msgstr ""
@ -6748,6 +6745,9 @@ msgstr ""
msgid "Application was successfully updated."
msgstr ""
msgid "Application: %{application_name}"
msgstr ""
msgid "Application: %{name}"
msgstr ""
@ -8705,9 +8705,6 @@ msgstr ""
msgid "BillingPlans|Introducing GitLab Duo Enterprise"
msgstr ""
msgid "BillingPlans|Learn more about each plan by reading our %{faq_link}, or start a free 30-day trial of GitLab.com Ultimate."
msgstr ""
msgid "BillingPlans|Learn more about each plan by visiting our %{pricing_page_link}."
msgstr ""
@ -8753,9 +8750,6 @@ msgstr ""
msgid "BillingPlans|Priority support"
msgstr ""
msgid "BillingPlans|Ready to explore the value of paid features today?"
msgstr ""
msgid "BillingPlans|Recommended"
msgstr ""
@ -8774,12 +8768,6 @@ msgstr ""
msgid "BillingPlans|Start a free GitLab Duo Pro trial"
msgstr ""
msgid "BillingPlans|Start a free Ultimate trial"
msgstr ""
msgid "BillingPlans|Start a free Ultimate trial. No credit card required."
msgstr ""
msgid "BillingPlans|Start an Ultimate trial with GitLab Duo Enterprise to try the complete set of features from GitLab. GitLab Duo Enterprise gives you access to the full product offering from GitLab, including AI-powered features."
msgstr ""
@ -8858,9 +8846,6 @@ msgstr ""
msgid "BillingPlans|billed annually at %{price_per_year}"
msgstr ""
msgid "BillingPlans|frequently asked questions"
msgstr ""
msgid "BillingPlans|group"
msgstr ""
@ -21221,9 +21206,6 @@ msgstr ""
msgid "Enabled OAuth authentication sources"
msgstr ""
msgid "Enables a free GitLab Ultimate trial when you create a new project."
msgstr ""
msgid "Enables a free Ultimate + GitLab Duo Enterprise trial when you create a new project."
msgstr ""
@ -23972,9 +23954,6 @@ msgstr ""
msgid "Frameworks"
msgstr ""
msgid "Free Trial of GitLab.com Ultimate"
msgstr ""
msgid "Free Ultimate Trial with GitLab Duo Enterprise"
msgstr ""
@ -24007,7 +23986,7 @@ msgstr ""
msgid "FreeUserCap|Start a trial:"
msgstr ""
msgid "FreeUserCap|To remove the %{link_start}read-only%{link_end} state and regain write access, you can reduce the number of users in your top-level group to %{free_user_limit} users or less. You can also %{upgrade_start}upgrade%{upgrade_end} to a paid tier, which do not have user limits. If you need additional time, you can %{trial_start}start a free 30-day trial%{trial_end} which includes unlimited users."
msgid "FreeUserCap|To remove the %{link_start}read-only%{link_end} state and regain write access, you can reduce the number of users in your top-level group to %{free_user_limit} users or less. You can also %{upgrade_start}upgrade%{upgrade_end} to a paid tier, which do not have user limits. If you need additional time, you can %{trial_start}start a free 60-day trial%{trial_end} which includes unlimited users."
msgstr ""
msgid "FreeUserCap|Upgrade:"
@ -28567,27 +28546,15 @@ msgstr ""
msgid "In use"
msgstr ""
msgid "InProductMarketing|%{upper_start}Free 30-day trial%{upper_end} %{lower_start}GitLab Ultimate%{lower_end}"
msgstr ""
msgid "InProductMarketing|%{upper_start}Free 60-day trial%{upper_end} %{lower_start}GitLab Ultimate & GitLab Duo Enterprise%{lower_end} %{last_start}Sign up for a free trial of the most comprehensive AI-powered DevSecOps Platform%{last_end}"
msgstr ""
msgid "InProductMarketing|Accelerate your digital transformation"
msgstr ""
msgid "InProductMarketing|Boost efficiency and collaboration"
msgstr ""
msgid "InProductMarketing|Build in security"
msgstr ""
msgid "InProductMarketing|Built-in security"
msgstr ""
msgid "InProductMarketing|Deliver software faster"
msgstr ""
msgid "InProductMarketing|End-to-end security and compliance"
msgstr ""
@ -28600,9 +28567,6 @@ msgstr ""
msgid "InProductMarketing|GitLab Duo Enterprise: AI across the software development lifecycle"
msgstr ""
msgid "InProductMarketing|Improve collaboration and visibility"
msgstr ""
msgid "InProductMarketing|Invite unlimited colleagues"
msgstr ""
@ -28621,9 +28585,6 @@ msgstr ""
msgid "InProductMarketing|Start a Self-Managed trial"
msgstr ""
msgid "InProductMarketing|Start your 30-day free trial"
msgstr ""
msgid "InProductMarketing|Start your 60-day free trial"
msgstr ""
@ -32188,7 +32149,7 @@ msgstr ""
msgid "LearnGitLab|Try GitLab Ultimate for free"
msgstr ""
msgid "LearnGitLab|Try all GitLab features for 30 days, no credit card required."
msgid "LearnGitLab|Try all GitLab features for 60 days, no credit card required."
msgstr ""
msgid "LearnGitLab|Use GitLab to deploy your application, monitor its health, and keep it secure:"
@ -53192,9 +53153,6 @@ msgstr ""
msgid "Start time"
msgstr ""
msgid "Start your Free Ultimate Trial"
msgstr ""
msgid "Start your Free Ultimate and GitLab Duo Enterprise Trial"
msgstr ""
@ -56712,7 +56670,7 @@ msgstr ""
msgid "TierBadgePopover|This project uses the %{tier} GitLab tier. %{copyEnd}"
msgstr ""
msgid "TierBadgePopover|Want to enhance team productivity and access advanced features like Merge Approvals, Push rules, Epics, Code Review Analytics, and Container Scanning? Try all GitLab has to offer for free for 30 days. No credit card required."
msgid "TierBadgePopover|Want to enhance team productivity and access advanced features like Merge Approvals, Push rules, Epics, Code Review Analytics, and Container Scanning? Try all GitLab has to offer for free for 60 days. No credit card required."
msgstr ""
msgid "TierBadge|Free"
@ -57217,8 +57175,8 @@ msgstr ""
msgid "To get started, use the link below to confirm your account."
msgstr ""
msgid "To invite more users, you can reduce the number of users in your top-level group to %{free_limit} user or less. You can also upgrade to a paid tier which do not have user limits. If you need additional time, you can start a free 30-day trial which includes unlimited users."
msgid_plural "To invite more users, you can reduce the number of users in your top-level group to %{free_limit} users or less. You can also upgrade to a paid tier which do not have user limits. If you need additional time, you can start a free 30-day trial which includes unlimited users."
msgid "To invite more users, you can reduce the number of users in your top-level group to %{free_limit} user or less. You can also upgrade to a paid tier which do not have user limits. If you need additional time, you can start a free 60-day trial which includes unlimited users."
msgid_plural "To invite more users, you can reduce the number of users in your top-level group to %{free_limit} users or less. You can also upgrade to a paid tier which do not have user limits. If you need additional time, you can start a free 60-day trial which includes unlimited users."
msgstr[0] ""
msgstr[1] ""
@ -57264,7 +57222,7 @@ msgstr ""
msgid "To remove the %{link_start}read-only%{link_end} state and regain write access, ask your top-level group owner(s) to reduce the number of users in your top-level group to %{free_limit} users or less, or to upgrade to a paid tier which do not have user limits."
msgstr ""
msgid "To remove the %{link_start}read-only%{link_end} state and regain write access, you can reduce the number of users in your top-level group to %{free_limit} users or less. You can also upgrade to a paid tier, which do not have user limits. If you need additional time, you can start a free 30-day trial which includes unlimited users."
msgid "To remove the %{link_start}read-only%{link_end} state and regain write access, you can reduce the number of users in your top-level group to %{free_limit} users or less. You can also upgrade to a paid tier, which do not have user limits. If you need additional time, you can start a free 60-day trial which includes unlimited users."
msgstr ""
msgid "To resolve the problem, refine your search criteria. Select a group or project or use double quotes for multiple keywords (for example, %{code_open}\"your search\"%{code_close})."
@ -58245,15 +58203,9 @@ msgstr ""
msgid "Trials|Create a new group and start your trial of Ultimate with GitLab Duo Enterprise."
msgstr ""
msgid "Trials|Create a new group to start your GitLab Ultimate trial."
msgstr ""
msgid "Trials|You can apply your trial of Ultimate with GitLab Duo Enterprise to a group."
msgstr ""
msgid "Trials|You can apply your trial to a new group or an existing group."
msgstr ""
msgid "Trial|Activate my trial"
msgstr ""
@ -58287,9 +58239,6 @@ msgstr ""
msgid "Trial|Select state or province"
msgstr ""
msgid "Trial|Start free GitLab Ultimate trial"
msgstr ""
msgid "Trial|Start free Ultimate + GitLab Duo Enterprise trial"
msgstr ""
@ -58299,12 +58248,6 @@ msgstr ""
msgid "Trial|To activate your trial, we need additional details from you."
msgstr ""
msgid "Trial|You don't need a credit card to start a trial. After the 30-day trial period, your account automatically becomes a GitLab Free account. You can use your GitLab Free account forever, or upgrade to a paid tier."
msgstr ""
msgid "Trial|Your GitLab Ultimate trial lasts for 30 days, but you can keep your free GitLab account forever. We just need some additional information to activate your trial."
msgstr ""
msgid "Trial|Your combined Ultimate and GitLab Duo Enterprise trial lasts for 60 days. We just need some additional information to activate it."
msgstr ""
@ -58380,9 +58323,6 @@ msgstr ""
msgid "Try again?"
msgstr ""
msgid "Try all GitLab has to offer for 30 days. No credit card required."
msgstr ""
msgid "Try grouping with different labels"
msgstr ""

View File

@ -193,7 +193,8 @@
"orderedmap": "^2.1.1",
"papaparse": "^5.3.1",
"patch-package": "6.5.1",
"pdfjs-dist": "^3.11.174",
"pdfjs-dist-v3": "npm:pdfjs-dist@3.11.174",
"pdfjs-dist-v4": "npm:pdfjs-dist@4.3.136",
"pikaday": "^1.8.0",
"pinia": "^2.2.2",
"popper.js": "^1.16.1",

View File

@ -338,7 +338,7 @@ tests = [
},
{
explanation: 'Internal Events test cases map to internal events tooling',
changed_file: 'spec/fixtures/scripts/internal_events/new_events.yml',
changed_file: 'spec/fixtures/scripts/internal_events/event_definer_examples.yml',
expected: %w[
spec/scripts/internal_events/cli_spec.rb
spec/support_specs/matchers/internal_events_matchers_spec.rb

View File

@ -4,7 +4,6 @@ FactoryBot.define do
factory :file_uploader do
skip_create
project
secret { nil }
transient do
@ -14,11 +13,12 @@ FactoryBot.define do
end
after(:build) do |uploader, evaluator|
uploader.store!(evaluator.file) if evaluator.project&.persisted?
uploader.store!(evaluator.file) if evaluator.model&.persisted?
end
initialize_with do
new(project, secret)
klass = container.is_a?(Group) ? NamespaceFileUploader : FileUploader
klass.new(container, nil, secret: secret)
end
end
end

View File

@ -1,12 +1,18 @@
import { shallowMount } from '@vue/test-utils';
import { GlobalWorkerOptions } from 'pdfjs-dist/legacy/build/pdf';
// eslint-disable-next-line import/extensions
import { GlobalWorkerOptions as GlobalWorkerOptionsV4 } from 'pdfjs-dist-v4/legacy/build/pdf.mjs';
import { GlobalWorkerOptions as GlobalWorkerOptionsV3 } from 'pdfjs-dist-v3/legacy/build/pdf';
import { FIXTURES_PATH } from 'spec/test_constants';
import PDFLab from '~/pdf/index.vue';
describe('PDFLab component', () => {
let wrapper;
const mountComponent = ({ pdf }) => shallowMount(PDFLab, { propsData: { pdf } });
const mountComponent = ({ pdf, flagValue = true }) =>
shallowMount(PDFLab, {
propsData: { pdf },
provide: { glFeatures: { upgradePdfjs: flagValue } },
});
describe('without PDF data', () => {
beforeEach(() => {
@ -19,18 +25,58 @@ describe('PDFLab component', () => {
});
describe('with PDF data', () => {
beforeEach(() => {
wrapper = mountComponent({ pdf: `${FIXTURES_PATH}/blob/pdf/test.pdf` });
describe('when upgradePdfjs flag is on', () => {
let mockGetDocument;
beforeEach(async () => {
mockGetDocument = jest.fn().mockReturnValue({ promise: Promise.resolve() });
jest.mock('pdfjs-dist-v4/legacy/build/pdf.mjs', () => ({
...jest.requireActual('pdfjs-dist-v4/legacy/build/pdf.mjs'),
getDocument: mockGetDocument,
}));
wrapper = mountComponent({ pdf: `${FIXTURES_PATH}/blob/pdf/test.pdf` });
await wrapper.vm.load();
});
it('renders with pdfjs-dist v4', () => {
expect(mockGetDocument).toHaveBeenCalledWith({
url: '/fixtures/blob/pdf/test.pdf',
cMapUrl: process.env.PDF_JS_CMAPS_V4_PUBLIC_PATH,
cMapPacked: true,
isEvalSupported: true,
});
expect(wrapper.isVisible()).toBe(true);
});
it('gets worker file path from environment var', () => {
expect(GlobalWorkerOptionsV4.workerSrc).toBe('mock/path/v4/pdf.worker.js');
});
});
it('renders', () => {
expect(wrapper.isVisible()).toBe(true);
});
describe('when upgradePdfjs flag is off', () => {
let mockGetDocument;
it('gets worker file path from environment var', () => {
expect(GlobalWorkerOptions).toEqual({
workerPort: null,
workerSrc: 'mock/path/pdf.worker.js',
beforeEach(async () => {
mockGetDocument = jest.fn().mockReturnValue({ promise: Promise.resolve() });
jest.mock('pdfjs-dist-v3/legacy/build/pdf', () => ({
...jest.requireActual('pdfjs-dist-v3/legacy/build/pdf'),
getDocument: mockGetDocument,
}));
wrapper = mountComponent({ pdf: `${FIXTURES_PATH}/blob/pdf/test.pdf`, flagValue: false });
await wrapper.vm.load();
});
it('renders with pdfjs-dist v3', () => {
expect(mockGetDocument).toHaveBeenCalledWith({
url: '/fixtures/blob/pdf/test.pdf',
cMapUrl: process.env.PDF_JS_CMAPS_V3_PUBLIC_PATH,
cMapPacked: true,
isEvalSupported: false,
});
expect(wrapper.isVisible()).toBe(true);
});
it('gets worker file path from environment var', () => {
expect(GlobalWorkerOptionsV3.workerSrc).toBe('mock/path/v3/pdf.worker.js');
});
});
});

View File

@ -2,10 +2,6 @@ import { nextTick } from 'vue';
import { mount } from '@vue/test-utils';
import PageComponent from '~/pdf/page/index.vue';
jest.mock('pdfjs-dist/webpack', () => {
return { default: jest.requireActual('pdfjs-dist/legacy/build/pdf') };
});
describe('Page component', () => {
let wrapper;

View File

@ -1,4 +1,4 @@
import { GlModal, GlFormTextarea, GlToggle } from '@gitlab/ui';
import { GlModal, GlFormTextarea, GlFormCheckbox } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import axios from 'axios';
@ -53,7 +53,7 @@ describe('NewDirectoryModal', () => {
const findDirName = () => wrapper.find('[name="dir_name"]');
const findBranchName = () => wrapper.find('[name="branch_name"]');
const findCommitMessage = () => wrapper.findComponent(GlFormTextarea);
const findMrToggle = () => wrapper.findComponent(GlToggle);
const findMrCheckbox = () => wrapper.findComponent(GlFormCheckbox);
const fillForm = async (inputValue = {}) => {
const {
@ -66,7 +66,7 @@ describe('NewDirectoryModal', () => {
await findDirName().vm.$emit('input', dirName);
await findBranchName().vm.$emit('input', branchName);
await findCommitMessage().vm.$emit('input', commitMessage);
await findMrToggle().vm.$emit('change', createNewMr);
await findMrCheckbox().vm.$emit('input', createNewMr);
await nextTick();
};
@ -95,16 +95,24 @@ describe('NewDirectoryModal', () => {
describe('form', () => {
it.each`
component | defaultValue | canPushCode | targetBranch | originalBranch | exist
${findDirName} | ${undefined} | ${true} | ${initialProps.targetBranch} | ${initialProps.originalBranch} | ${true}
${findBranchName} | ${initialProps.targetBranch} | ${true} | ${initialProps.targetBranch} | ${initialProps.originalBranch} | ${true}
${findBranchName} | ${undefined} | ${false} | ${initialProps.targetBranch} | ${initialProps.originalBranch} | ${false}
${findCommitMessage} | ${initialProps.commitMessage} | ${true} | ${initialProps.targetBranch} | ${initialProps.originalBranch} | ${true}
${findMrToggle} | ${'true'} | ${true} | ${'new-target-branch'} | ${'master'} | ${true}
${findMrToggle} | ${'true'} | ${true} | ${'master'} | ${'master'} | ${true}
component | defaultValue | canPushCode | targetBranch | originalBranch | exist | attributes
${findDirName} | ${undefined} | ${true} | ${initialProps.targetBranch} | ${initialProps.originalBranch} | ${true} | ${'value'}
${findBranchName} | ${initialProps.targetBranch} | ${true} | ${initialProps.targetBranch} | ${initialProps.originalBranch} | ${true} | ${'value'}
${findBranchName} | ${undefined} | ${false} | ${initialProps.targetBranch} | ${initialProps.originalBranch} | ${false} | ${'value'}
${findCommitMessage} | ${initialProps.commitMessage} | ${true} | ${initialProps.targetBranch} | ${initialProps.originalBranch} | ${true} | ${'value'}
${findMrCheckbox} | ${'true'} | ${true} | ${'new-target-branch'} | ${'master'} | ${true} | ${'checked'}
${findMrCheckbox} | ${'true'} | ${true} | ${'master'} | ${'master'} | ${true} | ${'checked'}
`(
'has the correct form fields',
({ component, defaultValue, canPushCode, targetBranch, originalBranch, exist }) => {
({
component,
defaultValue,
canPushCode,
targetBranch,
originalBranch,
exist,
attributes,
}) => {
createComponent({
canPushCode,
targetBranch,
@ -118,7 +126,7 @@ describe('NewDirectoryModal', () => {
}
expect(formField.exists()).toBe(true);
expect(formField.attributes('value')).toBe(defaultValue);
expect(formField.attributes(attributes)).toBe(defaultValue);
},
);
});

View File

@ -1,4 +1,4 @@
import { GlModal, GlFormInput, GlFormTextarea, GlToggle, GlAlert } from '@gitlab/ui';
import { GlModal, GlFormInput, GlFormTextarea, GlFormCheckbox, GlAlert } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
@ -52,7 +52,7 @@ describe('UploadBlobModal', () => {
const findAlert = () => wrapper.findComponent(GlAlert);
const findCommitMessage = () => wrapper.findComponent(GlFormTextarea);
const findBranchName = () => wrapper.findComponent(GlFormInput);
const findMrToggle = () => wrapper.findComponent(GlToggle);
const findMrCheckbox = () => wrapper.findComponent(GlFormCheckbox);
const findUploadDropzone = () => wrapper.findComponent(UploadDropzone);
const actionButtonDisabledState = () => findModal().props('actionPrimary').attributes.disabled;
const cancelButtonDisabledState = () => findModal().props('actionCancel').attributes.disabled;
@ -90,8 +90,8 @@ describe('UploadBlobModal', () => {
expect(cancelButtonDisabledState()).toBe(false);
});
it('does not display the MR toggle', () => {
expect(findMrToggle().exists()).toBe(false);
it('does not display the MR checkbox', () => {
expect(findMrCheckbox().exists()).toBe(false);
});
it(`${
@ -106,12 +106,12 @@ describe('UploadBlobModal', () => {
if (canPushCode) {
describe('when changing the branch name', () => {
it('displays the MR toggle', async () => {
it('displays the MR checkbox', async () => {
createComponent({ targetBranch: 'Not main' });
await nextTick();
expect(findMrToggle().exists()).toBe(true);
expect(findMrCheckbox().exists()).toBe(true);
});
});
}

View File

@ -459,9 +459,6 @@ export const TEST_RAW_BUCKETS = [
];
export const TEST_FILTER_DATA = {
header: 'Language',
scopes: { BLOBS: 'blobs' },
filterParam: 'language',
filters: {
GO: { label: 'Go', value: 'Go', count: 350 },
C: { label: 'C', value: 'C', count: 298 },

View File

@ -2,11 +2,6 @@ import { shallowMount } from '@vue/test-utils';
import Vue from 'vue';
// eslint-disable-next-line no-restricted-imports
import Vuex from 'vuex';
import {
SEARCH_TYPE_ZOEKT,
SEARCH_TYPE_ADVANCED,
SEARCH_TYPE_BASIC,
} from '~/search/sidebar/constants';
import { MOCK_QUERY } from 'jest/search/mock_data';
import { toggleSuperSidebarCollapsed } from '~/super_sidebar/super_sidebar_collapsed_state_manager';
import GlobalSearchSidebar from '~/search/sidebar/components/app.vue';
@ -89,26 +84,23 @@ describe('GlobalSearchSidebar', () => {
${'wiki_blobs'} | ${findWikiBlobsFilters}
${'wiki_blobs'} | ${findAllScopesStartFilters}
`('with sidebar scope: $scope', ({ scope, filter }) => {
describe.each([SEARCH_TYPE_BASIC, SEARCH_TYPE_ADVANCED])(
'with search_type %s',
(searchType) => {
beforeEach(() => {
getterSpies.currentScope = jest.fn(() => scope);
createComponent({ urlQuery: { scope }, searchType });
});
describe.each(['basic', 'advanced'])('with search_type %s', (searchType) => {
beforeEach(() => {
getterSpies.currentScope = jest.fn(() => scope);
createComponent({ urlQuery: { scope }, searchType });
});
it(`renders correctly ${filter.name.replace('find', '')}`, () => {
expect(filter().exists()).toBe(true);
});
},
);
it(`renders correctly ${filter.name.replace('find', '')}`, () => {
expect(filter().exists()).toBe(true);
});
});
});
describe.each`
scope | searchType | isShown
${'blobs'} | ${SEARCH_TYPE_BASIC} | ${false}
${'blobs'} | ${SEARCH_TYPE_ADVANCED} | ${true}
${'blobs'} | ${SEARCH_TYPE_ZOEKT} | ${true}
scope | searchType | isShown
${'blobs'} | ${'basic'} | ${false}
${'blobs'} | ${'advanced'} | ${true}
${'blobs'} | ${'zoekt'} | ${true}
`('sidebar blobs scope:', ({ scope, searchType, isShown }) => {
beforeEach(() => {
getterSpies.currentScope = jest.fn(() => scope);

View File

@ -6,8 +6,6 @@ import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import ArchivedFilter from '~/search/sidebar/components/archived_filter/index.vue';
import { archivedFilterData } from '~/search/sidebar/components/archived_filter/data';
Vue.use(Vuex);
describe('ArchivedFilter', () => {
@ -46,7 +44,7 @@ describe('ArchivedFilter', () => {
it('renders the divider', () => {
expect(findTitle().exists()).toBe(true);
expect(findTitle().text()).toBe(archivedFilterData.headerLabel);
expect(findTitle().text()).toBe('Archived');
});
it('wraps the label element with a tooltip', () => {
@ -67,7 +65,7 @@ describe('ArchivedFilter', () => {
it("doesn't render the divider", () => {
expect(findTitle().exists()).toBe(true);
expect(findTitle().text()).toBe(archivedFilterData.headerLabel);
expect(findTitle().text()).toBe('Archived');
});
it('wraps the label element with a tooltip', () => {

View File

@ -7,11 +7,6 @@ import BlobsFilters from '~/search/sidebar/components/blobs_filters.vue';
import LanguageFilter from '~/search/sidebar/components/language_filter/index.vue';
import ArchivedFilter from '~/search/sidebar/components/archived_filter/index.vue';
import ForksFilter from '~/search/sidebar/components/forks_filter/index.vue';
import {
SEARCH_TYPE_ZOEKT,
SEARCH_TYPE_ADVANCED,
SEARCH_TYPE_BASIC,
} from '~/search/sidebar/constants';
Vue.use(Vuex);
@ -23,7 +18,7 @@ describe('GlobalSearch BlobsFilters', () => {
hasMissingProjectContext: () => true,
};
const createComponent = (initialState = { searchType: SEARCH_TYPE_ADVANCED }) => {
const createComponent = (initialState = { searchType: 'advanced' }) => {
const store = new Vuex.Store({
state: {
urlQuery: MOCK_QUERY,
@ -46,10 +41,10 @@ describe('GlobalSearch BlobsFilters', () => {
});
describe.each`
searchType | isShown
${SEARCH_TYPE_BASIC} | ${false}
${SEARCH_TYPE_ADVANCED} | ${true}
${SEARCH_TYPE_ZOEKT} | ${false}
searchType | isShown
${'basic'} | ${false}
${'advanced'} | ${true}
${'zoekt'} | ${false}
`('sidebar blobs language filter:', ({ searchType, isShown }) => {
beforeEach(() => {
createComponent({ searchType });
@ -61,13 +56,13 @@ describe('GlobalSearch BlobsFilters', () => {
});
describe.each`
searchType | hasProjectContent | isShown
${SEARCH_TYPE_BASIC} | ${true} | ${false}
${SEARCH_TYPE_BASIC} | ${false} | ${false}
${SEARCH_TYPE_ADVANCED} | ${true} | ${false}
${SEARCH_TYPE_ADVANCED} | ${false} | ${false}
${SEARCH_TYPE_ZOEKT} | ${true} | ${true}
${SEARCH_TYPE_ZOEKT} | ${false} | ${false}
searchType | hasProjectContent | isShown
${'basic'} | ${true} | ${false}
${'basic'} | ${false} | ${false}
${'advanced'} | ${true} | ${false}
${'advanced'} | ${false} | ${false}
${'zoekt'} | ${true} | ${true}
${'zoekt'} | ${false} | ${false}
`('sidebar blobs fork filter:', ({ searchType, hasProjectContent, isShown }) => {
beforeEach(() => {
defaultGetters.hasMissingProjectContext = () => hasProjectContent;
@ -80,13 +75,13 @@ describe('GlobalSearch BlobsFilters', () => {
});
describe.each`
searchType | hasProjectContent | isShown
${SEARCH_TYPE_BASIC} | ${true} | ${true}
${SEARCH_TYPE_BASIC} | ${false} | ${false}
${SEARCH_TYPE_ADVANCED} | ${true} | ${true}
${SEARCH_TYPE_ADVANCED} | ${false} | ${false}
${SEARCH_TYPE_ZOEKT} | ${true} | ${true}
${SEARCH_TYPE_ZOEKT} | ${false} | ${false}
searchType | hasProjectContent | isShown
${'basic'} | ${true} | ${true}
${'basic'} | ${false} | ${false}
${'advanced'} | ${true} | ${true}
${'advanced'} | ${false} | ${false}
${'zoekt'} | ${true} | ${true}
${'zoekt'} | ${false} | ${false}
`('sidebar blobs archived filter:', ({ searchType, hasProjectContent, isShown }) => {
beforeEach(() => {
defaultGetters.hasMissingProjectContext = () => hasProjectContent;

View File

@ -5,12 +5,8 @@ import Vuex from 'vuex';
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { MOCK_QUERY, MOCK_LANGUAGE_AGGREGATIONS_BUCKETS } from 'jest/search/mock_data';
import CheckboxFilter, {
TRACKING_LABEL_CHECKBOX,
TRACKING_LABEL_SET,
} from '~/search/sidebar/components/language_filter/checkbox_filter.vue';
import CheckboxFilter from '~/search/sidebar/components/language_filter/checkbox_filter.vue';
import { languageFilterData } from '~/search/sidebar/components/language_filter/data';
import { convertFiltersData } from '~/search/sidebar/utils';
Vue.use(Vuex);
@ -30,6 +26,7 @@ describe('CheckboxFilter', () => {
const defaultProps = {
filtersData: convertFiltersData(MOCK_LANGUAGE_AGGREGATIONS_BUCKETS),
trackingNamespace: 'testNameSpace',
queryParam: 'language',
};
const createComponent = (Props = defaultProps) => {
@ -101,21 +98,17 @@ describe('CheckboxFilter', () => {
it('triggers setQuery', () => {
expect(actionSpies.setQuery).toHaveBeenCalledWith(expect.any(Object), {
key: languageFilterData.filterParam,
key: 'language',
value: checkedLanguageName,
});
});
it('sends tracking information when setQuery', () => {
findFormCheckboxGroup().vm.$emit('input', checkedLanguageName);
expect(trackingSpy).toHaveBeenCalledWith(
defaultProps.trackingNamespace,
TRACKING_LABEL_CHECKBOX,
{
label: TRACKING_LABEL_SET,
property: checkedLanguageName,
},
);
expect(trackingSpy).toHaveBeenCalledWith(defaultProps.trackingNamespace, 'checkbox', {
label: 'set',
property: checkedLanguageName,
});
});
});
});

View File

@ -5,10 +5,7 @@ import { GlFormCheckboxGroup } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { useMockInternalEventsTracking } from 'helpers/tracking_internal_events_helper';
import { EVENT_CLICK_ZOEKT_INCLUDE_FORKS_ON_SEARCH_RESULTS_PAGE } from '~/search/sidebar/constants';
import ForksFilter, {
INCLUDE_FORKED_FILTER_PARAM,
} from '~/search/sidebar/components/forks_filter/index.vue';
import ForksFilter from '~/search/sidebar/components/forks_filter/index.vue';
Vue.use(Vuex);
const { bindInternalEventDocument } = useMockInternalEventsTracking();
@ -104,7 +101,7 @@ describe('ForksFilter', () => {
findCheckboxFilter().vm.$emit('input', selectedFilter);
expect(defaultActions.setQuery).toHaveBeenCalledWith(expect.any(Object), {
key: INCLUDE_FORKED_FILTER_PARAM,
key: 'include_forked',
value: 'false',
});
expect(selectedFilter).toEqual([false]);
@ -120,12 +117,12 @@ describe('ForksFilter', () => {
});
});
it(`dispatches internal ${EVENT_CLICK_ZOEKT_INCLUDE_FORKS_ON_SEARCH_RESULTS_PAGE}`, () => {
it(`dispatches internal click_zoekt_include_forks_on_search_results_page`, () => {
findCheckboxFilter().vm.$emit('change');
const { trackEventSpy } = bindInternalEventDocument(wrapper.element);
expect(trackEventSpy).toHaveBeenCalledWith(
EVENT_CLICK_ZOEKT_INCLUDE_FORKS_ON_SEARCH_RESULTS_PAGE,
'click_zoekt_include_forks_on_search_results_page',
{},
undefined,
);

View File

@ -8,7 +8,6 @@ import { visitUrl, setUrlParams } from '~/lib/utils/url_utility';
import { GROUPS_LOCAL_STORAGE_KEY } from '~/search/store/constants';
import GroupFilter from '~/search/sidebar/components/group_filter.vue';
import SearchableDropdown from '~/search/sidebar/components/shared/searchable_dropdown.vue';
import { ANY_OPTION, GROUP_DATA, PROJECT_DATA } from '~/search/sidebar/constants';
Vue.use(Vuex);
@ -72,13 +71,17 @@ describe('GroupFilter', () => {
describe('when @change is emitted with Any', () => {
beforeEach(() => {
findSearchableDropdown().vm.$emit('change', ANY_OPTION);
findSearchableDropdown().vm.$emit('change', {
id: null,
name: 'Any',
name_with_namespace: 'Any',
});
});
it('calls setUrlParams with group null, project id null, nav_source null, and then calls visitUrl', () => {
expect(setUrlParams).toHaveBeenCalledWith({
[GROUP_DATA.queryParam]: null,
[PROJECT_DATA.queryParam]: null,
group_id: null,
project_id: null,
nav_source: null,
scope: CURRENT_SCOPE,
});
@ -98,8 +101,8 @@ describe('GroupFilter', () => {
it('calls setUrlParams with group id, project id null, nav_source null, and then calls visitUrl', () => {
expect(setUrlParams).toHaveBeenCalledWith({
[GROUP_DATA.queryParam]: MOCK_GROUP.id,
[PROJECT_DATA.queryParam]: null,
group_id: MOCK_GROUP.id,
project_id: null,
nav_source: null,
scope: CURRENT_SCOPE,
});
@ -131,7 +134,11 @@ describe('GroupFilter', () => {
});
it('sets selectedGroup to ANY_OPTION', () => {
expect(wrapper.vm.selectedGroup).toBe(ANY_OPTION);
expect(wrapper.vm.selectedGroup).toStrictEqual({
id: null,
name: 'Any',
name_with_namespace: 'Any',
});
});
});

View File

@ -8,7 +8,6 @@ import ConfidentialityFilter from '~/search/sidebar/components/confidentiality_f
import StatusFilter from '~/search/sidebar/components/status_filter/index.vue';
import LabelFilter from '~/search/sidebar/components/label_filter/index.vue';
import ArchivedFilter from '~/search/sidebar/components/archived_filter/index.vue';
import { SEARCH_TYPE_ADVANCED, SEARCH_TYPE_BASIC } from '~/search/sidebar/constants';
Vue.use(Vuex);
@ -24,7 +23,7 @@ describe('GlobalSearch IssuesFilters', () => {
const store = new Vuex.Store({
state: {
urlQuery: MOCK_QUERY,
searchType: SEARCH_TYPE_ADVANCED,
searchType: 'advanced',
...initialState,
},
getters: defaultGetters,
@ -64,7 +63,7 @@ describe('GlobalSearch IssuesFilters', () => {
describe('Renders correctly with basic search', () => {
beforeEach(() => {
createComponent({ initialState: { searchType: SEARCH_TYPE_BASIC } });
createComponent({ initialState: { searchType: 'basic' } });
});
it('renders StatusFilter', () => {
expect(findStatusFilter().exists()).toBe(true);

View File

@ -18,7 +18,9 @@ import {
MOCK_LABEL_AGGREGATIONS,
MOCK_FILTERED_UNSELECTED_LABELS,
} from 'jest/search/mock_data';
import LabelFilter from '~/search/sidebar/components/label_filter/index.vue';
import LabelDropdownItems from '~/search/sidebar/components/label_filter/label_dropdown_items.vue';
import * as actions from '~/search/store/actions';
@ -26,16 +28,6 @@ import * as getters from '~/search/store/getters';
import mutations from '~/search/store/mutations';
import createState from '~/search/store/state';
import {
TRACKING_LABEL_FILTER,
TRACKING_LABEL_DROPDOWN,
TRACKING_LABEL_CHECKBOX,
TRACKING_ACTION_SELECT,
TRACKING_ACTION_SHOW,
} from '~/search/sidebar/components/label_filter/tracking';
import { LABEL_FILTER_PARAM } from '~/search/sidebar/components/label_filter/data';
import {
RECEIVE_AGGREGATIONS_SUCCESS,
REQUEST_AGGREGATIONS,
@ -216,8 +208,8 @@ describe('GlobalSearchSidebarLabelFilter', () => {
});
it('sends tracking information when dropdown is opened', () => {
expect(trackingSpy).toHaveBeenCalledWith(TRACKING_ACTION_SHOW, TRACKING_LABEL_DROPDOWN, {
label: TRACKING_LABEL_DROPDOWN,
expect(trackingSpy).toHaveBeenCalledWith('search:agreggations:label:show', 'Dropdown', {
label: 'Dropdown',
});
});
});
@ -352,15 +344,19 @@ describe('GlobalSearchSidebarLabelFilter', () => {
it('trigger event', () => {
expect(actionSpies.setQuery).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({ key: LABEL_FILTER_PARAM, value: ['Cosche'] }),
expect.objectContaining({ key: 'label_name', value: ['Cosche'] }),
);
});
it('sends tracking information when checkbox is selected', () => {
expect(trackingSpy).toHaveBeenCalledWith(TRACKING_ACTION_SELECT, TRACKING_LABEL_CHECKBOX, {
label: TRACKING_LABEL_FILTER,
property: [6],
});
expect(trackingSpy).toHaveBeenCalledWith(
'search:agreggations:label:select',
'Label Checkbox',
{
label: 'Label Key',
property: [6],
},
);
});
});

View File

@ -12,18 +12,6 @@ import {
import LanguageFilter from '~/search/sidebar/components/language_filter/index.vue';
import CheckboxFilter from '~/search/sidebar/components/language_filter/checkbox_filter.vue';
import {
TRACKING_LABEL_SHOW_MORE,
TRACKING_PROPERTY_MAX,
TRACKING_LABEL_MAX,
TRACKING_LABEL_FILTERS,
TRACKING_ACTION_SHOW,
TRACKING_ACTION_CLICK,
TRACKING_LABEL_ALL,
} from '~/search/sidebar/components/language_filter/tracking';
import { MAX_ITEM_LENGTH } from '~/search/sidebar/components/language_filter/data';
Vue.use(Vuex);
describe('GlobalSearchSidebarLanguageFilter', () => {
@ -104,23 +92,23 @@ describe('GlobalSearchSidebarLanguageFilter', () => {
unmockTracking();
});
it(`renders ${MAX_ITEM_LENGTH} amount of items`, async () => {
it(`renders 100 items`, async () => {
findShowMoreButton().vm.$emit('click');
await nextTick();
expect(findAllCheckboxes()).toHaveLength(MAX_ITEM_LENGTH);
expect(findAllCheckboxes()).toHaveLength(100);
});
it('sends tracking information when show more clicked', () => {
findShowMoreButton().vm.$emit('click');
expect(trackingSpy).toHaveBeenCalledWith(TRACKING_ACTION_CLICK, TRACKING_LABEL_SHOW_MORE, {
label: TRACKING_LABEL_ALL,
expect(trackingSpy).toHaveBeenCalledWith('search:agreggations:language:click', 'Show More', {
label: 'All Filters',
});
});
it(`renders more then ${MAX_ITEM_LENGTH} text`, async () => {
it(`renders more then 10 text`, async () => {
findShowMoreButton().vm.$emit('click');
await nextTick();
expect(findHasOverMax().exists()).toBe(true);
@ -129,9 +117,9 @@ describe('GlobalSearchSidebarLanguageFilter', () => {
it('sends tracking information when show more clicked and max item reached', () => {
findShowMoreButton().vm.$emit('click');
expect(trackingSpy).toHaveBeenCalledWith(TRACKING_ACTION_SHOW, TRACKING_LABEL_FILTERS, {
label: TRACKING_LABEL_MAX,
property: TRACKING_PROPERTY_MAX,
expect(trackingSpy).toHaveBeenCalledWith('search:agreggations:language:show', 'Filters', {
label: 'Max Shown',
property: `More than 10 filters to show`,
});
});

View File

@ -8,7 +8,6 @@ import StatusFilter from '~/search/sidebar/components/status_filter/index.vue';
import ArchivedFilter from '~/search/sidebar/components/archived_filter/index.vue';
import SourceBranchFilter from '~/search/sidebar/components/source_branch_filter/index.vue';
import LabelFilter from '~/search/sidebar/components/label_filter/index.vue';
import { SEARCH_TYPE_ADVANCED, SEARCH_TYPE_BASIC } from '~/search/sidebar/constants';
Vue.use(Vuex);
@ -29,7 +28,7 @@ describe('GlobalSearch MergeRequestsFilters', () => {
const store = new Vuex.Store({
state: {
urlQuery: MOCK_QUERY,
searchType: SEARCH_TYPE_ADVANCED,
searchType: 'advanced',
groupInitialJson: {
id: 1,
},
@ -73,7 +72,7 @@ describe('GlobalSearch MergeRequestsFilters', () => {
describe('Renders correctly with basic search', () => {
beforeEach(() => {
createComponent({ searchType: SEARCH_TYPE_BASIC });
createComponent({ searchType: 'basic' });
});
it('renders StatusFilter', () => {

View File

@ -5,10 +5,8 @@ import Vue from 'vue';
import Vuex from 'vuex';
import { MOCK_PROJECT, MOCK_QUERY, CURRENT_SCOPE } from 'jest/search/mock_data';
import { visitUrl, setUrlParams } from '~/lib/utils/url_utility';
import { PROJECTS_LOCAL_STORAGE_KEY } from '~/search/store/constants';
import ProjectFilter from '~/search/sidebar/components/project_filter.vue';
import SearchableDropdown from '~/search/sidebar/components/shared/searchable_dropdown.vue';
import { ANY_OPTION, GROUP_DATA, PROJECT_DATA } from '~/search/sidebar/constants';
Vue.use(Vuex);
@ -75,13 +73,17 @@ describe('ProjectFilter', () => {
describe('when @change is emitted', () => {
describe('with Any', () => {
beforeEach(() => {
findSearchableDropdown().vm.$emit('change', ANY_OPTION);
findSearchableDropdown().vm.$emit('change', {
id: null,
name: 'Any',
name_with_namespace: 'Any',
});
});
it('calls setUrlParams with null, no group id, nav_source null, then calls visitUrl', () => {
expect(setUrlParams).toHaveBeenCalledWith({
include_archived: null,
[PROJECT_DATA.queryParam]: null,
project_id: null,
nav_source: null,
scope: CURRENT_SCOPE,
});
@ -100,16 +102,16 @@ describe('ProjectFilter', () => {
it('calls setUrlParams with project id, group id, nav_source null, then calls visitUrl', () => {
expect(setUrlParams).toHaveBeenCalledWith({
[GROUP_DATA.queryParam]: MOCK_PROJECT.namespace.id,
group_id: MOCK_PROJECT.namespace.id,
include_archived: null,
[PROJECT_DATA.queryParam]: MOCK_PROJECT.id,
project_id: MOCK_PROJECT.id,
nav_source: null,
scope: CURRENT_SCOPE,
});
expect(visitUrl).toHaveBeenCalled();
});
it(`calls setFrequentProject with the group and ${PROJECTS_LOCAL_STORAGE_KEY}`, () => {
it(`calls setFrequentProject with the group and global-search-frequent-projects`, () => {
expect(actionSpies.setFrequentProject).toHaveBeenCalledWith(
expect.any(Object),
MOCK_PROJECT,
@ -133,11 +135,24 @@ describe('ProjectFilter', () => {
describe('selectedProject', () => {
describe('when initialData is null', () => {
beforeEach(() => {
createComponent({ projectInitialJson: ANY_OPTION }, {});
createComponent(
{
projectInitialJson: {
id: null,
name: 'Any',
name_with_namespace: 'Any',
},
},
{},
);
});
it('sets selectedProject to ANY_OPTION', () => {
expect(cloneDeep(wrapper.vm.selectedProject)).toStrictEqual(ANY_OPTION);
expect(cloneDeep(wrapper.vm.selectedProject)).toStrictEqual({
id: null,
name: 'Any',
name_with_namespace: 'Any',
});
});
});

View File

@ -5,8 +5,6 @@ import Vue from 'vue';
import Vuex from 'vuex';
import { MOCK_QUERY } from 'jest/search/mock_data';
import RadioFilter from '~/search/sidebar/components/shared/radio_filter.vue';
import { confidentialFilterData } from '~/search/sidebar/components/confidentiality_filter/data';
import { statusFilterData } from '~/search/sidebar/components/status_filter/data';
Vue.use(Vuex);
@ -22,10 +20,66 @@ describe('RadioFilter', () => {
};
const defaultProps = {
filterData: statusFilterData,
filtersArray: {
issues: [
{
label: 'Any',
value: null,
},
{
label: 'Confidential',
value: 'yes',
},
{
label: 'Not confidential',
value: 'no',
},
],
},
header: 'Confidentiality',
filterParam: 'confidential',
};
const createComponent = (initialState, props = {}) => {
const statusDefaultProps = {
filtersArray: {
issues: [
{
label: 'Any',
value: null,
},
{
label: 'Open',
value: 'opened',
},
{
label: 'Closed',
value: 'closed',
},
],
merge_requests: [
{
label: 'Any',
value: null,
},
{
label: 'Open',
value: 'opened',
},
{
label: 'Merged',
value: 'merged',
},
{
label: 'Closed',
value: 'closed',
},
],
},
header: 'Status',
filterParam: 'state',
};
const createComponent = (initialState = {}, props = {}) => {
const store = new Vuex.Store({
state: {
query: MOCK_QUERY,
@ -59,50 +113,46 @@ describe('RadioFilter', () => {
describe('Radio Buttons', () => {
describe('Status Filter', () => {
beforeEach(() => {
createComponent({}, statusDefaultProps);
});
it('renders a radio button for each filterOption', () => {
expect(findGlRadioButtonsText()).toStrictEqual(
statusFilterData.filterByScope[statusFilterData.scopes.ISSUES].map((f) => {
return f.value === statusFilterData.filters.ANY.value
? `Any ${statusFilterData.header.toLowerCase()}`
: f.label;
statusDefaultProps.filtersArray.issues.map((f) => {
return f.value === null ? `Any ${'Status'.toLowerCase()}` : f.label;
}),
);
});
it('clicking a radio button item calls setQuery', () => {
const filter = statusFilterData.filters[Object.keys(statusFilterData.filters)[0]].value;
findGlRadioButtonGroup().vm.$emit('input', filter);
findGlRadioButtonGroup().vm.$emit('input', 'opened');
expect(actionSpies.setQuery).toHaveBeenCalledWith(expect.any(Object), {
key: statusFilterData.filterParam,
value: filter,
key: 'state',
value: 'opened',
});
});
});
describe('Confidentiality Filter', () => {
beforeEach(() => {
createComponent({}, { filterData: confidentialFilterData });
createComponent();
});
it('renders a radio button for each filterOption', () => {
expect(findGlRadioButtonsText()).toStrictEqual(
confidentialFilterData.filterByScope[confidentialFilterData.scopes.ISSUES].map((f) => {
return f.value === confidentialFilterData.filters.ANY.value
? `Any ${confidentialFilterData.header.toLowerCase()}`
: f.label;
defaultProps.filtersArray.issues.map((f) => {
return f.value === null ? `Any ${'Confidentiality'.toLowerCase()}` : f.label;
}),
);
});
it('clicking a radio button item calls setQuery', () => {
const filter =
confidentialFilterData.filters[Object.keys(confidentialFilterData.filters)[0]].value;
findGlRadioButtonGroup().vm.$emit('input', filter);
findGlRadioButtonGroup().vm.$emit('input', 'closed');
expect(actionSpies.setQuery).toHaveBeenCalledWith(expect.any(Object), {
key: confidentialFilterData.filterParam,
value: filter,
key: 'confidential',
value: 'closed',
});
});
});

View File

@ -7,7 +7,6 @@ import Vuex from 'vuex';
import waitForPromises from 'helpers/wait_for_promises';
import { MOCK_GROUPS, MOCK_QUERY } from 'jest/search/mock_data';
import SearchableDropdown from '~/search/sidebar/components/shared/searchable_dropdown.vue';
import { ANY_OPTION, GROUP_DATA } from '~/search/sidebar/constants';
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
Vue.use(Vuex);
@ -16,11 +15,15 @@ describe('Global Search Searchable Dropdown', () => {
let wrapper;
const defaultProps = {
headerText: GROUP_DATA.headerText,
name: GROUP_DATA.name,
fullName: GROUP_DATA.fullName,
headerText: 'Filter results by group',
name: 'name',
fullName: 'full_name',
loading: false,
selectedItem: ANY_OPTION,
selectedItem: {
id: null,
name: 'Any',
name_with_namespace: 'Any',
},
items: [],
frequentItems: [{ ...MOCK_GROUPS[0] }],
searchHandler: jest.fn(),
@ -55,7 +58,18 @@ describe('Global Search Searchable Dropdown', () => {
});
const propItems = [
{ text: '', options: [{ value: ANY_OPTION.name, text: ANY_OPTION.name, ...ANY_OPTION }] },
{
text: '',
options: [
{
value: 'Any',
text: 'Any',
id: null,
name: 'Any',
name_with_namespace: 'Any',
},
],
},
{
text: 'Frequently searched',
options: [{ value: MOCK_GROUPS[0].id, text: MOCK_GROUPS[0].full_name, ...MOCK_GROUPS[0] }],
@ -86,7 +100,11 @@ describe('Global Search Searchable Dropdown', () => {
it('emits reset', () => {
findGlDropdown().vm.$emit('reset');
expect(cloneDeep(wrapper.emitted('change')[0][0])).toStrictEqual(ANY_OPTION);
expect(cloneDeep(wrapper.emitted('change')[0][0])).toStrictEqual({
id: null,
name: 'Any',
name_with_namespace: 'Any',
});
});
it('emits first-open', () => {

View File

@ -11,7 +11,8 @@ const indexedDB = new IDBFactory();
Dexie.dependencies.indexedDB = indexedDB;
Dexie.dependencies.IDBKeyRange = IDBKeyRange;
process.env.PDF_JS_WORKER_PUBLIC_PATH = 'mock/path/pdf.worker.js';
process.env.PDF_JS_WORKER_V4_PUBLIC_PATH = 'mock/path/v4/pdf.worker.js';
process.env.PDF_JS_WORKER_V3_PUBLIC_PATH = 'mock/path/v3/pdf.worker.js';
afterEach(() =>
// give Promises a bit more time so they fail the right test

View File

@ -0,0 +1,168 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe ::Gitlab::Ci::Pipeline::Chain::Limit::Size, feature_category: :pipeline_composition do
let_it_be(:namespace) { create(:namespace) }
let_it_be(:project, reload: true) { create(:project, :repository, namespace: namespace) }
let_it_be(:default_plan) { create(:default_plan) }
let_it_be(:user) { create(:user) }
let(:pipeline) { build(:ci_pipeline, project: project) }
let(:command) do
instance_double(::Gitlab::Ci::Pipeline::Chain::Command,
project: project,
current_user: user,
pipeline_seed: instance_double(::Gitlab::Ci::Pipeline::Seed::Pipeline, size: 1))
end
let(:step) { described_class.new(pipeline, command) }
subject(:perform_step) { step.perform! }
context 'when pipeline size limit is exceeded' do
before do
create(:plan_limits, plan: default_plan, ci_pipeline_size: 1)
end
context 'when saving incomplete pipelines' do
let(:command) do
instance_double(::Gitlab::Ci::Pipeline::Chain::Command,
project: project,
current_user: user,
save_incompleted: true,
pipeline_seed: instance_double(::Gitlab::Ci::Pipeline::Seed::Pipeline, size: 2))
end
it 'drops the pipeline' do
perform_step
expect(pipeline.reload).to be_failed
end
it 'persists the pipeline' do
perform_step
expect(pipeline).to be_persisted
end
it 'breaks the chain' do
perform_step
expect(step.break?).to be true
end
it 'sets a valid failure reason' do
perform_step
expect(pipeline.size_limit_exceeded?).to be true
end
it 'appends validation error' do
perform_step
expect(pipeline.errors.to_a)
.to include "The number of jobs has exceeded the limit of 1. " \
"Try splitting the configuration with parent-child-pipelines " \
"http://localhost/help/ci/debugging.md#pipeline-with-many-jobs-fails-to-start"
end
it 'logs the error' do
expect(Gitlab::ErrorTracking).to receive(:log_exception).with(
instance_of(Gitlab::Ci::Limit::LimitExceededError),
{
jobs_count: pipeline.statuses.count,
project_id: project.id, plan: namespace.actual_plan_name,
project_full_path: project.full_path, pipeline_source: pipeline.source
}
)
perform_step
end
end
context 'when not saving incomplete pipelines' do
let(:command) do
instance_double(::Gitlab::Ci::Pipeline::Chain::Command,
project: project,
current_user: user,
save_incompleted: false,
pipeline_seed: instance_double(::Gitlab::Ci::Pipeline::Seed::Pipeline, size: 2),
increment_pipeline_failure_reason_counter: true)
end
it 'fails but does not persist the pipeline' do
perform_step
expect(pipeline).not_to be_persisted
expect(pipeline).to be_size_limit_exceeded
expect(pipeline).to be_failed
end
it 'breaks the chain' do
perform_step
expect(step.break?).to be true
end
it 'increments the error metric' do
expect(command).to receive(:increment_pipeline_failure_reason_counter).with(:size_limit_exceeded)
perform_step
end
end
end
context 'when pipeline size limit is not exceeded' do
before do
create(:plan_limits, plan: default_plan, ci_pipeline_size: 100)
end
it 'does not break the chain' do
perform_step
expect(step.break?).to be false
end
it 'does not persist the pipeline' do
perform_step
expect(pipeline).not_to be_persisted
end
it 'does not log any error' do
expect(Gitlab::ErrorTracking).not_to receive(:track_exception)
perform_step
end
end
context 'when pipeline size limit is disabled' do
before do
create(:plan_limits, plan: default_plan, ci_pipeline_size: 0)
end
context 'when global pipeline size limit is exceeded' do
let(:command) do
instance_double(::Gitlab::Ci::Pipeline::Chain::Command,
project: project,
current_user: user,
pipeline_seed: instance_double(::Gitlab::Ci::Pipeline::Seed::Pipeline, size: 2001))
end
it 'logs the pipeline' do
expect(Gitlab::ErrorTracking).to receive(:log_exception).with(
instance_of(Gitlab::Ci::Limit::LimitExceededError),
{
jobs_count: pipeline.statuses.count,
project_id: project.id, plan: namespace.actual_plan_name,
project_full_path: project.full_path, pipeline_source: pipeline.source
}
)
perform_step
end
end
end
end

View File

@ -0,0 +1,125 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Ci::Pipeline::Quota::Size, feature_category: :pipeline_composition do
let_it_be(:namespace) { create(:namespace) }
let_it_be(:project, reload: true) { create(:project, :repository, namespace: namespace) }
let_it_be(:plan_limits, reload: true) { create(:plan_limits, :default_plan) }
let(:pipeline) { build_stubbed(:ci_pipeline, project: project) }
let(:command) do
instance_double(::Gitlab::Ci::Pipeline::Chain::Command, pipeline_seed: pipeline_seed_double)
end
let(:pipeline_seed_double) do
instance_double(::Gitlab::Ci::Pipeline::Seed::Pipeline, size: 2)
end
subject { described_class.new(namespace, pipeline, command) }
shared_context 'when pipeline size limit exceeded' do
before do
plan_limits.update!(ci_pipeline_size: 1)
end
end
shared_context 'when pipeline size limit not exceeded' do
before do
plan_limits.update!(ci_pipeline_size: 2)
end
end
describe '#enabled?' do
context 'when limit is enabled in plan' do
before do
plan_limits.update!(ci_pipeline_size: 10)
end
it 'is enabled' do
is_expected.to be_enabled
end
end
context 'when limit is not enabled' do
before do
plan_limits.update!(ci_pipeline_size: 0)
end
it 'is not enabled' do
is_expected.not_to be_enabled
end
end
context 'when limit does not exist' do
before do
allow(namespace).to receive(:actual_plan) { create(:default_plan) }
end
it 'is not enabled' do
is_expected.not_to be_enabled
end
end
end
describe '#exceeded?' do
context 'when limit is exceeded' do
include_context 'when pipeline size limit exceeded'
it 'is exceeded' do
is_expected.to be_exceeded
end
end
context 'when limit is not exceeded' do
include_context 'when pipeline size limit not exceeded'
it 'is not exceeded' do
is_expected.not_to be_exceeded
end
end
end
describe '#message' do
context 'when limit is exceeded' do
include_context 'when pipeline size limit exceeded'
it 'returns info about pipeline size limit exceeded' do
is_expected.to have_attributes(message: "The number of jobs has exceeded the limit of 1. " \
"Try splitting the configuration with parent-child-pipelines " \
"http://localhost/help/ci/debugging.md#pipeline-with-many-jobs-fails-to-start")
end
end
end
describe '#log_exceeded_limit?' do
context 'when there are more than 2000 jobs in the pipeline' do
let(:command) do
instance_double(::Gitlab::Ci::Pipeline::Chain::Command, pipeline_seed: pipeline_seed_double)
end
let(:pipeline_seed_double) do
instance_double(::Gitlab::Ci::Pipeline::Seed::Pipeline, size: 2001)
end
it 'returns true' do
is_expected.to be_log_exceeded_limit
end
end
context 'when there are 2000 or less jobs in the pipeline' do
let(:command) do
instance_double(::Gitlab::Ci::Pipeline::Chain::Command, pipeline_seed: pipeline_seed_double)
end
let(:pipeline_seed_double) do
instance_double(::Gitlab::Ci::Pipeline::Seed::Pipeline, size: 2000)
end
it 'returns false' do
is_expected.not_to be_log_exceeded_limit
end
end
end
end

View File

@ -9,14 +9,8 @@ RSpec.describe Gitlab::Gfm::UploadsRewriter do
let(:rewriter) { described_class.new(+text, nil, old_project, user) }
context 'text contains links to uploads' do
let(:image_uploader) do
build(:file_uploader, project: old_project)
end
let(:zip_uploader) do
build(:file_uploader, project: old_project,
fixture: 'ci_build_artifacts.zip')
end
let(:image_uploader) { build(:file_uploader, container: old_project) }
let(:zip_uploader) { build(:file_uploader, container: old_project, fixture: 'ci_build_artifacts.zip') }
let(:text) do
"Text and #{image_uploader.markdown_link} and #{zip_uploader.markdown_link}".freeze

View File

@ -8,7 +8,7 @@ RSpec.describe 'InternalEventsCli::Flows::EventDefiner', :aggregate_failures, fe
include_context 'when running the Internal Events Cli'
describe 'end-to-end behavior' do
YAML.safe_load(File.read('spec/fixtures/scripts/internal_events/new_events.yml')).each do |test_case|
YAML.safe_load(File.read('spec/fixtures/scripts/internal_events/event_definer_examples.yml')).each do |test_case|
it_behaves_like 'creates the right definition files', test_case['description'], test_case
end
end

View File

@ -15,7 +15,7 @@ RSpec.describe 'InternalEventsCli::Flows::MetricDefiner', :aggregate_failures, f
let_it_be(:event3_content) { internal_event_fixture('events/secondary_event_with_identifiers.yml') }
describe 'end-to-end behavior' do
YAML.safe_load(File.read('spec/fixtures/scripts/internal_events/new_metrics.yml')).each do |test_case|
YAML.safe_load(File.read('spec/fixtures/scripts/internal_events/metric_definer_examples.yml')).each do |test_case|
it_behaves_like 'creates the right definition files', test_case['description'], test_case
end
end

View File

@ -69,7 +69,7 @@ RSpec.describe MarkdownContentRewriterService, feature_category: :markdown do
end
context 'when content contains an upload' do
let(:image_uploader) { build(:file_uploader, project: source_parent) }
let(:image_uploader) { build(:file_uploader, container: source_parent) }
let(:content) { "Text and #{image_uploader.markdown_link}" }
it 'rewrites content' do
@ -96,7 +96,7 @@ RSpec.describe MarkdownContentRewriterService, feature_category: :markdown do
end
context 'when content has uploaded file references' do
let(:image_uploader) { build(:file_uploader, project: source_parent) }
let(:image_uploader) { build(:file_uploader, container: source_parent) }
let(:content) { "Text and #{image_uploader.markdown_link}" }
it { is_expected.to eq(false) }

View File

@ -149,7 +149,7 @@ RSpec.describe Notes::CopyService, feature_category: :team_planning do
end
context 'notes with upload' do
let(:uploader) { build(:file_uploader, project: from_noteable.project) }
let(:uploader) { build(:file_uploader, container: from_noteable.project) }
let(:text) { "Simple text with image: #{uploader.markdown_link} " }
let!(:note) { create(:note, noteable: from_noteable, note: text, project: from_noteable.project) }

View File

@ -114,7 +114,7 @@ RSpec.describe Projects::AfterRenameService, feature_category: :groups_and_proje
context 'attachments' do
let(:uploader) { create(:upload, :issuable_upload, :with_file, model: project) }
let(:file_uploader) { build(:file_uploader, project: project) }
let(:file_uploader) { build(:file_uploader, container: project) }
let(:legacy_storage_path) { File.join(file_uploader.root, legacy_storage.disk_path) }
let(:hashed_storage_path) { File.join(file_uploader.root, hashed_storage.disk_path) }

View File

@ -10,7 +10,7 @@ RSpec.describe Projects::HashedStorage::MigrateAttachmentsService, feature_categ
let(:hashed_storage) { Storage::Hashed.new(project) }
let!(:upload) { Upload.find_by(path: file_uploader.upload_path) }
let(:file_uploader) { build(:file_uploader, project: project) }
let(:file_uploader) { build(:file_uploader, container: project) }
let(:old_disk_path) { File.join(base_path(legacy_storage), upload.path) }
let(:new_disk_path) { File.join(base_path(hashed_storage), upload.path) }

Some files were not shown because too many files have changed in this diff Show More