Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
028bf690c4
commit
d68c057b16
|
|
@ -1436,7 +1436,6 @@ lib/gitlab/checks/**
|
|||
/app/uploaders/job_artifact_uploader.rb
|
||||
/app/validators/json_schemas/build_metadata_id_tokens.json
|
||||
/app/workers/build_queue_worker.rb
|
||||
/app/workers/ci_platform_metrics_update_cron_worker.rb
|
||||
/app/workers/create_pipeline_worker.rb
|
||||
/app/workers/expire_build_artifacts_worker.rb
|
||||
/app/workers/pipeline_hooks_worker.rb
|
||||
|
|
|
|||
|
|
@ -1966,7 +1966,6 @@ Gitlab/BoundedContexts:
|
|||
- 'app/workers/chaos/leak_mem_worker.rb'
|
||||
- 'app/workers/chaos/sleep_worker.rb'
|
||||
- 'app/workers/chat_notification_worker.rb'
|
||||
- 'app/workers/ci_platform_metrics_update_cron_worker.rb'
|
||||
- 'app/workers/cleanup_container_repository_worker.rb'
|
||||
- 'app/workers/click_house/audit_event_partition_sync_worker.rb'
|
||||
- 'app/workers/click_house/audit_events_sync_worker.rb'
|
||||
|
|
|
|||
|
|
@ -705,7 +705,6 @@ Gitlab/NamespacedClass:
|
|||
- 'app/workers/build_queue_worker.rb'
|
||||
- 'app/workers/bulk_import_worker.rb'
|
||||
- 'app/workers/chat_notification_worker.rb'
|
||||
- 'app/workers/ci_platform_metrics_update_cron_worker.rb'
|
||||
- 'app/workers/cleanup_container_repository_worker.rb'
|
||||
- 'app/workers/cluster_configure_istio_worker.rb'
|
||||
- 'app/workers/cluster_install_app_worker.rb'
|
||||
|
|
|
|||
|
|
@ -991,7 +991,6 @@ Layout/LineLength:
|
|||
- 'ee/app/workers/audit_events/audit_event_streaming_worker.rb'
|
||||
- 'ee/app/workers/concerns/elastic/migration_backfill_helper.rb'
|
||||
- 'ee/app/workers/concerns/elastic/migration_obsolete.rb'
|
||||
- 'ee/app/workers/elastic/migration_worker.rb'
|
||||
- 'ee/app/workers/elastic_delete_project_worker.rb'
|
||||
- 'ee/app/workers/elastic_namespace_rollout_worker.rb'
|
||||
- 'ee/app/workers/geo/destroy_worker.rb'
|
||||
|
|
@ -2136,7 +2135,6 @@ Layout/LineLength:
|
|||
- 'ee/spec/workers/ci/minutes/update_project_and_namespace_usage_worker_spec.rb'
|
||||
- 'ee/spec/workers/ci/upstream_projects_subscriptions_cleanup_worker_spec.rb'
|
||||
- 'ee/spec/workers/compliance_management/merge_requests/compliance_violations_worker_spec.rb'
|
||||
- 'ee/spec/workers/elastic/migration_worker_spec.rb'
|
||||
- 'ee/spec/workers/geo/destroy_worker_spec.rb'
|
||||
- 'ee/spec/workers/geo/prune_event_log_worker_spec.rb'
|
||||
- 'ee/spec/workers/geo/secondary/registry_consistency_worker_spec.rb'
|
||||
|
|
|
|||
|
|
@ -828,7 +828,6 @@ RSpec/ContextWording:
|
|||
- 'ee/spec/workers/ci/runners/stale_group_runners_prune_cron_worker_spec.rb'
|
||||
- 'ee/spec/workers/ci/upstream_projects_subscriptions_cleanup_worker_spec.rb'
|
||||
- 'ee/spec/workers/ee/repository_check/batch_worker_spec.rb'
|
||||
- 'ee/spec/workers/elastic/migration_worker_spec.rb'
|
||||
- 'ee/spec/workers/elastic_index_bulk_cron_worker_spec.rb'
|
||||
- 'ee/spec/workers/elastic_indexing_control_worker_spec.rb'
|
||||
- 'ee/spec/workers/geo/prune_event_log_worker_spec.rb'
|
||||
|
|
|
|||
|
|
@ -1112,7 +1112,6 @@ RSpec/NamedSubject:
|
|||
- 'ee/spec/workers/ee/issuable/related_links_create_worker_spec.rb'
|
||||
- 'ee/spec/workers/ee/issuable_export_csv_worker_spec.rb'
|
||||
- 'ee/spec/workers/ee/repository_check/batch_worker_spec.rb'
|
||||
- 'ee/spec/workers/elastic/migration_worker_spec.rb'
|
||||
- 'ee/spec/workers/elastic/namespace_update_worker_spec.rb'
|
||||
- 'ee/spec/workers/elastic_full_index_worker_spec.rb'
|
||||
- 'ee/spec/workers/elastic_indexing_control_worker_spec.rb'
|
||||
|
|
|
|||
|
|
@ -52,7 +52,6 @@ SidekiqLoadBalancing/WorkerDataConsistency:
|
|||
- 'app/workers/ci/stuck_builds/drop_running_worker.rb'
|
||||
- 'app/workers/ci/stuck_builds/drop_scheduled_worker.rb'
|
||||
- 'app/workers/ci/test_failure_history_worker.rb'
|
||||
- 'app/workers/ci_platform_metrics_update_cron_worker.rb'
|
||||
- 'app/workers/cleanup_container_repository_worker.rb'
|
||||
- 'app/workers/cluster_configure_istio_worker.rb'
|
||||
- 'app/workers/cluster_install_app_worker.rb'
|
||||
|
|
@ -317,7 +316,6 @@ SidekiqLoadBalancing/WorkerDataConsistency:
|
|||
- 'ee/app/workers/dependencies/export_worker.rb'
|
||||
- 'ee/app/workers/deployments/auto_rollback_worker.rb'
|
||||
- 'ee/app/workers/dora/daily_metrics/refresh_worker.rb'
|
||||
- 'ee/app/workers/elastic/migration_worker.rb'
|
||||
- 'ee/app/workers/elastic_association_indexer_worker.rb'
|
||||
- 'ee/app/workers/elastic_cluster_reindexing_cron_worker.rb'
|
||||
- 'ee/app/workers/elastic_commit_indexer_worker.rb'
|
||||
|
|
|
|||
|
|
@ -320,7 +320,6 @@ Style/GuardClause:
|
|||
- 'ee/app/validators/user_existence_validator.rb'
|
||||
- 'ee/app/workers/ee/ci/build_finished_worker.rb'
|
||||
- 'ee/app/workers/ee/post_receive.rb'
|
||||
- 'ee/app/workers/elastic/migration_worker.rb'
|
||||
- 'ee/app/workers/elastic_namespace_rollout_worker.rb'
|
||||
- 'ee/app/workers/epics/new_epic_issue_worker.rb'
|
||||
- 'ee/app/workers/geo/scheduler/scheduler_worker.rb'
|
||||
|
|
|
|||
|
|
@ -494,7 +494,6 @@ Style/IfUnlessModifier:
|
|||
- 'ee/spec/support/helpers/feature_approval_helper.rb'
|
||||
- 'ee/spec/support/helpers/search_results_helpers.rb'
|
||||
- 'ee/spec/support/http_io/http_io_helpers.rb'
|
||||
- 'ee/spec/workers/elastic/migration_worker_spec.rb'
|
||||
- 'lib/api/api_guard.rb'
|
||||
- 'lib/api/boards_responses.rb'
|
||||
- 'lib/api/branches.rb'
|
||||
|
|
|
|||
|
|
@ -796,7 +796,6 @@ Style/InlineDisableAnnotation:
|
|||
- 'app/workers/ci/track_failed_build_worker.rb'
|
||||
- 'app/workers/ci/unlock_pipelines_in_queue_worker.rb'
|
||||
- 'app/workers/ci/update_locked_unknown_artifacts_worker.rb'
|
||||
- 'app/workers/ci_platform_metrics_update_cron_worker.rb'
|
||||
- 'app/workers/cluster_configure_istio_worker.rb'
|
||||
- 'app/workers/cluster_install_app_worker.rb'
|
||||
- 'app/workers/cluster_patch_app_worker.rb'
|
||||
|
|
|
|||
2
Gemfile
2
Gemfile
|
|
@ -275,7 +275,7 @@ gem 'rack', '~> 2.2.9' # rubocop:todo Gemfile/MissingFeatureCategory
|
|||
gem 'rack-timeout', '~> 0.7.0', require: 'rack/timeout/base' # rubocop:todo Gemfile/MissingFeatureCategory
|
||||
|
||||
group :puma do
|
||||
gem 'puma', '= 6.4.0', require: false, feature_category: :shared
|
||||
gem 'puma', '= 6.4.3', require: false, feature_category: :shared
|
||||
gem 'sd_notify', '~> 0.1.0', require: false # rubocop:todo Gemfile/MissingFeatureCategory
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -525,8 +525,8 @@
|
|||
{"name":"pry-rails","version":"0.3.9","platform":"ruby","checksum":"468662575abb6b67f4a9831219f99290d5eae7bf186e64dd810d0a3e4a8cc4b1"},
|
||||
{"name":"pry-shell","version":"0.6.4","platform":"ruby","checksum":"ad024882d29912b071a7de65ebea538b242d2dc1498c60c7c2352ef94769f208"},
|
||||
{"name":"public_suffix","version":"6.0.1","platform":"ruby","checksum":"61d44e1cab5cbbbe5b31068481cf16976dd0dc1b6b07bd95617ef8c5e3e00c6f"},
|
||||
{"name":"puma","version":"6.4.0","platform":"java","checksum":"eb27679e9e665882bab85dfa84704b0615b4f77cec46de014f05b90a5ab36cfe"},
|
||||
{"name":"puma","version":"6.4.0","platform":"ruby","checksum":"d5dda11362744df9f4694708a62e3cfddf72eba7498c16016ebbb30f106712f9"},
|
||||
{"name":"puma","version":"6.4.3","platform":"java","checksum":"373fcfacacaafd0f5a24db18cb99b3f2decb5c5316470169852559aa80adc8ab"},
|
||||
{"name":"puma","version":"6.4.3","platform":"ruby","checksum":"24a4645c006811d83f2480057d1f54a96e7627b6b90e1c99b260b9dc630eb43e"},
|
||||
{"name":"pyu-ruby-sasl","version":"0.0.3.3","platform":"ruby","checksum":"5683a6bc5738db5a1bf5ceddeaf545405fb241b4184dd4f2587e679a7e9497e5"},
|
||||
{"name":"raabro","version":"1.4.0","platform":"ruby","checksum":"d4fa9ff5172391edb92b242eed8be802d1934b1464061ae5e70d80962c5da882"},
|
||||
{"name":"racc","version":"1.6.2","platform":"java","checksum":"0880781e7dfde09e665d0b6160b583e01ed52fcc2955d7891447d33c2d1d2cf1"},
|
||||
|
|
|
|||
|
|
@ -1451,7 +1451,7 @@ GEM
|
|||
tty-markdown
|
||||
tty-prompt
|
||||
public_suffix (6.0.1)
|
||||
puma (6.4.0)
|
||||
puma (6.4.3)
|
||||
nio4r (~> 2.0)
|
||||
pyu-ruby-sasl (0.0.3.3)
|
||||
raabro (1.4.0)
|
||||
|
|
@ -2232,7 +2232,7 @@ DEPENDENCIES
|
|||
pry-byebug
|
||||
pry-rails (~> 0.3.9)
|
||||
pry-shell (~> 0.6.4)
|
||||
puma (= 6.4.0)
|
||||
puma (= 6.4.3)
|
||||
rack (~> 2.2.9)
|
||||
rack-attack (~> 6.7.0)
|
||||
rack-cors (~> 2.0.1)
|
||||
|
|
|
|||
|
|
@ -535,8 +535,8 @@
|
|||
{"name":"psych","version":"5.1.2","platform":"java","checksum":"1dd68dc609eddbc884e6892e11da942e16f7256bd30ebde9d35449d43043a6fe"},
|
||||
{"name":"psych","version":"5.1.2","platform":"ruby","checksum":"337322f58fc2bf24827d2b9bd5ab595f6a72971867d151bb39980060ea40a368"},
|
||||
{"name":"public_suffix","version":"6.0.1","platform":"ruby","checksum":"61d44e1cab5cbbbe5b31068481cf16976dd0dc1b6b07bd95617ef8c5e3e00c6f"},
|
||||
{"name":"puma","version":"6.4.0","platform":"java","checksum":"eb27679e9e665882bab85dfa84704b0615b4f77cec46de014f05b90a5ab36cfe"},
|
||||
{"name":"puma","version":"6.4.0","platform":"ruby","checksum":"d5dda11362744df9f4694708a62e3cfddf72eba7498c16016ebbb30f106712f9"},
|
||||
{"name":"puma","version":"6.4.3","platform":"java","checksum":"373fcfacacaafd0f5a24db18cb99b3f2decb5c5316470169852559aa80adc8ab"},
|
||||
{"name":"puma","version":"6.4.3","platform":"ruby","checksum":"24a4645c006811d83f2480057d1f54a96e7627b6b90e1c99b260b9dc630eb43e"},
|
||||
{"name":"pyu-ruby-sasl","version":"0.0.3.3","platform":"ruby","checksum":"5683a6bc5738db5a1bf5ceddeaf545405fb241b4184dd4f2587e679a7e9497e5"},
|
||||
{"name":"raabro","version":"1.4.0","platform":"ruby","checksum":"d4fa9ff5172391edb92b242eed8be802d1934b1464061ae5e70d80962c5da882"},
|
||||
{"name":"racc","version":"1.6.2","platform":"java","checksum":"0880781e7dfde09e665d0b6160b583e01ed52fcc2955d7891447d33c2d1d2cf1"},
|
||||
|
|
|
|||
|
|
@ -1456,7 +1456,7 @@ GEM
|
|||
psych (5.1.2)
|
||||
stringio
|
||||
public_suffix (6.0.1)
|
||||
puma (6.4.0)
|
||||
puma (6.4.3)
|
||||
nio4r (~> 2.0)
|
||||
pyu-ruby-sasl (0.0.3.3)
|
||||
raabro (1.4.0)
|
||||
|
|
@ -2247,7 +2247,7 @@ DEPENDENCIES
|
|||
pry-byebug
|
||||
pry-rails (~> 0.3.9)
|
||||
pry-shell (~> 0.6.4)
|
||||
puma (= 6.4.0)
|
||||
puma (= 6.4.3)
|
||||
rack (~> 2.2.9)
|
||||
rack-attack (~> 6.7.0)
|
||||
rack-cors (~> 2.0.1)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ export const BRANCH_REF_TYPE = 'heads';
|
|||
export const TAG_REF_TYPE = 'tags';
|
||||
export const TAG_REF_TYPE_ICON = 'tag';
|
||||
export const BRANCH_REF_TYPE_ICON = 'branch';
|
||||
export const SEARCH_ICON = 'search';
|
||||
export const REF_TYPE_PARAM_NAME = 'ref_type';
|
||||
|
||||
export const X_TOTAL_HEADER = 'x-total';
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
<script>
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { mapGetters } from 'vuex';
|
||||
import { mapGetters, mapState } from 'vuex';
|
||||
import StatusFilter from './status_filter/index.vue';
|
||||
import FiltersTemplate from './filters_template.vue';
|
||||
import ArchivedFilter from './archived_filter/index.vue';
|
||||
import SourceBranchFilter from './source_branch_filter/index.vue';
|
||||
|
||||
export default {
|
||||
name: 'MergeRequestsFilters',
|
||||
|
|
@ -11,9 +12,15 @@ export default {
|
|||
StatusFilter,
|
||||
FiltersTemplate,
|
||||
ArchivedFilter,
|
||||
SourceBranchFilter,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['hasProjectContext']),
|
||||
...mapState(['groupInitialJson']),
|
||||
shouldShowSourceBranchFilter() {
|
||||
// this will be changed https://gitlab.com/gitlab-org/gitlab/-/issues/480740
|
||||
return !this.hasProjectContext || this.groupInitialJson?.id;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -22,5 +29,6 @@ export default {
|
|||
<filters-template>
|
||||
<status-filter class="gl-mb-5" />
|
||||
<archived-filter v-if="hasProjectContext" class="gl-mb-5" />
|
||||
<source-branch-filter v-if="shouldShowSourceBranchFilter" class="gl-mb-5" />
|
||||
</filters-template>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,143 @@
|
|||
<script>
|
||||
import { GlIcon, GlCollapsibleListbox } from '@gitlab/ui';
|
||||
import { debounce } from 'lodash';
|
||||
import { s__ } from '~/locale';
|
||||
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
|
||||
import { SEARCH_ICON } from '../../constants';
|
||||
|
||||
export default {
|
||||
name: 'BranchDropdown',
|
||||
components: {
|
||||
GlIcon,
|
||||
GlCollapsibleListbox,
|
||||
},
|
||||
props: {
|
||||
sourceBranches: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
errors: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
headerText: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
searchBranchText: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
selectedBranch: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: SEARCH_ICON,
|
||||
},
|
||||
isLoading: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
i18n: {
|
||||
noSearchResultsText: s__('GlobalSearch|No matching results'),
|
||||
noLoadResultsText: s__('GlobalSearch|No results found'),
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectedRef: '',
|
||||
query: '',
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
extendedToggleButtonClass() {
|
||||
return [
|
||||
{
|
||||
'!gl-shadow-inner-1-red-500': this.hasError,
|
||||
'gl-font-monospace': Boolean(this.selectedBranch),
|
||||
},
|
||||
'gl-mb-0',
|
||||
];
|
||||
},
|
||||
isSearching() {
|
||||
return this.query.length > 0;
|
||||
},
|
||||
dropdownItems() {
|
||||
return this.isSearching ? this.searchResults : this.sourceBranches;
|
||||
},
|
||||
noResultsText() {
|
||||
return this.isSearching
|
||||
? this.$options.i18n.noSearchResultsText
|
||||
: this.$options.i18n.noLoadResultsText;
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.debouncedSearch = debounce(this.search, DEFAULT_DEBOUNCE_AND_THROTTLE_MS);
|
||||
},
|
||||
methods: {
|
||||
onSearchBoxInput(searchQuery = '') {
|
||||
this.query = searchQuery?.trim();
|
||||
this.debouncedSearch();
|
||||
},
|
||||
search() {
|
||||
if (!this.query) {
|
||||
this.searchResults = [];
|
||||
return;
|
||||
}
|
||||
this.searchResults = this.sourceBranches.filter((branch) => branch.text.includes(this.query));
|
||||
},
|
||||
selectRef(ref) {
|
||||
this.$emit('selected', ref);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<gl-collapsible-listbox
|
||||
class="ref-selector gl-w-full"
|
||||
block
|
||||
searchable
|
||||
resetable
|
||||
:selected="selectedBranch"
|
||||
:header-text="s__('GlobalSearch|Source branch')"
|
||||
:items="dropdownItems"
|
||||
:no-results-text="noResultsText"
|
||||
:searching="isLoading"
|
||||
:search-placeholder="searchBranchText"
|
||||
:toggle-class="extendedToggleButtonClass"
|
||||
:toggle-text="searchBranchText"
|
||||
:icon="icon"
|
||||
:loading="isLoading"
|
||||
:reset-button-label="s__('GlobalSearch|Reset')"
|
||||
v-bind="$attrs"
|
||||
v-on="$listeners"
|
||||
@hidden="$emit('hide')"
|
||||
@search="onSearchBoxInput"
|
||||
@select="selectRef"
|
||||
@reset="$emit('reset')"
|
||||
>
|
||||
<template #list-item="{ item }">
|
||||
{{ item.text }}
|
||||
</template>
|
||||
<template #footer>
|
||||
<div
|
||||
v-for="errorMessage in errors"
|
||||
:key="errorMessage"
|
||||
data-testid="branch-dropdown-error-list"
|
||||
class="gl-mx-4 gl-my-3 gl-flex gl-items-start gl-text-red-500"
|
||||
>
|
||||
<gl-icon name="error" class="gl-mr-2 gl-mt-2 gl-shrink-0" />
|
||||
<span>{{ errorMessage }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</gl-collapsible-listbox>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
<script>
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { mapActions, mapState } from 'vuex';
|
||||
import { GlFormCheckbox, GlTooltipDirective } from '@gitlab/ui';
|
||||
import { s__ } from '~/locale';
|
||||
import AjaxCache from '~/lib/utils/ajax_cache';
|
||||
import { mergeUrlParams } from '~/lib/utils/url_utility';
|
||||
import { InternalEvents } from '~/tracking';
|
||||
import BranchDropdown from '~/search/sidebar/components/shared/branch_dropdown.vue';
|
||||
import { BRANCH_REF_TYPE_ICON } from '~/ref/constants';
|
||||
import {
|
||||
SEARCH_ICON,
|
||||
EVENT_SELECT_SOURCE_BRANCH_FILTER_ON_MERGE_REQUEST_PAGE,
|
||||
} 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: {
|
||||
BranchDropdown,
|
||||
GlFormCheckbox,
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
mixins: [trackingMixin],
|
||||
data() {
|
||||
return {
|
||||
sourceBranches: [],
|
||||
errors: [],
|
||||
toggleState: false,
|
||||
selectedBranch: '',
|
||||
isLoading: false,
|
||||
};
|
||||
},
|
||||
i18n: {
|
||||
toggleTooltip: s__('GlobalSearch|Toggle if results have source branch included or excluded'),
|
||||
},
|
||||
computed: {
|
||||
...mapState(['groupInitialJson', 'projectInitialJson', 'query']),
|
||||
showDropdownPlaceholderText() {
|
||||
return !this.selectedBranch ? s__('GlobalSearch|Search') : this.selectedBranch;
|
||||
},
|
||||
showDropdownPlaceholderIcon() {
|
||||
return !this.selectedBranch ? SEARCH_ICON : BRANCH_REF_TYPE_ICON;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.selectedBranch =
|
||||
this.query?.[SOURCE_BRANCH_PARAM] || this.query?.[NOT_SOURCE_BRANCH_PARAM];
|
||||
this.toggleState = Boolean(this.query?.[NOT_SOURCE_BRANCH_PARAM]);
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['setQuery', 'applyQuery']),
|
||||
getMergeRequestSourceBranchesEndpoint() {
|
||||
const endpoint = `${gon.relative_url_root || ''}${SOURCE_BRANCH_ENDPOINT_PATH}`;
|
||||
const params = {
|
||||
group_id: this.groupInitialJson?.id || null,
|
||||
project_id: this.projectInitialJson?.id || null,
|
||||
};
|
||||
return mergeUrlParams(params, endpoint);
|
||||
},
|
||||
convertToListboxItems(data) {
|
||||
return data.map((item) => ({
|
||||
text: item.title,
|
||||
value: item.title,
|
||||
}));
|
||||
},
|
||||
async getCachedSourceBranches() {
|
||||
this.isLoading = true;
|
||||
try {
|
||||
const data = await AjaxCache.retrieve(this.getMergeRequestSourceBranchesEndpoint());
|
||||
this.errors = [];
|
||||
this.isLoading = false;
|
||||
this.sourceBranches = this.convertToListboxItems(data);
|
||||
} catch (e) {
|
||||
this.isLoading = false;
|
||||
this.errors.push(e.message);
|
||||
}
|
||||
},
|
||||
handleSelected(ref) {
|
||||
this.selectedBranch = ref;
|
||||
|
||||
if (this.toggleState) {
|
||||
this.setQuery({ key: SOURCE_BRANCH_PARAM, value: '' });
|
||||
this.setQuery({ key: NOT_SOURCE_BRANCH_PARAM, value: ref });
|
||||
this.trackEvent(EVENT_SELECT_SOURCE_BRANCH_FILTER_ON_MERGE_REQUEST_PAGE, {
|
||||
label: 'exclude',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.setQuery({ key: SOURCE_BRANCH_PARAM, value: ref });
|
||||
this.setQuery({ key: NOT_SOURCE_BRANCH_PARAM, value: '' });
|
||||
this.trackEvent(EVENT_SELECT_SOURCE_BRANCH_FILTER_ON_MERGE_REQUEST_PAGE, {
|
||||
label: 'include',
|
||||
});
|
||||
},
|
||||
changeCheckboxInput(state) {
|
||||
this.toggleState = state;
|
||||
this.handleSelected(this.selectedBranch);
|
||||
},
|
||||
handleReset() {
|
||||
this.toggleState = false;
|
||||
this.setQuery({ key: SOURCE_BRANCH_PARAM, value: '' });
|
||||
this.setQuery({ key: NOT_SOURCE_BRANCH_PARAM, value: '' });
|
||||
this.applyQuery();
|
||||
},
|
||||
},
|
||||
SOURCE_BRANCH_PARAM,
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="gl-relative gl-pb-0 md:gl-pt-0">
|
||||
<div class="gl-mb-2 gl-text-sm gl-font-bold" data-testid="source-branch-filter-title">
|
||||
{{ s__('GlobalSearch|Source branch') }}
|
||||
</div>
|
||||
<branch-dropdown
|
||||
:source-branches="sourceBranches"
|
||||
:errors="errors"
|
||||
:header-text="s__('GlobalSearch|Source branch')"
|
||||
:search-branch-text="showDropdownPlaceholderText"
|
||||
:selected-branch="selectedBranch"
|
||||
:icon="showDropdownPlaceholderIcon"
|
||||
:is-loading="isLoading"
|
||||
@selected="handleSelected"
|
||||
@shown="getCachedSourceBranches"
|
||||
@reset="handleReset"
|
||||
/>
|
||||
<gl-form-checkbox
|
||||
v-model="toggleState"
|
||||
class="gl-inline-flex gl-w-full gl-grow gl-justify-between"
|
||||
@input="changeCheckboxInput"
|
||||
>
|
||||
<span v-gl-tooltip="$options.i18n.toggleTooltip" data-testid="branch">
|
||||
{{ s__('GlobalSearch|Branch not included') }}
|
||||
</span>
|
||||
</gl-form-checkbox>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -28,6 +28,8 @@ export const SEARCH_TYPE_BASIC = 'basic';
|
|||
export const SEARCH_TYPE_ADVANCED = 'advanced';
|
||||
export const SEARCH_TYPE_ZOEKT = 'zoekt';
|
||||
|
||||
export const SEARCH_ICON = 'search';
|
||||
|
||||
export const ANY_OPTION = {
|
||||
id: null,
|
||||
name: __('Any'),
|
||||
|
|
@ -50,3 +52,8 @@ export const PROJECT_DATA = {
|
|||
|
||||
export const EVENT_CLICK_ZOEKT_INCLUDE_FORKS_ON_SEARCH_RESULTS_PAGE =
|
||||
'click_zoekt_include_forks_on_search_results_page';
|
||||
|
||||
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';
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { omitBy } from 'lodash';
|
||||
import Api from '~/api';
|
||||
import { createAlert } from '~/alert';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
|
|
@ -124,7 +125,8 @@ export const setQuery = ({ state, commit, getters }, { key, value }) => {
|
|||
};
|
||||
|
||||
export const applyQuery = ({ state }) => {
|
||||
visitUrl(setUrlParams({ ...state.query, page: null }, window.location.href, false, true));
|
||||
const query = omitBy(state.query, (item) => item === '');
|
||||
visitUrl(setUrlParams({ ...query, page: null }, window.location.href, true, true));
|
||||
};
|
||||
|
||||
export const resetQuery = ({ state }) => {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,10 @@ import { LABEL_FILTER_PARAM } from '~/search/sidebar/components/label_filter/dat
|
|||
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 {
|
||||
SOURCE_BRANCH_PARAM,
|
||||
NOT_SOURCE_BRANCH_PARAM,
|
||||
} from '~/search/sidebar/components/source_branch_filter/index.vue';
|
||||
|
||||
export const MAX_FREQUENT_ITEMS = 5;
|
||||
|
||||
|
|
@ -21,6 +25,8 @@ export const SIDEBAR_PARAMS = [
|
|||
LABEL_FILTER_PARAM,
|
||||
archivedFilterData.filterParam,
|
||||
INCLUDE_FORKED_FILTER_PARAM,
|
||||
SOURCE_BRANCH_PARAM,
|
||||
NOT_SOURCE_BRANCH_PARAM,
|
||||
];
|
||||
|
||||
export const REGEX_PARAM = 'regex';
|
||||
|
|
|
|||
|
|
@ -321,6 +321,10 @@
|
|||
.description {
|
||||
line-height: 1.5;
|
||||
max-height: $gl-spacing-scale-8;
|
||||
|
||||
a {
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -301,7 +301,7 @@ class Commit
|
|||
end
|
||||
|
||||
def lazy_author
|
||||
BatchLoader.for(author_email.downcase).batch do |emails, loader|
|
||||
BatchLoader.for(author_email&.downcase).batch do |emails, loader|
|
||||
users = User.by_any_email(emails, confirmed: true).includes(:emails)
|
||||
|
||||
emails.each do |email|
|
||||
|
|
@ -317,7 +317,7 @@ class Commit
|
|||
lazy_author&.itself
|
||||
end
|
||||
end
|
||||
request_cache(:author) { author_email.downcase }
|
||||
request_cache(:author) { author_email&.downcase }
|
||||
|
||||
def committer(confirmed: true)
|
||||
@committer ||= User.find_by_any_email(committer_email, confirmed: confirmed)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ module Packages
|
|||
|
||||
has_many :conan_recipe_revisions, inverse_of: :package, class_name: 'Packages::Conan::RecipeRevision'
|
||||
|
||||
has_many :conan_package_references, inverse_of: :package, class_name: 'Packages::Conan::PackageReference'
|
||||
|
||||
accepts_nested_attributes_for :conan_metadatum
|
||||
|
||||
delegate :recipe, :recipe_path, to: :conan_metadatum, prefix: :conan
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Packages
|
||||
module Conan
|
||||
class PackageReference < ApplicationRecord
|
||||
include ShaAttribute
|
||||
|
||||
REFERENCE_LENGTH_MAX = 20
|
||||
MAX_INFO_SIZE = 20_000
|
||||
|
||||
sha_attribute :reference
|
||||
|
||||
belongs_to :package, class_name: 'Packages::Conan::Package',
|
||||
inverse_of: :conan_package_references
|
||||
belongs_to :recipe_revision, class_name: 'Packages::Conan::RecipeRevision',
|
||||
inverse_of: :conan_package_references
|
||||
belongs_to :project
|
||||
|
||||
validates :package, :project, presence: true
|
||||
validates :reference, presence: true, bytesize: { maximum: -> { REFERENCE_LENGTH_MAX } },
|
||||
uniqueness: { scope: [:package_id, :recipe_revision_id] }
|
||||
validates :info, json_schema: { filename: 'conan_package_info', detail_errors: true }
|
||||
validate :ensure_info_size
|
||||
|
||||
private
|
||||
|
||||
def ensure_info_size
|
||||
return if info.to_s.size <= MAX_INFO_SIZE
|
||||
|
||||
errors.add(:info, :too_large,
|
||||
message: format(
|
||||
_('conaninfo is too large. Maximum size is %{max_size} characters'),
|
||||
max_size: MAX_INFO_SIZE
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -12,6 +12,9 @@ module Packages
|
|||
belongs_to :package, class_name: 'Packages::Conan::Package', inverse_of: :conan_recipe_revisions
|
||||
belongs_to :project
|
||||
|
||||
has_many :conan_package_references, inverse_of: :recipe_revision,
|
||||
class_name: 'Packages::Conan::PackageReference'
|
||||
|
||||
validates :package, :project, presence: true
|
||||
validates :revision, presence: true, bytesize: { maximum: -> { REVISION_LENGTH_MAX } },
|
||||
uniqueness: { scope: :package_id }
|
||||
|
|
|
|||
|
|
@ -805,6 +805,8 @@ class User < ApplicationRecord
|
|||
# @param emails [String, Array<String>] email addresses to check
|
||||
# @param confirmed [Boolean] Only return users where the primary email is confirmed
|
||||
def by_any_email(emails, confirmed: false)
|
||||
return none if Array(emails).all?(&:nil?)
|
||||
|
||||
from_users = by_user_email(emails)
|
||||
from_users = from_users.confirmed if confirmed
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,9 @@ module VirtualRegistries
|
|||
belongs_to :group
|
||||
belongs_to :upstream, class_name: 'VirtualRegistries::Packages::Maven::Upstream', inverse_of: :cached_responses
|
||||
|
||||
# Used in destroying stale cached responses in DestroyOrphanCachedResponsesWorker
|
||||
enum :status, default: 0, processing: 1, error: 3
|
||||
|
||||
validates :group, top_level_group: true, presence: true
|
||||
validates :relative_path,
|
||||
:object_storage_key,
|
||||
|
|
@ -35,6 +38,12 @@ module VirtualRegistries
|
|||
scope :search_by_relative_path, ->(query) do
|
||||
fuzzy_search(query, [:relative_path], use_minimum_char_limit: false)
|
||||
end
|
||||
scope :orphan, -> { where(upstream: nil) }
|
||||
scope :pending_destruction, -> { orphan.default }
|
||||
|
||||
def self.next_pending_destruction
|
||||
pending_destruction.lock('FOR UPDATE SKIP LOCKED').take
|
||||
end
|
||||
|
||||
# create or update a cached response identified by the upstream, group_id and relative_path
|
||||
# Given that we have chances that this function is not executed in isolation, we can't use
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"description": "Conan Info Schema",
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"settings": {
|
||||
"type": "object"
|
||||
},
|
||||
"requires": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"settings",
|
||||
"requires",
|
||||
"options"
|
||||
],
|
||||
"additionalProperties": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -33,7 +33,6 @@
|
|||
= render 'groups/settings/lfs', f: f
|
||||
= render_if_exists 'groups/settings/auto_assign_duo_pro', f: f, group: @group
|
||||
= render_if_exists 'groups/settings/duo_features_enabled', f: f, group: @group
|
||||
= render_if_exists 'groups/settings/experimental_settings', f: f, group: @group
|
||||
= render_if_exists 'groups/settings/product_analytics_settings', f: f, group: @group
|
||||
= render 'groups/settings/git_access_protocols', f: f, group: @group
|
||||
= render 'groups/settings/project_creation_level', f: f, group: @group
|
||||
|
|
|
|||
|
|
@ -300,15 +300,6 @@
|
|||
:weight: 1
|
||||
:idempotent: true
|
||||
:tags: []
|
||||
- :name: cronjob:ci_platform_metrics_update_cron
|
||||
:worker_name: CiPlatformMetricsUpdateCronWorker
|
||||
:feature_category: :continuous_integration
|
||||
:has_external_dependencies: false
|
||||
:urgency: :low
|
||||
:resource_boundary: :cpu
|
||||
:weight: 1
|
||||
:idempotent: false
|
||||
:tags: []
|
||||
- :name: cronjob:ci_runners_reconcile_existing_runner_versions_cron
|
||||
:worker_name: Ci::Runners::ReconcileExistingRunnerVersionsCronWorker
|
||||
:feature_category: :fleet_visibility
|
||||
|
|
@ -1038,6 +1029,15 @@
|
|||
:weight: 1
|
||||
:idempotent: true
|
||||
:tags: []
|
||||
- :name: dependency_proxy_blob:virtual_registries_packages_destroy_orphan_cached_responses
|
||||
:worker_name: VirtualRegistries::Packages::DestroyOrphanCachedResponsesWorker
|
||||
:feature_category: :virtual_registry
|
||||
:has_external_dependencies: false
|
||||
:urgency: :low
|
||||
:resource_boundary: :unknown
|
||||
:weight: 1
|
||||
:idempotent: true
|
||||
:tags: []
|
||||
- :name: dependency_proxy_manifest:dependency_proxy_cleanup_manifest
|
||||
:worker_name: DependencyProxy::CleanupManifestWorker
|
||||
:feature_category: :virtual_registry
|
||||
|
|
|
|||
|
|
@ -1,18 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CiPlatformMetricsUpdateCronWorker # rubocop:disable Scalability/IdempotentWorker
|
||||
include ApplicationWorker
|
||||
|
||||
data_consistency :always
|
||||
|
||||
# This worker does not perform work scoped to a context
|
||||
include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
|
||||
|
||||
feature_category :continuous_integration
|
||||
urgency :low
|
||||
worker_resource_boundary :cpu
|
||||
|
||||
def perform
|
||||
# no-op
|
||||
end
|
||||
end
|
||||
|
|
@ -13,6 +13,7 @@ module DependencyProxy
|
|||
def perform
|
||||
enqueue_blob_cleanup_job if DependencyProxy::Blob.pending_destruction.any?
|
||||
enqueue_manifest_cleanup_job if DependencyProxy::Manifest.pending_destruction.any?
|
||||
enqueue_vreg_packages_cached_response_cleanup_job
|
||||
end
|
||||
|
||||
private
|
||||
|
|
@ -24,5 +25,13 @@ module DependencyProxy
|
|||
def enqueue_manifest_cleanup_job
|
||||
DependencyProxy::CleanupManifestWorker.perform_with_capacity
|
||||
end
|
||||
|
||||
def enqueue_vreg_packages_cached_response_cleanup_job
|
||||
[::VirtualRegistries::Packages::Maven::CachedResponse].each do |klass|
|
||||
if klass.pending_destruction.any?
|
||||
::VirtualRegistries::Packages::DestroyOrphanCachedResponsesWorker.perform_with_capacity(klass.name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,72 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module VirtualRegistries
|
||||
module Packages
|
||||
class DestroyOrphanCachedResponsesWorker
|
||||
include ApplicationWorker
|
||||
include LimitedCapacity::Worker
|
||||
|
||||
MAX_CAPACITY = 2
|
||||
|
||||
data_consistency :sticky
|
||||
urgency :low
|
||||
idempotent!
|
||||
|
||||
queue_namespace :dependency_proxy_blob
|
||||
feature_category :virtual_registry
|
||||
|
||||
def perform_work(model)
|
||||
next_item = next_item(model.constantize)
|
||||
|
||||
return unless next_item
|
||||
|
||||
next_item.destroy!
|
||||
log_metadata(next_item)
|
||||
rescue StandardError => exception
|
||||
next_item&.update_column(:status, :error) unless next_item&.destroyed?
|
||||
|
||||
Gitlab::ErrorTracking.log_exception(
|
||||
exception,
|
||||
class: self.class.name
|
||||
)
|
||||
end
|
||||
|
||||
def remaining_work_count(model)
|
||||
model.constantize.pending_destruction.limit(max_running_jobs + 1).count
|
||||
end
|
||||
|
||||
def max_running_jobs
|
||||
MAX_CAPACITY
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def next_item(klass)
|
||||
klass.transaction do
|
||||
next_item = klass.next_pending_destruction
|
||||
|
||||
if next_item
|
||||
next_item.update_column(:status, :processing)
|
||||
log_cleanup_item(next_item)
|
||||
end
|
||||
|
||||
next_item
|
||||
end
|
||||
end
|
||||
|
||||
def log_metadata(cached_response)
|
||||
log_extra_metadata_on_done(:cached_response_id, cached_response.id)
|
||||
log_extra_metadata_on_done(:group_id, cached_response.group_id)
|
||||
log_extra_metadata_on_done(:relative_path, cached_response.relative_path)
|
||||
end
|
||||
|
||||
def log_cleanup_item(cached_response)
|
||||
logger.info(
|
||||
structured_payload(
|
||||
cached_response_id: cached_response.id
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
description: Merge request source branch filter
|
||||
internal_events: true
|
||||
action: select_source_branch_filter_on_merge_request_page
|
||||
identifiers:
|
||||
- project
|
||||
- namespace
|
||||
- user
|
||||
additional_properties:
|
||||
label:
|
||||
description: include or exclude
|
||||
product_group: global_search
|
||||
milestone: '17.5'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/156491
|
||||
distributions:
|
||||
- ce
|
||||
- ee
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
|
|
@ -592,10 +592,6 @@ production: &base
|
|||
schedule_migrate_external_diffs_worker:
|
||||
cron: "15 * * * *"
|
||||
|
||||
# Update CI Platform Metrics daily
|
||||
ci_platform_metrics_update_cron_worker:
|
||||
cron: "47 9 * * *"
|
||||
|
||||
# Periodically update ci_runner_versions table with up-to-date versions and status.
|
||||
ci_runner_versions_reconciliation_worker:
|
||||
cron: "@daily"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,77 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
return unless Gitlab::Runtime.puma?
|
||||
|
||||
require 'puma'
|
||||
require 'puma/cluster'
|
||||
|
||||
# Ruby 3.1 and 3.2 have bugs that prevents Puma from reaping child processes properly:
|
||||
# https://bugs.ruby-lang.org/issues/20490
|
||||
# https://bugs.ruby-lang.org/issues/19837
|
||||
#
|
||||
# https://github.com/puma/puma/pull/3314 fixes this in Puma, but a release
|
||||
# has not been forthcoming.
|
||||
if Gem::Version.new(Puma::Const::PUMA_VERSION) > Gem::Version.new('6.5')
|
||||
raise 'This patch should not be needed after Puma 6.5.0.'
|
||||
end
|
||||
|
||||
# rubocop:disable Style/RedundantBegin -- These are upstream changes
|
||||
# rubocop:disable Cop/LineBreakAfterGuardClauses -- These are upstream changes
|
||||
# rubocop:disable Layout/EmptyLineAfterGuardClause -- These are upstream changes
|
||||
module Puma
|
||||
class Cluster < Runner
|
||||
# loops thru @workers, removing workers that exited, and calling
|
||||
# `#term` if needed
|
||||
def wait_workers
|
||||
# Reap all children, known workers or otherwise.
|
||||
# If puma has PID 1, as it's common in containerized environments,
|
||||
# then it's responsible for reaping orphaned processes, so we must reap
|
||||
# all our dead children, regardless of whether they are workers we spawned
|
||||
# or some reattached processes.
|
||||
reaped_children = {}
|
||||
loop do
|
||||
begin
|
||||
pid, status = Process.wait2(-1, Process::WNOHANG)
|
||||
break unless pid
|
||||
reaped_children[pid] = status
|
||||
rescue Errno::ECHILD
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
@workers.reject! do |w|
|
||||
next false if w.pid.nil?
|
||||
begin
|
||||
# We may need to check the PID individually because:
|
||||
# 1. From Ruby versions 2.6 to 3.2, `Process.detach` can prevent or delay
|
||||
# `Process.wait2(-1)` from detecting a terminated process: https://bugs.ruby-lang.org/issues/19837.
|
||||
# 2. When `fork_worker` is enabled, some worker may not be direct children,
|
||||
# but grand children. Because of this they won't be reaped by `Process.wait2(-1)`.
|
||||
if reaped_children.delete(w.pid) || Process.wait(w.pid, Process::WNOHANG)
|
||||
true
|
||||
else
|
||||
w.term if w.term?
|
||||
nil
|
||||
end
|
||||
rescue Errno::ECHILD
|
||||
begin
|
||||
Process.kill(0, w.pid)
|
||||
# child still alive but has another parent (e.g., using fork_worker)
|
||||
w.term if w.term?
|
||||
false
|
||||
rescue Errno::ESRCH, Errno::EPERM
|
||||
true # child is already terminated
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Log unknown children
|
||||
reaped_children.each do |pid, status|
|
||||
log "! reaped unknown child process pid=#{pid} status=#{status}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
# rubocop:enable Style/RedundantBegin
|
||||
# rubocop:enable Cop/LineBreakAfterGuardClauses
|
||||
# rubocop:enable Layout/EmptyLineAfterGuardClause
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
---
|
||||
key_path: redis_hll_counters.count_distinct_user_id_from_select_source_branch_exclude_filter_on_merge_request_page_monthly
|
||||
description: Monthly count of unique users who selected source branch exlude option
|
||||
product_group: global_search
|
||||
performance_indicator_type: []
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: '17.5'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/156491
|
||||
time_frame: 28d
|
||||
data_source: internal_events
|
||||
data_category: optional
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
events:
|
||||
- name: select_source_branch_filter_on_merge_request_page
|
||||
unique: user.id
|
||||
filter:
|
||||
label: exclude
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
---
|
||||
key_path: redis_hll_counters.count_distinct_user_id_from_select_source_branch_include_filter_on_merge_request_page_monthly
|
||||
description: Monthly count of unique users who selected source branch with include option
|
||||
product_group: global_search
|
||||
performance_indicator_type: []
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: '17.5'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/156491
|
||||
time_frame: 28d
|
||||
data_source: internal_events
|
||||
data_category: optional
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
events:
|
||||
- name: select_source_branch_filter_on_merge_request_page
|
||||
unique: user.id
|
||||
filter:
|
||||
label: include
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
---
|
||||
key_path: redis_hll_counters.count_distinct_user_id_from_select_source_branch_exclude_filter_on_merge_request_page_weekly
|
||||
description: Weekly count of unique users who selected source branch exlude option
|
||||
product_group: global_search
|
||||
performance_indicator_type: []
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: '17.5'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/156491
|
||||
time_frame: 7d
|
||||
data_source: internal_events
|
||||
data_category: optional
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
events:
|
||||
- name: select_source_branch_filter_on_merge_request_page
|
||||
unique: user.id
|
||||
filter:
|
||||
label: exclude
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
---
|
||||
key_path: redis_hll_counters.count_distinct_user_id_from_select_source_branch_include_filter_on_merge_request_page_weekly
|
||||
description: Weekly count of unique users who selected source branch with include option
|
||||
product_group: global_search
|
||||
performance_indicator_type: []
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: '17.5'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/156491
|
||||
time_frame: 7d
|
||||
data_source: internal_events
|
||||
data_category: optional
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
events:
|
||||
- name: select_source_branch_filter_on_merge_request_page
|
||||
unique: user.id
|
||||
filter:
|
||||
label: include
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
---
|
||||
table_name: packages_conan_package_references
|
||||
classes:
|
||||
- Packages::Conan::PackageReference
|
||||
feature_categories:
|
||||
- package_registry
|
||||
description: Conan package references
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/166443
|
||||
milestone: '17.5'
|
||||
gitlab_schema: gitlab_main_cell
|
||||
sharding_key:
|
||||
project_id: projects
|
||||
|
|
@ -9,6 +9,7 @@ classes:
|
|||
- Packages::Helm::Package
|
||||
- Packages::MlModel::Package
|
||||
- Packages::Package
|
||||
- Packages::Pypi::Package
|
||||
- Packages::Rpm::Package
|
||||
- Packages::Rubygems::Package
|
||||
feature_categories:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddStatusToVirtualRegistriesPackagesMavenCachedResponses < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.5'
|
||||
|
||||
def change
|
||||
add_column :virtual_registries_packages_maven_cached_responses, :status, :smallint, default: 0, null: false
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CreatePackagesConanPackageReferences < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.5'
|
||||
|
||||
UNIQ_IND_PACKAGE_REVISION_REF = 'uniq_idx_on_packages_conan_package_references_package_reference'
|
||||
CONSTRAINT_NAME = 'chk_conan_references_info_length'
|
||||
|
||||
def up
|
||||
create_table :packages_conan_package_references do |t| # rubocop:disable Migration/EnsureFactoryForTable -- https://gitlab.com/gitlab-org/gitlab/-/issues/468630
|
||||
t.bigint :package_id, null: false
|
||||
t.bigint :project_id, null: false
|
||||
t.bigint :recipe_revision_id
|
||||
t.timestamps_with_timezone null: false
|
||||
t.binary :reference, null: false, limit: 20 # A SHA-1 hash (20 bytes)
|
||||
t.jsonb :info, default: {}, null: false
|
||||
|
||||
t.index :project_id
|
||||
t.index :recipe_revision_id
|
||||
t.index [:package_id, :recipe_revision_id, :reference], unique: true, name: UNIQ_IND_PACKAGE_REVISION_REF
|
||||
|
||||
t.check_constraint "char_length(info::text) <= 20000", name: CONSTRAINT_NAME
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
drop_table :packages_conan_package_references
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# See https://docs.gitlab.com/ee/development/migration_style_guide.html
|
||||
# for more information on how to write migrations for GitLab.
|
||||
|
||||
class AddPackageIdAsForeignKeyInPackagesConanPackageReferences < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.5'
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
add_concurrent_foreign_key :packages_conan_package_references, :packages_packages, column: :package_id,
|
||||
on_delete: :cascade
|
||||
end
|
||||
|
||||
def down
|
||||
with_lock_retries do
|
||||
remove_foreign_key :packages_conan_package_references, column: :package_id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddProjectIdAsForeignKeyInPackagesConanPackageReferences < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.5'
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
add_concurrent_foreign_key :packages_conan_package_references, :projects, column: :project_id, on_delete: :cascade
|
||||
end
|
||||
|
||||
def down
|
||||
with_lock_retries do
|
||||
remove_foreign_key :packages_conan_package_references, column: :project_id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddRecipeRevisionIdAsForeignKeyInPackagesConanPackageReferences < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.5'
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
add_concurrent_foreign_key :packages_conan_package_references, :packages_conan_recipe_revisions,
|
||||
column: :recipe_revision_id, on_delete: :cascade
|
||||
end
|
||||
|
||||
def down
|
||||
with_lock_retries do
|
||||
remove_foreign_key :packages_conan_package_references, column: :recipe_revision_id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RemoveCiPlatformMetricsUpdateCronWorkerJobInstances < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.5'
|
||||
disable_ddl_transaction!
|
||||
|
||||
DEPRECATED_JOB_CLASSES = %w[
|
||||
CiPlatformMetricsUpdateCronWorker
|
||||
]
|
||||
|
||||
def up
|
||||
sidekiq_remove_jobs(job_klasses: DEPRECATED_JOB_CLASSES)
|
||||
end
|
||||
|
||||
def down
|
||||
# This migration removes any instances of deprecated workers and cannot be undone.
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RemoveConanInfoColumnsInPackagesConanMetadata < Gitlab::Database::Migration[2.2]
|
||||
disable_ddl_transaction!
|
||||
milestone '17.5'
|
||||
|
||||
def up
|
||||
with_lock_retries do
|
||||
remove_column :packages_conan_metadata, :os, if_exists: true
|
||||
remove_column :packages_conan_metadata, :architecture, if_exists: true
|
||||
remove_column :packages_conan_metadata, :build_type, if_exists: true
|
||||
remove_column :packages_conan_metadata, :compiler, if_exists: true
|
||||
remove_column :packages_conan_metadata, :compiler_version, if_exists: true
|
||||
remove_column :packages_conan_metadata, :compiler_libcxx, if_exists: true
|
||||
remove_column :packages_conan_metadata, :compiler_cppstd, if_exists: true
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
with_lock_retries do
|
||||
add_column :packages_conan_metadata, :os, :text, if_not_exists: true
|
||||
add_column :packages_conan_metadata, :architecture, :text, if_not_exists: true
|
||||
add_column :packages_conan_metadata, :build_type, :text, if_not_exists: true
|
||||
add_column :packages_conan_metadata, :compiler, :text, if_not_exists: true
|
||||
add_column :packages_conan_metadata, :compiler_version, :text, if_not_exists: true
|
||||
add_column :packages_conan_metadata, :compiler_libcxx, :text, if_not_exists: true
|
||||
add_column :packages_conan_metadata, :compiler_cppstd, :text, if_not_exists: true
|
||||
end
|
||||
|
||||
add_text_limit :packages_conan_metadata, :os, 32
|
||||
add_text_limit :packages_conan_metadata, :architecture, 32
|
||||
add_text_limit :packages_conan_metadata, :build_type, 32
|
||||
add_text_limit :packages_conan_metadata, :compiler, 32
|
||||
add_text_limit :packages_conan_metadata, :compiler_version, 16
|
||||
add_text_limit :packages_conan_metadata, :compiler_libcxx, 32
|
||||
add_text_limit :packages_conan_metadata, :compiler_cppstd, 32
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
9e550c57e33c3e3118416b9ab3728e8b53d54168012cd5aef40c748285b561bc
|
||||
|
|
@ -0,0 +1 @@
|
|||
3e8ba4ec7cc1d5a3ac4927cc47ae458ad044553697d1e9d01e1029080c3d5505
|
||||
|
|
@ -0,0 +1 @@
|
|||
ea088526f189d7db821bd04f90c7699faa9673de6d3c3345b57c8e6dbceaee66
|
||||
|
|
@ -0,0 +1 @@
|
|||
df306ba517e589c537b76c855f4829adc28e4f4b164137ade5a9a1c7a18f40bc
|
||||
|
|
@ -0,0 +1 @@
|
|||
055a99f9018a2a0f3f39670561ad84a5d792bdc3b9ca763bd9850253926db48f
|
||||
|
|
@ -0,0 +1 @@
|
|||
d8344419bd22f1b91b93afc7ea84e11a898f8c653296fae319bc8b9a3d9ac9eb
|
||||
|
|
@ -0,0 +1 @@
|
|||
e1c3d82e4e7e607484f94d7a6f03508930478cdc21edbf05a9d674b7be337831
|
||||
|
|
@ -14790,21 +14790,7 @@ CREATE TABLE packages_conan_metadata (
|
|||
updated_at timestamp with time zone NOT NULL,
|
||||
package_username character varying(255) NOT NULL,
|
||||
package_channel character varying(255) NOT NULL,
|
||||
project_id bigint,
|
||||
os text,
|
||||
architecture text,
|
||||
build_type text,
|
||||
compiler text,
|
||||
compiler_version text,
|
||||
compiler_libcxx text,
|
||||
compiler_cppstd text,
|
||||
CONSTRAINT check_15f3356ff2 CHECK ((char_length(architecture) <= 32)),
|
||||
CONSTRAINT check_3dc474bc51 CHECK ((char_length(compiler_version) <= 16)),
|
||||
CONSTRAINT check_52abd85dde CHECK ((char_length(compiler_libcxx) <= 32)),
|
||||
CONSTRAINT check_535bd0bf5b CHECK ((char_length(os) <= 32)),
|
||||
CONSTRAINT check_a0b998cb1b CHECK ((char_length(build_type) <= 32)),
|
||||
CONSTRAINT check_e57d0def27 CHECK ((char_length(compiler_cppstd) <= 32)),
|
||||
CONSTRAINT check_e7f03884b8 CHECK ((char_length(compiler) <= 32))
|
||||
project_id bigint
|
||||
);
|
||||
|
||||
CREATE SEQUENCE packages_conan_metadata_id_seq
|
||||
|
|
@ -14816,6 +14802,27 @@ CREATE SEQUENCE packages_conan_metadata_id_seq
|
|||
|
||||
ALTER SEQUENCE packages_conan_metadata_id_seq OWNED BY packages_conan_metadata.id;
|
||||
|
||||
CREATE TABLE packages_conan_package_references (
|
||||
id bigint NOT NULL,
|
||||
package_id bigint NOT NULL,
|
||||
project_id bigint NOT NULL,
|
||||
recipe_revision_id bigint,
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
updated_at timestamp with time zone NOT NULL,
|
||||
reference bytea NOT NULL,
|
||||
info jsonb DEFAULT '{}'::jsonb NOT NULL,
|
||||
CONSTRAINT chk_conan_references_info_length CHECK ((char_length((info)::text) <= 20000))
|
||||
);
|
||||
|
||||
CREATE SEQUENCE packages_conan_package_references_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
ALTER SEQUENCE packages_conan_package_references_id_seq OWNED BY packages_conan_package_references.id;
|
||||
|
||||
CREATE TABLE packages_conan_recipe_revisions (
|
||||
id bigint NOT NULL,
|
||||
package_id bigint NOT NULL,
|
||||
|
|
@ -19775,6 +19782,7 @@ CREATE TABLE virtual_registries_packages_maven_cached_responses (
|
|||
object_storage_key text NOT NULL,
|
||||
upstream_etag text,
|
||||
content_type text DEFAULT 'application/octet-stream'::text NOT NULL,
|
||||
status smallint DEFAULT 0 NOT NULL,
|
||||
CONSTRAINT check_28c64d513d CHECK ((char_length(object_storage_key) <= 255)),
|
||||
CONSTRAINT check_30b7e853d9 CHECK ((char_length(upstream_etag) <= 255)),
|
||||
CONSTRAINT check_68b105cda6 CHECK ((char_length(file) <= 255)),
|
||||
|
|
@ -22271,6 +22279,8 @@ ALTER TABLE ONLY packages_conan_file_metadata ALTER COLUMN id SET DEFAULT nextva
|
|||
|
||||
ALTER TABLE ONLY packages_conan_metadata ALTER COLUMN id SET DEFAULT nextval('packages_conan_metadata_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY packages_conan_package_references ALTER COLUMN id SET DEFAULT nextval('packages_conan_package_references_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY packages_conan_recipe_revisions ALTER COLUMN id SET DEFAULT nextval('packages_conan_recipe_revisions_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY packages_debian_group_architectures ALTER COLUMN id SET DEFAULT nextval('packages_debian_group_architectures_id_seq'::regclass);
|
||||
|
|
@ -24736,6 +24746,9 @@ ALTER TABLE ONLY packages_conan_file_metadata
|
|||
ALTER TABLE ONLY packages_conan_metadata
|
||||
ADD CONSTRAINT packages_conan_metadata_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY packages_conan_package_references
|
||||
ADD CONSTRAINT packages_conan_package_references_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY packages_conan_recipe_revisions
|
||||
ADD CONSTRAINT packages_conan_recipe_revisions_pkey PRIMARY KEY (id);
|
||||
|
||||
|
|
@ -29702,6 +29715,10 @@ CREATE UNIQUE INDEX index_packages_conan_metadata_on_package_id_username_channel
|
|||
|
||||
CREATE INDEX index_packages_conan_metadata_on_project_id ON packages_conan_metadata USING btree (project_id);
|
||||
|
||||
CREATE INDEX index_packages_conan_package_references_on_project_id ON packages_conan_package_references USING btree (project_id);
|
||||
|
||||
CREATE INDEX index_packages_conan_package_references_on_recipe_revision_id ON packages_conan_package_references USING btree (recipe_revision_id);
|
||||
|
||||
CREATE INDEX index_packages_conan_recipe_revisions_on_project_id ON packages_conan_recipe_revisions USING btree (project_id);
|
||||
|
||||
CREATE INDEX index_packages_debian_group_architectures_on_group_id ON packages_debian_group_architectures USING btree (group_id);
|
||||
|
|
@ -31400,6 +31417,8 @@ CREATE UNIQUE INDEX uniq_audit_instance_event_filters_destination_id_and_event_t
|
|||
|
||||
CREATE UNIQUE INDEX uniq_google_cloud_logging_configuration_namespace_id_and_name ON audit_events_google_cloud_logging_configurations USING btree (namespace_id, name);
|
||||
|
||||
CREATE UNIQUE INDEX uniq_idx_on_packages_conan_package_references_package_reference ON packages_conan_package_references USING btree (package_id, recipe_revision_id, reference);
|
||||
|
||||
CREATE UNIQUE INDEX uniq_idx_packages_packages_on_project_id_name_version_ml_model ON packages_packages USING btree (project_id, name, version) WHERE ((package_type = 14) AND (status <> 4));
|
||||
|
||||
CREATE UNIQUE INDEX uniq_idx_project_compliance_framework_on_project_framework ON project_compliance_framework_settings USING btree (project_id, framework_id);
|
||||
|
|
@ -34058,6 +34077,9 @@ ALTER TABLE ONLY import_source_users
|
|||
ALTER TABLE ONLY integrations
|
||||
ADD CONSTRAINT fk_71cce407f9 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY packages_conan_package_references
|
||||
ADD CONSTRAINT fk_7210467bfc FOREIGN KEY (package_id) REFERENCES packages_packages(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY subscription_user_add_on_assignments
|
||||
ADD CONSTRAINT fk_724c2df9a8 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
|
||||
|
||||
|
|
@ -34472,6 +34494,9 @@ ALTER TABLE ONLY compliance_management_frameworks
|
|||
ALTER TABLE ONLY ml_experiment_metadata
|
||||
ADD CONSTRAINT fk_b764e76c6c FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY packages_conan_package_references
|
||||
ADD CONSTRAINT fk_b7c05e1b1c FOREIGN KEY (recipe_revision_id) REFERENCES packages_conan_recipe_revisions(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY external_status_checks_protected_branches
|
||||
ADD CONSTRAINT fk_b7d788e813 FOREIGN KEY (protected_branch_id) REFERENCES protected_branches(id) ON DELETE CASCADE;
|
||||
|
||||
|
|
@ -34784,6 +34809,9 @@ ALTER TABLE ONLY namespaces
|
|||
ALTER TABLE ONLY fork_networks
|
||||
ADD CONSTRAINT fk_e7b436b2b5 FOREIGN KEY (root_project_id) REFERENCES projects(id) ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE ONLY packages_conan_package_references
|
||||
ADD CONSTRAINT fk_e7b5f3afc7 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY error_tracking_error_events
|
||||
ADD CONSTRAINT fk_e84882273e FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
|
||||
|
||||
|
|
|
|||
|
|
@ -103,11 +103,18 @@ Backup and restore recreates the entire database, including the indexes.
|
|||
1. In all PostgreSQL nodes, install the new GitLab package of the same GitLab version.
|
||||
1. In a [database console](../troubleshooting/postgresql.md#start-a-database-console), rebuild all indexes:
|
||||
|
||||
```shell
|
||||
```sql
|
||||
SET statement_timeout = 0;
|
||||
REINDEX DATABASE gitlabhq_production;
|
||||
```
|
||||
|
||||
1. After reindexing the database, the version must be refreshed for all affected collations.
|
||||
To update the system catalog to record the current collation version:
|
||||
|
||||
```sql
|
||||
ALTER COLLATION <collation_name> REFRESH VERSION;
|
||||
```
|
||||
|
||||
1. In all nodes, start GitLab.
|
||||
|
||||
**Advantages**:
|
||||
|
|
@ -136,11 +143,18 @@ Backup and restore recreates the entire database, including the indexes.
|
|||
1. In the primary site, in a
|
||||
[database console](../troubleshooting/postgresql.md#start-a-database-console), rebuild all indexes:
|
||||
|
||||
```shell
|
||||
```sql
|
||||
SET statement_timeout = 0;
|
||||
REINDEX DATABASE gitlabhq_production;
|
||||
```
|
||||
|
||||
1. After reindexing the database, the version must be refreshed for all affected collations.
|
||||
To update the system catalog to record the current collation version:
|
||||
|
||||
```sql
|
||||
ALTER COLLATION <collation_name> REFRESH VERSION;
|
||||
```
|
||||
|
||||
1. If the secondary sites receive traffic from users, then let the read-replica databases catch up
|
||||
before starting GitLab.
|
||||
1. In all nodes of all sites, start GitLab.
|
||||
|
|
@ -165,7 +179,7 @@ different types of indexes were handled, see the blog post about
|
|||
1. [Determine which indexes are affected](https://wiki.postgresql.org/wiki/Locale_data_changes#What_indexes_are_affected).
|
||||
1. In a [database console](../troubleshooting/postgresql.md#start-a-database-console), reindex each affected index:
|
||||
|
||||
```shell
|
||||
```sql
|
||||
SET statement_timeout = 0;
|
||||
REINDEX INDEX <index name> CONCURRENTLY;
|
||||
```
|
||||
|
|
@ -173,8 +187,8 @@ different types of indexes were handled, see the blog post about
|
|||
1. After reindexing bad indexes, the collation must be refreshed. To update the system catalog to
|
||||
record the current collation version:
|
||||
|
||||
```shell
|
||||
ALTER COLLATION <collation_name> REFRESH VERSION
|
||||
```sql
|
||||
ALTER COLLATION <collation_name> REFRESH VERSION;
|
||||
```
|
||||
|
||||
1. In all nodes, start GitLab.
|
||||
|
|
@ -206,7 +220,7 @@ different types of indexes were handled, see the blog post about
|
|||
1. In the primary site, in a
|
||||
[database console](../troubleshooting/postgresql.md#start-a-database-console), reindex each affected index:
|
||||
|
||||
```shell
|
||||
```sql
|
||||
SET statement_timeout = 0;
|
||||
REINDEX INDEX <index name> CONCURRENTLY;
|
||||
```
|
||||
|
|
@ -214,8 +228,8 @@ different types of indexes were handled, see the blog post about
|
|||
1. After reindexing bad indexes, the collation must be refreshed. To update the system catalog to
|
||||
record the current collation version:
|
||||
|
||||
```shell
|
||||
ALTER COLLATION <collation_name> REFRESH VERSION
|
||||
```sql
|
||||
ALTER COLLATION <collation_name> REFRESH VERSION;
|
||||
```
|
||||
|
||||
1. The existing PostgreSQL streaming replication should replicate the reindex changes to the
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
DETAILS:
|
||||
**Tier:** For a limited time, Premium and Ultimate. In the future, [GitLab Duo Enterprise](../../subscriptions/subscription-add-ons.md).
|
||||
**Offering:** Self-managed
|
||||
**Status:** Experiment
|
||||
**Status:** Beta
|
||||
|
||||
> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/12972) in GitLab 17.1 [with a flag](../../administration/feature_flags.md) named `ai_custom_model`. Disabled by default.
|
||||
|
||||
|
|
@ -18,16 +18,6 @@ FLAG:
|
|||
The availability of this feature is controlled by a feature flag.
|
||||
For more information, see the history.
|
||||
|
||||
WARNING:
|
||||
This feature is considered [experimental](../../policy/experiment-beta-support.md) and is not intended for customer usage outside of initial design partners. We expect major changes to this feature.
|
||||
|
||||
DISCLAIMER:
|
||||
This page contains information related to upcoming products, features, and functionality.
|
||||
It is important to note that the information presented is for informational purposes only.
|
||||
Please do not rely on this information for purchasing or planning purposes.
|
||||
The development, release, and timing of any products, features, or functionality may be subject to change or delay and remain at the
|
||||
sole discretion of GitLab Inc.
|
||||
|
||||
To configure your GitLab instance to access the available self-hosted models in your infrastructure:
|
||||
|
||||
1. Configure the self-hosted model.
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
DETAILS:
|
||||
**Tier:** For a limited time, Premium and Ultimate. In the future, [GitLab Duo Enterprise](../../subscriptions/subscription-add-ons.md).
|
||||
**Offering:** Self-managed
|
||||
**Status:** Experiment
|
||||
**Status:** Beta
|
||||
|
||||
> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/12972) in GitLab 17.1 [with a flag](../../administration/feature_flags.md) named `ai_custom_model`. Disabled by default.
|
||||
|
||||
|
|
@ -18,15 +18,6 @@ FLAG:
|
|||
The availability of this feature is controlled by a feature flag.
|
||||
For more information, see the history.
|
||||
|
||||
WARNING:
|
||||
This feature is considered [experimental](../../policy/experiment-beta-support.md) and is not intended for customer usage outside of initial design partners. We expect major changes to this feature.
|
||||
|
||||
DISCLAIMER:
|
||||
This page contains information related to upcoming products, features, and functionality.
|
||||
It is important to note that the information presented is for informational purposes only.
|
||||
Please do not rely on this information for purchasing or planning purposes.
|
||||
The development, release, and timing of any products, features, or functionality may be subject to change or delay and remain at the sole discretion of GitLab Inc.
|
||||
|
||||
When you deploy a self-hosted model, you can:
|
||||
|
||||
- Manage the end-to-end transmission of requests to enterprise-hosted large
|
||||
|
|
@ -59,7 +50,7 @@ feature. For more information about this offering, see
|
|||
|
||||
To deploy a self-hosted large language model:
|
||||
|
||||
1. [Set up your self-hosted model deployment infrastructure](../../administration/self_hosted_models/install_infrastructure.md) and connect it to your GitLab instance.
|
||||
1. [Set up your self-hosted model infrastructure](../../administration/self_hosted_models/install_infrastructure.md) and connect it to your GitLab instance.
|
||||
1. [Configure your GitLab instance to access self-hosted models](../../administration/self_hosted_models/configure_duo_features.md) using instance and group settings.
|
||||
|
||||
## Self-hosted models compared to the default GitLab AI vendor architecture
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
---
|
||||
stage: AI-Powered
|
||||
group: Custom Models
|
||||
description: Setup your self-hosted model deployment infrastructure
|
||||
description: Set up your self-hosted model infrastructure
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# Set up your self-hosted model deployment infrastructure
|
||||
# Set up your self-hosted model infrastructure
|
||||
|
||||
DETAILS:
|
||||
**Tier:** For a limited time, Premium and Ultimate. In the future, [GitLab Duo Enterprise](../../subscriptions/subscription-add-ons.md).
|
||||
**Offering:** Self-managed
|
||||
**Status:** Experiment
|
||||
**Status:** Beta
|
||||
|
||||
> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/12972) in GitLab 17.1 [with a flag](../../administration/feature_flags.md) named `ai_custom_model`. Disabled by default.
|
||||
|
||||
|
|
@ -18,19 +18,10 @@ FLAG:
|
|||
The availability of this feature is controlled by a feature flag.
|
||||
For more information, see the history.
|
||||
|
||||
WARNING:
|
||||
This feature is considered [experimental](../../policy/experiment-beta-support.md) and is not intended for customer usage outside of initial design partners. We expect major changes to this feature.
|
||||
|
||||
DISCLAIMER:
|
||||
This page contains information related to upcoming products, features, and functionality.
|
||||
It is important to note that the information presented is for informational purposes only.
|
||||
Please do not rely on this information for purchasing or planning purposes.
|
||||
The development, release, and timing of any products, features, or functionality may be subject to change or delay and remain at the sole discretion of GitLab Inc.
|
||||
|
||||
By self-hosting the model, AI Gateway, and GitLab instance, there are no calls to
|
||||
external architecture, ensuring maximum levels of security.
|
||||
|
||||
To set up your self-hosted model deployment infrastructure:
|
||||
To set up your self-hosted model infrastructure:
|
||||
|
||||
1. Install the large language model (LLM) serving infrastructure.
|
||||
1. Configure your GitLab instance.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
stage: AI-Powered
|
||||
group: Custom Models
|
||||
description: Troubleshooting tips for deploying self-hosted model deployment
|
||||
description: Troubleshooting tips for deploying self-hosted models
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1009,6 +1009,7 @@ the following are the names of GitLab Duo features:
|
|||
- GitLab Duo Merge Request Summary
|
||||
- GitLab Duo Product Analytics
|
||||
- GitLab Duo Root Cause Analysis
|
||||
- GitLab Duo Self-Hosted Models
|
||||
- GitLab Duo Test Generation
|
||||
- GitLab Duo Vulnerability Explanation
|
||||
- GitLab Duo Vulnerability Resolution
|
||||
|
|
@ -2002,6 +2003,16 @@ Use **self-hosted model** (lowercase) to refer to a language model that's hosted
|
|||
|
||||
The language model might be an LLM (large language model), but it might not be.
|
||||
|
||||
## Self-Hosted Models
|
||||
|
||||
Use title case for the **GitLab Duo Self-Hosted Models** feature.
|
||||
|
||||
On first mention on a page, use **GitLab Duo Self-Hosted Models**.
|
||||
Thereafter, use **Self-Hosted Models** by itself.
|
||||
|
||||
This phrase applies when specifically referring to the feature name only.
|
||||
If you're writing about [self-hosted models](#self-hosted-model), no need to use title case.
|
||||
|
||||
## self-managed
|
||||
|
||||
Use **self-managed** to refer to a customer's installation of GitLab. Do not use **self-hosted**.
|
||||
|
|
|
|||
|
|
@ -148,6 +148,16 @@ DETAILS:
|
|||
|
||||
## Beta features
|
||||
|
||||
### Self-Hosted Models
|
||||
|
||||
DETAILS:
|
||||
**Tier:** For a limited time, Premium and Ultimate. In the future, [GitLab Duo Enterprise](../../subscriptions/subscription-add-ons.md).
|
||||
**Offering:** Self-managed
|
||||
**Status:** Beta
|
||||
|
||||
- Host a GitLab-approved model that's different from the default.
|
||||
- [View documentation](../../administration/self_hosted_models/index.md).
|
||||
|
||||
### Merge Request Summary
|
||||
|
||||
DETAILS:
|
||||
|
|
|
|||
|
|
@ -142,17 +142,52 @@ To use GitLab Duo Chat in the GitLab Duo plugin for JetBrains IDEs:
|
|||
1. In the JetBrains marketplace, download and install the [GitLab Duo plugin](../../editor_extensions/jetbrains_ide/index.md#download-the-extension).
|
||||
1. Configure the [GitLab Duo plugin](../../editor_extensions/jetbrains_ide/index.md#configure-the-extension).
|
||||
1. In a JetBrains IDE, open a project.
|
||||
1. Open Chat by using one of the following methods:
|
||||
- On the right tool window bar, select **GitLab Duo Chat**.
|
||||
- Use a keyboard shortcut: <kbd>ALT</kbd> + <kbd>d</kbd> on Windows and Linux, or
|
||||
<kbd>Option</kbd> + <kbd>d</kbd> on macOS.
|
||||
- In the file that you have open in the editor:
|
||||
1. Optional. Select some code.
|
||||
1. Right-click and select **GitLab Duo Chat**.
|
||||
1. Select **Open Chat Window**.
|
||||
1. Select **Explain Code**, **Generate Tests**, or **Refactor Code**.
|
||||
- Add keyboard or mouse shortcuts for each action under **Keymap** in the **Settings**.
|
||||
1. In the message box, enter your question and press **Enter** or select **Send**.
|
||||
1. Open GitLab Duo Chat in either a chat window or an editor window:
|
||||
|
||||
### In a chat window
|
||||
|
||||
To open GitLab Duo Chat in a chat window, use any of these methods:
|
||||
|
||||
- On the right tool window bar, by selecting **GitLab Duo Chat**.
|
||||
- From a keyboard shortcut, by pressing:
|
||||
- MacOS: <kbd>Option</kbd> + <kbd>d</kbd>
|
||||
- Windows and Linux: <kbd>ALT</kbd> + <kbd>d</kbd>
|
||||
- In the file that you have open in the editor:
|
||||
1. Optional. Select some code.
|
||||
1. Right-click and select **GitLab Duo Chat**.
|
||||
1. Select **Open Chat Window**.
|
||||
1. Select **Explain Code**, **Generate Tests**, or **Refactor Code**.
|
||||
- Adding keyboard or mouse shortcuts for each action under **Keymap** in the **Settings**.
|
||||
|
||||
After GitLab Duo Chat opens:
|
||||
|
||||
1. In the message box, enter your question. The available commands are shown while you enter text:
|
||||
- Enter `/` to display all available commands.
|
||||
- Enter `/re` to display `/refactor` and `/reset`.
|
||||
1. To send your question, press **Enter** or select **Send**.
|
||||
1. Use the buttons within code blocks in the responses to interact with them.
|
||||
|
||||
### In the editor window
|
||||
|
||||
> - [Generally available](https://gitlab.com/groups/gitlab-org/editor-extensions/-/epics/80) in GitLab Duo 3.0.0.
|
||||
|
||||
To open GitLab Duo Chat in the editor window, use any of these methods:
|
||||
|
||||
- From a keyboard shortcut, by pressing:
|
||||
- MacOS: <kbd>Option</kbd> + <kbd>c</kbd>
|
||||
- Windows and Linux: <kbd>ALT</kbd> + <kbd>c</kbd>
|
||||
- In the currently open file in your IDE, by selecting some code,
|
||||
then, in the floating toolbar, selecting **GitLab Duo Quick Chat** (**{tanuki-ai}**).
|
||||
- Right-clicking, then selecting **GitLab Duo Chat > Open Quick Chat**.
|
||||
|
||||
After Quick Chat opens:
|
||||
|
||||
1. In the message box, enter your question. The available commands are shown while you enter text:
|
||||
- Enter `/` to display all available commands.
|
||||
- Enter `/re` to display `/refactor` and `/reset`.
|
||||
1. To send your question, press **Enter**.
|
||||
1. Use the buttons around code blocks in the responses to interact with them.
|
||||
1. To exit chat, either select **Escape to close**, or press **Escape** while focused on the chat.
|
||||
|
||||
## Watch a demo and get tips
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,13 @@ module Gitlab
|
|||
# @attr [String] stderr
|
||||
# @attr [Array<Process::Status>] status_list
|
||||
# @attr [Float] duration
|
||||
Result = Struct.new(:stderr, :status_list, :duration, keyword_init: true)
|
||||
Result = Struct.new(:stderr, :status_list, :duration, keyword_init: true) do
|
||||
def success?
|
||||
return false unless status_list&.any?
|
||||
|
||||
status_list.map(&:success?).all?
|
||||
end
|
||||
end
|
||||
|
||||
attr_reader :shell_commands
|
||||
|
||||
|
|
@ -24,7 +30,7 @@ module Gitlab
|
|||
# @param [IO|String|Array] input stdin redirection
|
||||
# @param [IO|String|Array] output stdout redirection
|
||||
# @return [Pipeline::Result]
|
||||
def run_pipeline!(input: nil, output: nil)
|
||||
def run!(input: nil, output: nil)
|
||||
start = Time.now
|
||||
# Open3 writes on `err_write` and we receive from `err_read`
|
||||
err_read, err_write = IO.pipe
|
||||
|
|
@ -43,7 +49,11 @@ module Gitlab
|
|||
stderr = err_read.read
|
||||
err_read.close # close after reading to avoid leaking file descriptors
|
||||
|
||||
Result.new(stderr: stderr, status_list: status_list, duration: duration)
|
||||
Result.new(
|
||||
stderr: stderr,
|
||||
status_list: status_list,
|
||||
duration: duration
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ RSpec.describe Gitlab::Backup::Cli::Shell::Pipeline do
|
|||
let(:command) { Gitlab::Backup::Cli::Shell::Command }
|
||||
let(:printf_command) { command.new('printf "3\n2\n1"') }
|
||||
let(:sort_command) { command.new('sort') }
|
||||
let(:true_command) { command.new('true') }
|
||||
let(:false_command) { command.new('false') }
|
||||
|
||||
subject(:pipeline) { described_class }
|
||||
|
||||
|
|
@ -19,11 +21,9 @@ RSpec.describe Gitlab::Backup::Cli::Shell::Pipeline do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#run_pipeline!' do
|
||||
it 'returns a Pipeline::Status' do
|
||||
true_command = command.new('true')
|
||||
|
||||
result = pipeline.new(true_command, true_command).run_pipeline!
|
||||
describe '#run!' do
|
||||
it 'returns a Pipeline::Result' do
|
||||
result = pipeline.new(true_command, true_command).run!
|
||||
|
||||
expect(result).to be_a(Gitlab::Backup::Cli::Shell::Pipeline::Result)
|
||||
end
|
||||
|
|
@ -33,24 +33,20 @@ RSpec.describe Gitlab::Backup::Cli::Shell::Pipeline do
|
|||
expected_output = 'my custom error content'
|
||||
err_command = command.new("echo #{expected_output} > /dev/stderr")
|
||||
|
||||
result = pipeline.new(err_command).run_pipeline!
|
||||
result = pipeline.new(err_command).run!
|
||||
|
||||
expect(result.stderr.chomp).to eq(expected_output)
|
||||
end
|
||||
|
||||
it 'includes a list of Process::Status from the executed pipeline' do
|
||||
true_command = command.new('true')
|
||||
|
||||
result = pipeline.new(true_command, true_command).run_pipeline!
|
||||
result = pipeline.new(true_command, true_command).run!
|
||||
|
||||
expect(result.status_list).to all be_a(Process::Status)
|
||||
expect(result.status_list).to all respond_to(:exited?, :termsig, :stopsig, :exitstatus, :success?, :pid)
|
||||
end
|
||||
|
||||
it 'includes a list of Process::Status that handles exit signals' do
|
||||
false_command = command.new('false')
|
||||
|
||||
result = pipeline.new(false_command, false_command).run_pipeline!
|
||||
result = pipeline.new(false_command, false_command).run!
|
||||
|
||||
expect(result.status_list).to all satisfy { |status| !status.success? }
|
||||
expect(result.status_list).to all satisfy { |status| status.exitstatus == 1 }
|
||||
|
|
@ -66,7 +62,7 @@ RSpec.describe Gitlab::Backup::Cli::Shell::Pipeline do
|
|||
|
||||
output_r, output_w = IO.pipe
|
||||
|
||||
result = pipeline.new(echo_command).run_pipeline!(input: input_r, output: output_w)
|
||||
result = pipeline.new(echo_command).run!(input: input_r, output: output_w)
|
||||
|
||||
input_r.close
|
||||
output_w.close
|
||||
|
|
@ -78,4 +74,44 @@ RSpec.describe Gitlab::Backup::Cli::Shell::Pipeline do
|
|||
expect(output).to match(/stdin is : my custom content/)
|
||||
end
|
||||
end
|
||||
|
||||
describe Gitlab::Backup::Cli::Shell::Pipeline::Result do
|
||||
describe '#success?' do
|
||||
context 'when one of multiple commands is unsuccessful' do
|
||||
it 'returns false' do
|
||||
expect(Gitlab::Backup::Cli::Shell::Pipeline.new(true_command, false_command).run!.success?).to be false
|
||||
end
|
||||
end
|
||||
|
||||
context 'when all commands are successful' do
|
||||
it 'returns true' do
|
||||
expect(Gitlab::Backup::Cli::Shell::Pipeline.new(true_command, true_command).run!.success?).to be true
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there is no result' do
|
||||
let(:result) { described_class.new(status_list: nil) }
|
||||
|
||||
it 'returns false' do
|
||||
expect(result.success?).to be false
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there is no status list' do
|
||||
let(:result) { described_class.new }
|
||||
|
||||
it 'returns false' do
|
||||
expect(result.success?).to be false
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there are no status results' do
|
||||
let(:result) { described_class.new(status_list: []) }
|
||||
|
||||
it 'returns false' do
|
||||
expect(result.success?).to be false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ module Backup
|
|||
target_directory: backup_files_realpath,
|
||||
target: '.',
|
||||
excludes: excludes)
|
||||
result = shell_pipeline.new(tar_command, compress_command).run_pipeline!(output: archive_file)
|
||||
result = shell_pipeline.new(tar_command, compress_command).run!(output: archive_file)
|
||||
|
||||
FileUtils.rm_rf(backup_files_realpath)
|
||||
else
|
||||
|
|
@ -69,7 +69,7 @@ module Backup
|
|||
target: '.',
|
||||
excludes: excludes)
|
||||
|
||||
result = shell_pipeline.new(tar_command, compress_command).run_pipeline!(output: archive_file)
|
||||
result = shell_pipeline.new(tar_command, compress_command).run!(output: archive_file)
|
||||
end
|
||||
|
||||
success = pipeline_succeeded?(
|
||||
|
|
@ -94,7 +94,7 @@ module Backup
|
|||
archive_file: USE_STDIN,
|
||||
target_directory: storage_realpath)
|
||||
|
||||
result = shell_pipeline.new(decompress_command, tar_command).run_pipeline!(input: archive_file)
|
||||
result = shell_pipeline.new(decompress_command, tar_command).run!(input: archive_file)
|
||||
|
||||
success = pipeline_succeeded?(
|
||||
compress_status: result.status_list[0],
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ namespace :tw do
|
|||
# CodeOwnerRule.new('Respond', ''),
|
||||
CodeOwnerRule.new('Runner', '@rsarangadharan'),
|
||||
CodeOwnerRule.new('Hosted Runners', '@rsarangadharan'),
|
||||
CodeOwnerRule.new('Security Policies', '@rdickenson'),
|
||||
CodeOwnerRule.new('Security Policies', '@rlehmann1'),
|
||||
CodeOwnerRule.new('Secret Detection', '@rdickenson'),
|
||||
CodeOwnerRule.new('Solutions Architecture', '@jfullam @brianwald @Darwinjs'),
|
||||
CodeOwnerRule.new('Source Code', '@brendan777'),
|
||||
|
|
@ -97,7 +97,7 @@ namespace :tw do
|
|||
'@gitlab-org/analytics-section/product-analytics/engineers/frontend ' \
|
||||
'@gitlab-org/analytics-section/analytics-instrumentation/engineers'),
|
||||
CodeOwnerRule.new('Authentication', '@gitlab-org/govern/authentication/approvers'),
|
||||
CodeOwnerRule.new('Authorization', '@gitlab-org/govern/authorization/approvers'),
|
||||
CodeOwnerRule.new('Authorization', '@rlehmann1'),
|
||||
CodeOwnerRule.new('Compliance',
|
||||
'@gitlab-org/govern/security-policies-frontend @gitlab-org/govern/threat-insights-frontend-team ' \
|
||||
'@gitlab-org/govern/threat-insights-backend-team'),
|
||||
|
|
|
|||
|
|
@ -25045,6 +25045,9 @@ msgstr ""
|
|||
msgid "GlobalSearch|Archived"
|
||||
msgstr ""
|
||||
|
||||
msgid "GlobalSearch|Branch not included"
|
||||
msgstr ""
|
||||
|
||||
msgid "GlobalSearch|Change context %{kbdStart}↵%{kbdEnd}"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -25168,6 +25171,9 @@ msgstr ""
|
|||
msgid "GlobalSearch|No labels found"
|
||||
msgstr ""
|
||||
|
||||
msgid "GlobalSearch|No matching results"
|
||||
msgstr ""
|
||||
|
||||
msgid "GlobalSearch|No results found"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -25219,6 +25225,9 @@ msgstr ""
|
|||
msgid "GlobalSearch|Results updated. %{count} results available. Use the up and down arrow keys to navigate search results list, or ENTER to submit."
|
||||
msgstr ""
|
||||
|
||||
msgid "GlobalSearch|Search"
|
||||
msgstr ""
|
||||
|
||||
msgid "GlobalSearch|Search %{kbdStart}↵%{kbdEnd}"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -25279,6 +25288,9 @@ msgstr ""
|
|||
msgid "GlobalSearch|Snippets"
|
||||
msgstr ""
|
||||
|
||||
msgid "GlobalSearch|Source branch"
|
||||
msgstr ""
|
||||
|
||||
msgid "GlobalSearch|The search term must be at least 3 characters long."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -25288,6 +25300,9 @@ msgstr ""
|
|||
msgid "GlobalSearch|Tip:"
|
||||
msgstr ""
|
||||
|
||||
msgid "GlobalSearch|Toggle if results have source branch included or excluded"
|
||||
msgstr ""
|
||||
|
||||
msgid "GlobalSearch|Type %{kbdOpen}/%{kbdClose} to search"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -26529,9 +26544,6 @@ msgstr ""
|
|||
msgid "GroupSettings|Organizations and contacts can be created and associated with issues."
|
||||
msgstr ""
|
||||
|
||||
msgid "GroupSettings|Participate in the %{link_start}GitLab Early Access Program%{link_end}."
|
||||
msgstr ""
|
||||
|
||||
msgid "GroupSettings|Please choose a group URL with no special characters or spaces."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -48908,6 +48920,9 @@ msgstr ""
|
|||
msgid "SecurityExclusions|Add exclusion"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityExclusions|Delete exclusion"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityExclusions|Description"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -48929,9 +48944,24 @@ msgstr ""
|
|||
msgid "SecurityExclusions|Enter one or more rules to ignore, separated by line breaks."
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityExclusions|Exclusion deleted successfully."
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityExclusions|Exclusion disabled successfully."
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityExclusions|Exclusion enabled successfully."
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityExclusions|Exclusion has been created successfully"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityExclusions|Failed to delete the exclusion:"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityExclusions|Failed to update the exclusion:"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityExclusions|File or directory location"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -48985,6 +49015,9 @@ msgstr ""
|
|||
msgid "SecurityExclusions|Value"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityExclusions|You are about to delete the %{type} `%{value}` from the secret detection exclusions. Are you sure you want to continue?"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityExclusions|ex: This secret is used for testing"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -64513,6 +64546,9 @@ msgstr ""
|
|||
msgid "compliance violation has already been recorded"
|
||||
msgstr ""
|
||||
|
||||
msgid "conaninfo is too large. Maximum size is %{max_size} characters"
|
||||
msgstr ""
|
||||
|
||||
msgid "contacts can only be added to root groups"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@
|
|||
"@gitlab/fonts": "^1.3.0",
|
||||
"@gitlab/query-language": "^0.0.5-a-20240903",
|
||||
"@gitlab/svgs": "3.117.0",
|
||||
"@gitlab/ui": "94.0.0",
|
||||
"@gitlab/ui": "94.0.1",
|
||||
"@gitlab/web-ide": "^0.0.1-dev-20240909013227",
|
||||
"@mattiasbuelens/web-streams-adapter": "^0.1.0",
|
||||
"@rails/actioncable": "7.0.8-4",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
FactoryBot.define do
|
||||
factory :conan_package_reference, class: 'Packages::Conan::PackageReference' do
|
||||
package { association(:conan_package) }
|
||||
project { association(:project) }
|
||||
recipe_revision { association(:conan_recipe_revision) }
|
||||
info do
|
||||
{
|
||||
settings: { os: 'Linux', arch: 'x86_64' },
|
||||
requires: ['libA/1.0@user/testing'],
|
||||
options: { fPIC: true },
|
||||
otherProperties: 'some_value'
|
||||
}
|
||||
end
|
||||
sequence(:reference) { |n| Digest::SHA1.digest(n.to_s) } # rubocop:disable Fips/SHA1 -- The conan registry is not FIPS compliant
|
||||
end
|
||||
end
|
||||
|
|
@ -5,7 +5,7 @@ FactoryBot.define do
|
|||
class: 'VirtualRegistries::Packages::Maven::CachedResponse' do
|
||||
upstream { association :virtual_registries_packages_maven_upstream }
|
||||
group { upstream.group }
|
||||
relative_path { |n| "/a/relative/path/test-#{n}.txt" }
|
||||
sequence(:relative_path) { |n| "/a/relative/path/test-#{n}.txt" }
|
||||
size { 1.kilobyte }
|
||||
upstream_etag { OpenSSL::Digest.hexdigest('SHA256', 'test') }
|
||||
content_type { 'text/plain' }
|
||||
|
|
@ -24,5 +24,11 @@ FactoryBot.define do
|
|||
upstream_checked_at { 30.minutes.ago }
|
||||
upstream_etag { 'test' }
|
||||
end
|
||||
|
||||
trait :orphan do
|
||||
after(:create) do |entry|
|
||||
entry.update_attribute(:upstream_id, nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1680,3 +1680,18 @@ export const mockDataForBlobBody = {
|
|||
projectPath: 'Testjs/Test',
|
||||
__typename: 'SearchBlobFileType',
|
||||
};
|
||||
|
||||
export const mockSourceBranches = [
|
||||
{
|
||||
text: 'master',
|
||||
value: 'master',
|
||||
},
|
||||
{
|
||||
text: 'feature',
|
||||
value: 'feature',
|
||||
},
|
||||
{
|
||||
text: 'develop',
|
||||
value: 'develop',
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -0,0 +1,113 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import { GlCollapsibleListbox, GlListboxItem, GlIcon } from '@gitlab/ui';
|
||||
import BranchDropdown from '~/search/sidebar/components/shared/branch_dropdown.vue';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
|
||||
|
||||
import { mockSourceBranches } from 'jest/search/mock_data';
|
||||
|
||||
describe('BranchDropdown', () => {
|
||||
let wrapper;
|
||||
|
||||
const defaultProps = {
|
||||
sourceBranches: mockSourceBranches,
|
||||
errors: [],
|
||||
headerText: 'Source branch',
|
||||
searchBranchText: 'Search source branch',
|
||||
selectedBranch: 'master',
|
||||
icon: 'branch',
|
||||
isLoading: false,
|
||||
};
|
||||
|
||||
const createComponent = (props = {}, options = {}) => {
|
||||
wrapper = shallowMount(BranchDropdown, {
|
||||
propsData: {
|
||||
...defaultProps,
|
||||
...props,
|
||||
},
|
||||
stubs: {
|
||||
GlCollapsibleListbox,
|
||||
GlIcon,
|
||||
},
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
const findGlCollapsibleListbox = () => wrapper.findComponent(GlCollapsibleListbox);
|
||||
const findGlListboxItems = () => wrapper.findAllComponents(GlListboxItem);
|
||||
const findErrorMessages = () => wrapper.findAll('[data-testid="branch-dropdown-error-list"]');
|
||||
|
||||
describe('when nothing is selected', () => {
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
it('renders the GlCollapsibleListbox component with correct props', () => {
|
||||
const toggleClass = [
|
||||
{
|
||||
'!gl-shadow-inner-1-red-500': undefined,
|
||||
'gl-font-monospace': true,
|
||||
},
|
||||
'gl-mb-0',
|
||||
];
|
||||
|
||||
// This is a workaround for "Property or method `nodeType` is not defined"
|
||||
// https://docs.gitlab.com/ee/development/fe_guide/troubleshooting.html#property-or-method-nodetype-is-not-defined-but-youre-not-using-nodetype-anywhere
|
||||
// usual workourounds didn't work so I had to do following:
|
||||
|
||||
const props = findGlCollapsibleListbox().props();
|
||||
|
||||
expect(props.selected).toBe('master');
|
||||
expect(props.headerText).toBe('Source branch');
|
||||
expect(props.items).toMatchObject(mockSourceBranches);
|
||||
expect(props.noResultsText).toBe('No results found');
|
||||
expect(props.searching).toBe(false);
|
||||
expect(props.searchPlaceholder).toBe('Search source branch');
|
||||
expect(props.toggleClass).toMatchObject(toggleClass);
|
||||
expect(props.toggleText).toBe('Search source branch');
|
||||
expect(props.icon).toBe('branch');
|
||||
expect(props.loading).toBe(false);
|
||||
expect(props.resetButtonLabel).toBe('Reset');
|
||||
});
|
||||
|
||||
it('renders error messages when errors prop is passed', async () => {
|
||||
const errors = ['Error 1', 'Error 2'];
|
||||
createComponent({ errors });
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
const errorMessages = findErrorMessages();
|
||||
|
||||
expect(errorMessages.length).toBe(errors.length);
|
||||
errorMessages.wrappers.forEach((errorWrapper, index) => {
|
||||
expect(errorWrapper.text()).toContain(errors[index]);
|
||||
});
|
||||
});
|
||||
|
||||
it('search filters items', async () => {
|
||||
findGlCollapsibleListbox().vm.$emit('search', 'fea');
|
||||
jest.advanceTimersByTime(DEFAULT_DEBOUNCE_AND_THROTTLE_MS);
|
||||
await waitForPromises();
|
||||
|
||||
expect(findGlListboxItems()).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('emits hide', () => {
|
||||
findGlCollapsibleListbox().vm.$emit('hidden');
|
||||
|
||||
expect(wrapper.emitted('hide')).toStrictEqual([[]]);
|
||||
});
|
||||
|
||||
it('emits selected', () => {
|
||||
findGlCollapsibleListbox().vm.$emit('select', 'main');
|
||||
|
||||
expect(wrapper.emitted('selected')).toStrictEqual([['main']]);
|
||||
});
|
||||
|
||||
it('emits reset', () => {
|
||||
findGlCollapsibleListbox().vm.$emit('reset');
|
||||
|
||||
expect(wrapper.emitted('reset')).toStrictEqual([[]]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -6,6 +6,7 @@ import { MOCK_QUERY } from 'jest/search/mock_data';
|
|||
import MergeRequestsFilters from '~/search/sidebar/components/merge_requests_filters.vue';
|
||||
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 { SEARCH_TYPE_ADVANCED, SEARCH_TYPE_BASIC } from '~/search/sidebar/constants';
|
||||
|
||||
Vue.use(Vuex);
|
||||
|
|
@ -23,6 +24,9 @@ describe('GlobalSearch MergeRequestsFilters', () => {
|
|||
state: {
|
||||
urlQuery: MOCK_QUERY,
|
||||
searchType: SEARCH_TYPE_ADVANCED,
|
||||
groupInitialJson: {
|
||||
id: 1,
|
||||
},
|
||||
...initialState,
|
||||
},
|
||||
getters: defaultGetters,
|
||||
|
|
@ -35,6 +39,7 @@ describe('GlobalSearch MergeRequestsFilters', () => {
|
|||
|
||||
const findStatusFilter = () => wrapper.findComponent(StatusFilter);
|
||||
const findArchivedFilter = () => wrapper.findComponent(ArchivedFilter);
|
||||
const findSourceBranchFilter = () => wrapper.findComponent(SourceBranchFilter);
|
||||
|
||||
describe('Renders correctly with Archived Filter', () => {
|
||||
beforeEach(() => {
|
||||
|
|
@ -48,6 +53,10 @@ describe('GlobalSearch MergeRequestsFilters', () => {
|
|||
it('renders ArchivedFilter', () => {
|
||||
expect(findArchivedFilter().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('renders sourceBranchFilter', () => {
|
||||
expect(findSourceBranchFilter().exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Renders correctly with basic search', () => {
|
||||
|
|
@ -62,6 +71,10 @@ describe('GlobalSearch MergeRequestsFilters', () => {
|
|||
it('renders ArchivedFilter', () => {
|
||||
expect(findArchivedFilter().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('renders sourceBranchFilter', () => {
|
||||
expect(findSourceBranchFilter().exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasProjectContext getter', () => {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,161 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import Vue from 'vue';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import Vuex from 'vuex';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import { GlFormCheckbox } from '@gitlab/ui';
|
||||
import AjaxCache from '~/lib/utils/ajax_cache';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import SourceBranchFilter from '~/search/sidebar/components/source_branch_filter/index.vue';
|
||||
import BranchDropdown from '~/search/sidebar/components/shared/branch_dropdown.vue';
|
||||
import { useMockInternalEventsTracking } from 'helpers/tracking_internal_events_helper';
|
||||
import { MOCK_QUERY } from 'jest/search/mock_data';
|
||||
|
||||
Vue.use(Vuex);
|
||||
|
||||
describe('Source branch filter', () => {
|
||||
let wrapper;
|
||||
let mock;
|
||||
const actions = {
|
||||
setQuery: jest.fn(),
|
||||
applyQuery: jest.fn(),
|
||||
};
|
||||
|
||||
const defaultState = {
|
||||
query: {
|
||||
scope: 'merge_requests',
|
||||
group_id: 1,
|
||||
search: '*',
|
||||
},
|
||||
};
|
||||
|
||||
const createComponent = (state) => {
|
||||
const store = new Vuex.Store({
|
||||
...defaultState,
|
||||
state,
|
||||
actions,
|
||||
});
|
||||
|
||||
wrapper = shallowMount(SourceBranchFilter, {
|
||||
store,
|
||||
});
|
||||
};
|
||||
|
||||
const findBranchDropdown = () => wrapper.findComponent(BranchDropdown);
|
||||
const findGlFormCheckbox = () => wrapper.findComponent(GlFormCheckbox);
|
||||
|
||||
describe('when nothing is selected', () => {
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
it('renders the component', () => {
|
||||
expect(findBranchDropdown().exists()).toBe(true);
|
||||
expect(findGlFormCheckbox().exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when everything is selected', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
query: {
|
||||
...MOCK_QUERY,
|
||||
'not[source_branch]': 'feature',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('renders the component with selected options', () => {
|
||||
expect(findBranchDropdown().props('selectedBranch')).toBe('feature');
|
||||
expect(findGlFormCheckbox().attributes('checked')).toBe('true');
|
||||
});
|
||||
|
||||
it('displays the correct placeholder text and icon', () => {
|
||||
expect(findBranchDropdown().props('searchBranchText')).toBe('feature');
|
||||
expect(findBranchDropdown().props('icon')).toBe('branch');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when opening dropdown', () => {
|
||||
beforeEach(() => {
|
||||
mock = new MockAdapter(axios);
|
||||
jest.spyOn(axios, 'get');
|
||||
jest.spyOn(AjaxCache, 'retrieve');
|
||||
|
||||
createComponent({
|
||||
groupInitialJson: {
|
||||
id: 1,
|
||||
full_name: 'gitlab-org/gitlab-test',
|
||||
full_path: 'gitlab-org/gitlab-test',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mock.restore();
|
||||
});
|
||||
|
||||
it('calls AjaxCache with correct params', () => {
|
||||
findBranchDropdown().vm.$emit('shown');
|
||||
expect(AjaxCache.retrieve).toHaveBeenCalledWith(
|
||||
'/-/autocomplete/merge_request_source_branches.json?group_id=1',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe.each(['source_branch', 'not[source_branch]'])(
|
||||
'when selecting a branch with and withouth toggle',
|
||||
(paramName) => {
|
||||
const { bindInternalEventDocument } = useMockInternalEventsTracking();
|
||||
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
query: {
|
||||
...MOCK_QUERY,
|
||||
[paramName]: 'feature',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it(`calls setQuery with correct param ${paramName}`, () => {
|
||||
const { trackEventSpy } = bindInternalEventDocument(wrapper.element);
|
||||
findBranchDropdown().vm.$emit('selected', 'feature');
|
||||
|
||||
expect(actions.setQuery).toHaveBeenCalledWith(expect.anything(), {
|
||||
key: paramName,
|
||||
value: 'feature',
|
||||
});
|
||||
|
||||
expect(trackEventSpy).toHaveBeenCalledWith(
|
||||
'select_source_branch_filter_on_merge_request_page',
|
||||
{
|
||||
label: paramName === 'not[source_branch]' ? 'exclude' : 'include',
|
||||
},
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
describe('when reseting selected branch', () => {
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
it(`calls setQuery with correct param`, () => {
|
||||
findBranchDropdown().vm.$emit('reset');
|
||||
|
||||
expect(actions.setQuery).toHaveBeenCalledWith(expect.anything(), {
|
||||
key: 'source_branch',
|
||||
value: '',
|
||||
});
|
||||
|
||||
expect(actions.setQuery).toHaveBeenCalledWith(expect.anything(), {
|
||||
key: 'not[source_branch]',
|
||||
value: '',
|
||||
});
|
||||
|
||||
expect(actions.applyQuery).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,14 +1,20 @@
|
|||
import { mount } from '@vue/test-utils';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import Vue from 'vue';
|
||||
import locale from '~/locale';
|
||||
import Translate from '~/vue_shared/translate';
|
||||
import Component from './translate_spec.vue';
|
||||
|
||||
Vue.use(Translate);
|
||||
|
||||
describe('Vue translate filter', () => {
|
||||
const createTranslationMock = (key, ...translations) => {
|
||||
locale.textdomain('app');
|
||||
let oldDomain;
|
||||
let oldData;
|
||||
|
||||
beforeAll(() => {
|
||||
oldDomain = locale.textdomain();
|
||||
oldData = locale.options.locale_data;
|
||||
|
||||
locale.textdomain('app');
|
||||
locale.options.locale_data = {
|
||||
app: {
|
||||
'': {
|
||||
|
|
@ -16,172 +22,32 @@ describe('Vue translate filter', () => {
|
|||
lang: 'vo',
|
||||
plural_forms: 'nplurals=2; plural=(n != 1);',
|
||||
},
|
||||
[key]: translations,
|
||||
singular: ['singular_translated'],
|
||||
plural: ['plural_singular translation', 'plural_multiple translation'],
|
||||
'%d day': ['%d singular translated', '%d plural translated'],
|
||||
'Context|Foobar': ['Context|Foobar translated'],
|
||||
'multiline string': ['multiline string translated'],
|
||||
'multiline plural': ['multiline string singular', 'multiline string plural'],
|
||||
'Context| multiline string': ['multiline string with context'],
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
it('translate singular text (`__`)', () => {
|
||||
const key = 'singular';
|
||||
const translation = 'singular_translated';
|
||||
createTranslationMock(key, translation);
|
||||
|
||||
const wrapper = mount({
|
||||
template: `
|
||||
<span>
|
||||
{{ __('${key}') }}
|
||||
</span>
|
||||
`,
|
||||
});
|
||||
|
||||
expect(wrapper.text()).toBe(translation);
|
||||
});
|
||||
|
||||
it('translate plural text (`n__`) without any substituting text', () => {
|
||||
const key = 'plural';
|
||||
const translationPlural = 'plural_multiple translation';
|
||||
createTranslationMock(key, 'plural_singular translation', translationPlural);
|
||||
|
||||
const wrapper = mount({
|
||||
template: `
|
||||
<span>
|
||||
{{ n__('${key}', 'plurals', 2) }}
|
||||
</span>
|
||||
`,
|
||||
});
|
||||
|
||||
expect(wrapper.text()).toBe(translationPlural);
|
||||
afterAll(() => {
|
||||
locale.textdomain(oldDomain);
|
||||
locale.options.locale_data = oldData;
|
||||
});
|
||||
|
||||
describe('translate plural text (`n__`) with substituting %d', () => {
|
||||
const key = '%d day';
|
||||
it('works properly', async () => {
|
||||
const wrapper = await shallowMount(Component);
|
||||
|
||||
beforeEach(() => {
|
||||
createTranslationMock(key, '%d singular translated', '%d plural translated');
|
||||
});
|
||||
const { wrappers } = wrapper.findAll('span');
|
||||
|
||||
it('and n === 1', () => {
|
||||
const wrapper = mount({
|
||||
template: `
|
||||
<span>
|
||||
{{ n__('${key}', '%d days', 1) }}
|
||||
</span>
|
||||
`,
|
||||
});
|
||||
// Just to ensure that the rendering actually worked;
|
||||
expect(wrappers.length).toBe(10);
|
||||
|
||||
expect(wrapper.text()).toBe('1 singular translated');
|
||||
});
|
||||
|
||||
it('and n > 1', () => {
|
||||
const wrapper = mount({
|
||||
template: `
|
||||
<span>
|
||||
{{ n__('${key}', '%d days', 2) }}
|
||||
</span>
|
||||
`,
|
||||
});
|
||||
|
||||
expect(wrapper.text()).toBe('2 plural translated');
|
||||
});
|
||||
});
|
||||
|
||||
describe('translates text with context `s__`', () => {
|
||||
const key = 'Context|Foobar';
|
||||
const translation = 'Context|Foobar translated';
|
||||
const expectation = 'Foobar translated';
|
||||
|
||||
beforeEach(() => {
|
||||
createTranslationMock(key, translation);
|
||||
});
|
||||
|
||||
it('and using two parameters', () => {
|
||||
const wrapper = mount({
|
||||
template: `
|
||||
<span>
|
||||
{{ s__('Context', 'Foobar') }}
|
||||
</span>
|
||||
`,
|
||||
});
|
||||
|
||||
expect(wrapper.text()).toBe(expectation);
|
||||
});
|
||||
|
||||
it('and using the pipe syntax', () => {
|
||||
const wrapper = mount({
|
||||
template: `
|
||||
<span>
|
||||
{{ s__('${key}') }}
|
||||
</span>
|
||||
`,
|
||||
});
|
||||
|
||||
expect(wrapper.text()).toBe(expectation);
|
||||
});
|
||||
});
|
||||
|
||||
it('translate multi line text', () => {
|
||||
const translation = 'multiline string translated';
|
||||
createTranslationMock('multiline string', translation);
|
||||
|
||||
const wrapper = mount({
|
||||
template: `
|
||||
<span>
|
||||
{{ __(\`
|
||||
multiline
|
||||
string
|
||||
\`) }}
|
||||
</span>
|
||||
`,
|
||||
});
|
||||
|
||||
expect(wrapper.text()).toBe(translation);
|
||||
});
|
||||
|
||||
it('translate pluralized multi line text', () => {
|
||||
const translation = 'multiline string plural';
|
||||
|
||||
createTranslationMock('multiline string', 'multiline string singular', translation);
|
||||
|
||||
const wrapper = mount({
|
||||
template: `
|
||||
<span>
|
||||
{{ n__(
|
||||
\`
|
||||
multiline
|
||||
string
|
||||
\`,
|
||||
\`
|
||||
multiline
|
||||
strings
|
||||
\`,
|
||||
2
|
||||
) }}
|
||||
</span>
|
||||
`,
|
||||
});
|
||||
|
||||
expect(wrapper.text()).toBe(translation);
|
||||
});
|
||||
|
||||
it('translate pluralized multi line text with context', () => {
|
||||
const translation = 'multiline string with context';
|
||||
|
||||
createTranslationMock('Context| multiline string', translation);
|
||||
|
||||
const wrapper = mount({
|
||||
template: `
|
||||
<span>
|
||||
{{ s__(
|
||||
\`
|
||||
Context|
|
||||
multiline
|
||||
string
|
||||
\`
|
||||
) }}
|
||||
</span>
|
||||
`,
|
||||
});
|
||||
|
||||
expect(wrapper.text()).toBe(translation);
|
||||
for (const span of wrappers) {
|
||||
expect(span.text().trim()).toBe(span.attributes()['data-expected']);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
<template>
|
||||
<div>
|
||||
<!-- singular text (`__`) -->
|
||||
<span data-expected="singular_translated">
|
||||
{{ __('singular') }}
|
||||
</span>
|
||||
<!-- plural text (`n__`) without any substituting text -->
|
||||
<span data-expected="plural_singular translation">
|
||||
{{ n__('plural', 'plurals', 1) }}
|
||||
</span>
|
||||
<span data-expected="plural_multiple translation">
|
||||
{{ n__('plural', 'plurals', 2) }}
|
||||
</span>
|
||||
<!-- plural text (`n__`) with substituting %d -->
|
||||
<span data-expected="1 singular translated">
|
||||
{{ n__('%d day', '%d days', 1) }}
|
||||
</span>
|
||||
<span data-expected="2 plural translated">
|
||||
{{ n__('%d day', '%d days', 2) }}
|
||||
</span>
|
||||
<!-- text with context `s__` -->
|
||||
<span data-expected="Foobar translated">
|
||||
{{ s__('Context', 'Foobar') }}
|
||||
</span>
|
||||
<span data-expected="Foobar translated">
|
||||
{{ s__('Context|Foobar') }}
|
||||
</span>
|
||||
<!-- multi line text -->
|
||||
<span data-expected="multiline string translated">
|
||||
{{
|
||||
__(`
|
||||
multiline
|
||||
string
|
||||
`)
|
||||
}}
|
||||
</span>
|
||||
<!-- pluralized line text -->
|
||||
<span data-expected="multiline string plural">
|
||||
{{
|
||||
n__(
|
||||
`
|
||||
multiline
|
||||
plural
|
||||
`,
|
||||
`
|
||||
multiline
|
||||
plurals
|
||||
`,
|
||||
2,
|
||||
)
|
||||
}}
|
||||
</span>
|
||||
<!-- multi line text with context -->
|
||||
<span data-expected="multiline string with context">
|
||||
{{
|
||||
s__(
|
||||
`
|
||||
Context|
|
||||
multiline
|
||||
string
|
||||
`,
|
||||
)
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -56,7 +56,7 @@ RSpec.describe Backup::Targets::Files, feature_category: :backup_restore do
|
|||
|
||||
it 'moves all necessary files' do
|
||||
expect_next_instance_of(Gitlab::Backup::Cli::Shell::Pipeline) do |pipeline|
|
||||
expect(pipeline).to receive(:run_pipeline!).and_return(pipeline_status_success)
|
||||
expect(pipeline).to receive(:run!).and_return(pipeline_status_success)
|
||||
end
|
||||
|
||||
tmp_dir = backup_basepath.join('tmp', "registry.#{Time.now.to_i}")
|
||||
|
|
@ -67,7 +67,7 @@ RSpec.describe Backup::Targets::Files, feature_category: :backup_restore do
|
|||
|
||||
it 'raises no errors' do
|
||||
expect_next_instance_of(Gitlab::Backup::Cli::Shell::Pipeline) do |pipeline|
|
||||
expect(pipeline).to receive(:run_pipeline!).and_return(pipeline_status_success)
|
||||
expect(pipeline).to receive(:run!).and_return(pipeline_status_success)
|
||||
end
|
||||
|
||||
expect { files.restore('registry.tar.gz', 'backup_id') }.not_to raise_error
|
||||
|
|
@ -80,7 +80,7 @@ RSpec.describe Backup::Targets::Files, feature_category: :backup_restore do
|
|||
expect(tar_cmd.cmd_args).to include('--unlink-first')
|
||||
expect(tar_cmd.cmd_args).to include('--recursive-unlink')
|
||||
|
||||
expect(pipeline).to receive(:run_pipeline!).and_return(pipeline_status_success)
|
||||
expect(pipeline).to receive(:run!).and_return(pipeline_status_success)
|
||||
end
|
||||
|
||||
files.restore('registry.tar.gz', 'backup_id')
|
||||
|
|
@ -88,7 +88,7 @@ RSpec.describe Backup::Targets::Files, feature_category: :backup_restore do
|
|||
|
||||
it 'raises an error on failure' do
|
||||
expect_next_instance_of(Gitlab::Backup::Cli::Shell::Pipeline) do |pipeline|
|
||||
expect(pipeline).to receive(:run_pipeline!).and_return(pipeline_status_failed)
|
||||
expect(pipeline).to receive(:run!).and_return(pipeline_status_failed)
|
||||
end
|
||||
|
||||
expect { files.restore('registry.tar.gz', 'backup_id') }.to raise_error(/Restore operation failed:/)
|
||||
|
|
@ -99,7 +99,7 @@ RSpec.describe Backup::Targets::Files, feature_category: :backup_restore do
|
|||
before do
|
||||
FileUtils.touch('registry.tar.gz')
|
||||
allow(FileUtils).to receive(:mv).and_raise(Errno::EACCES)
|
||||
allow(files).to receive(:run_pipeline!).and_return([[true, true], ''])
|
||||
allow(files).to receive(:run!).and_return([[true, true], ''])
|
||||
allow(files).to receive(:pipeline_succeeded?).and_return(true)
|
||||
end
|
||||
|
||||
|
|
@ -117,7 +117,7 @@ RSpec.describe Backup::Targets::Files, feature_category: :backup_restore do
|
|||
describe 'folders that are a mountpoint' do
|
||||
before do
|
||||
allow(FileUtils).to receive(:mv).and_raise(Errno::EBUSY)
|
||||
allow(files).to receive(:run_pipeline!).and_return([[true, true], ''])
|
||||
allow(files).to receive(:run!).and_return([[true, true], ''])
|
||||
allow(files).to receive(:pipeline_succeeded?).and_return(true)
|
||||
end
|
||||
|
||||
|
|
@ -140,7 +140,7 @@ RSpec.describe Backup::Targets::Files, feature_category: :backup_restore do
|
|||
decompress_cmd = pipeline.shell_commands[0]
|
||||
|
||||
expect(decompress_cmd.cmd_args).to include('tee')
|
||||
expect(pipeline).to receive(:run_pipeline!).and_return(pipeline_status_success)
|
||||
expect(pipeline).to receive(:run!).and_return(pipeline_status_success)
|
||||
end
|
||||
|
||||
expect do
|
||||
|
|
@ -166,7 +166,7 @@ RSpec.describe Backup::Targets::Files, feature_category: :backup_restore do
|
|||
expect(tar_cmd.cmd_args).to include('--exclude=lost+found')
|
||||
expect(tar_cmd.cmd_args).to include('--exclude=./@pages.tmp')
|
||||
|
||||
allow(pipeline).to receive(:run_pipeline!).and_call_original
|
||||
allow(pipeline).to receive(:run!).and_call_original
|
||||
end
|
||||
|
||||
files.dump('registry.tar.gz', 'backup_id')
|
||||
|
|
|
|||
|
|
@ -242,6 +242,15 @@ RSpec.describe Commit, feature_category: :source_code_management do
|
|||
expect(recorder.count).to be_zero
|
||||
end
|
||||
end
|
||||
|
||||
context 'when author_email is nil' do
|
||||
let(:git_commit) { RepoHelpers.sample_commit.tap { |c| c.author_email = nil } }
|
||||
let(:commit) { described_class.new(git_commit, build(:project)) }
|
||||
|
||||
it 'returns nil' do
|
||||
expect(commit.author).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#committer' do
|
||||
|
|
|
|||
|
|
@ -0,0 +1,102 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Packages::Conan::PackageReference, type: :model, feature_category: :package_registry do
|
||||
describe 'associations' do
|
||||
it do
|
||||
is_expected.to belong_to(:package).class_name('Packages::Conan::Package').inverse_of(:conan_package_references)
|
||||
end
|
||||
|
||||
it do
|
||||
is_expected.to belong_to(:recipe_revision).class_name('Packages::Conan::RecipeRevision')
|
||||
.inverse_of(:conan_package_references)
|
||||
end
|
||||
|
||||
it { is_expected.to belong_to(:project) }
|
||||
end
|
||||
|
||||
describe 'validations' do
|
||||
subject(:package_reference) { build(:conan_package_reference) }
|
||||
|
||||
it { is_expected.to validate_presence_of(:package) }
|
||||
it { is_expected.to validate_presence_of(:project) }
|
||||
it { is_expected.to validate_presence_of(:reference) }
|
||||
|
||||
it do
|
||||
# ignore case, same revision string with different case are converted to same hexa binary
|
||||
is_expected.to validate_uniqueness_of(:reference).scoped_to([:package_id,
|
||||
:recipe_revision_id]).case_insensitive
|
||||
end
|
||||
|
||||
context 'on reference' do
|
||||
let(:invalid_reference) { 'a' * (Packages::Conan::PackageReference::REFERENCE_LENGTH_MAX + 1) }
|
||||
|
||||
context 'when the length exceeds the maximum byte size' do
|
||||
it 'is not valid', :aggregate_failures do
|
||||
package_reference.reference = invalid_reference
|
||||
|
||||
expect(package_reference).not_to be_valid
|
||||
expect(package_reference.errors[:reference]).to include(
|
||||
"is too long (#{Packages::Conan::PackageReference::REFERENCE_LENGTH_MAX + 1} B). " \
|
||||
"The maximum size is #{Packages::Conan::PackageReference::REFERENCE_LENGTH_MAX} B.")
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the length is within the byte size limit' do
|
||||
it 'is valid' do
|
||||
# package_reference is set correclty in the factory
|
||||
expect(package_reference).to be_valid
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'on info' do
|
||||
subject(:package_reference) do
|
||||
pr = build(:conan_package_reference)
|
||||
pr.info = info if defined?(info)
|
||||
pr
|
||||
end
|
||||
|
||||
it { is_expected.to be_valid }
|
||||
|
||||
context 'with empty conan info' do
|
||||
let(:info) { {} }
|
||||
|
||||
it { is_expected.to be_valid }
|
||||
end
|
||||
|
||||
context 'with invalid conan info' do
|
||||
let(:info) { { invalid_field: 'some_value' } }
|
||||
|
||||
it 'is invalid', :aggregate_failures do
|
||||
expect(package_reference).not_to be_valid
|
||||
expect(package_reference.errors[:info]).to include(
|
||||
'object at root is missing required properties: settings, requires, options')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when info size exceeds the maximum allowed size' do
|
||||
before do
|
||||
stub_const('Packages::Conan::PackageReference::MAX_INFO_SIZE', 1000)
|
||||
end
|
||||
|
||||
let(:info) do
|
||||
{
|
||||
settings: { os: 'Linux', arch: 'x86_64' },
|
||||
requires: ['libA/1.0@user/testing'],
|
||||
options: { fPIC: true },
|
||||
otherProperties: 'a' * 1001 # Simulates large data
|
||||
}
|
||||
end
|
||||
|
||||
it 'is invalid due to large size' do
|
||||
expect(package_reference).not_to be_valid
|
||||
expect(package_reference.errors[:info]).to include(
|
||||
'conaninfo is too large. Maximum size is 1000 characters'
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -10,6 +10,11 @@ RSpec.describe Packages::Conan::Package, type: :model, feature_category: :packag
|
|||
is_expected.to have_many(:conan_recipe_revisions).inverse_of(:package)
|
||||
.class_name('Packages::Conan::RecipeRevision')
|
||||
end
|
||||
|
||||
it do
|
||||
is_expected.to have_many(:conan_package_references).inverse_of(:package)
|
||||
.class_name('Packages::Conan::PackageReference')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'validations' do
|
||||
|
|
|
|||
|
|
@ -9,6 +9,11 @@ RSpec.describe Packages::Conan::RecipeRevision, type: :model, feature_category:
|
|||
end
|
||||
|
||||
it { is_expected.to belong_to(:project) }
|
||||
|
||||
it do
|
||||
is_expected.to have_many(:conan_package_references).inverse_of(:recipe_revision)
|
||||
.class_name('Packages::Conan::PackageReference')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'validations' do
|
||||
|
|
|
|||
|
|
@ -48,6 +48,39 @@ RSpec.describe VirtualRegistries::Packages::Maven::CachedResponse, type: :model,
|
|||
end
|
||||
end
|
||||
|
||||
describe 'scopes' do
|
||||
describe '.orphan' do
|
||||
subject { described_class.orphan }
|
||||
|
||||
let_it_be(:cached_response) { create(:virtual_registries_packages_maven_cached_response) }
|
||||
let_it_be(:orphan_cached_response) { create(:virtual_registries_packages_maven_cached_response, :orphan) }
|
||||
|
||||
it { is_expected.to contain_exactly(orphan_cached_response) }
|
||||
end
|
||||
|
||||
describe '.pending_destruction' do
|
||||
subject { described_class.pending_destruction }
|
||||
|
||||
let_it_be(:cached_response) { create(:virtual_registries_packages_maven_cached_response, :orphan, :processing) }
|
||||
let_it_be(:pending_destruction_cached_response) do
|
||||
create(:virtual_registries_packages_maven_cached_response, :orphan)
|
||||
end
|
||||
|
||||
it { is_expected.to contain_exactly(pending_destruction_cached_response) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '.next_pending_destruction' do
|
||||
subject { described_class.next_pending_destruction }
|
||||
|
||||
let_it_be(:cached_response) { create(:virtual_registries_packages_maven_cached_response) }
|
||||
let_it_be(:pending_destruction_cached_response) do
|
||||
create(:virtual_registries_packages_maven_cached_response, :orphan)
|
||||
end
|
||||
|
||||
it { is_expected.to eq(pending_destruction_cached_response) }
|
||||
end
|
||||
|
||||
describe 'object storage key' do
|
||||
it 'can not be null' do
|
||||
cached_response.object_storage_key = nil
|
||||
|
|
|
|||
|
|
@ -1008,7 +1008,7 @@ RSpec.describe API::VirtualRegistries::Packages::Maven, :aggregate_failures, fea
|
|||
cached_response
|
||||
.as_json
|
||||
.merge('cached_response_id' => Base64.urlsafe_encode64(cached_response.relative_path))
|
||||
.except('id', 'object_storage_key', 'file_store')
|
||||
.except('id', 'object_storage_key', 'file_store', 'status')
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -11,9 +11,12 @@ RSpec.describe DependencyProxy::CleanupDependencyProxyWorker, feature_category:
|
|||
it 'queues the cleanup jobs', :aggregate_failures do
|
||||
create(:dependency_proxy_blob, :pending_destruction)
|
||||
create(:dependency_proxy_manifest, :pending_destruction)
|
||||
create(:virtual_registries_packages_maven_cached_response, :orphan)
|
||||
|
||||
expect(DependencyProxy::CleanupBlobWorker).to receive(:perform_with_capacity).twice
|
||||
expect(DependencyProxy::CleanupManifestWorker).to receive(:perform_with_capacity).twice
|
||||
expect(::VirtualRegistries::Packages::DestroyOrphanCachedResponsesWorker)
|
||||
.to receive(:perform_with_capacity).twice
|
||||
|
||||
subject
|
||||
end
|
||||
|
|
@ -25,6 +28,8 @@ RSpec.describe DependencyProxy::CleanupDependencyProxyWorker, feature_category:
|
|||
it 'does not queue the cleanup jobs', :aggregate_failures do
|
||||
expect(DependencyProxy::CleanupBlobWorker).not_to receive(:perform_with_capacity)
|
||||
expect(DependencyProxy::CleanupManifestWorker).not_to receive(:perform_with_capacity)
|
||||
expect(::VirtualRegistries::Packages::DestroyOrphanCachedResponsesWorker)
|
||||
.not_to receive(:perform_with_capacity)
|
||||
|
||||
subject
|
||||
end
|
||||
|
|
|
|||
|
|
@ -473,6 +473,7 @@ RSpec.describe 'Every Sidekiq worker', feature_category: :shared do
|
|||
'Vulnerabilities::Statistics::AdjustmentWorker' => 3,
|
||||
'VulnerabilityExports::ExportDeletionWorker' => 3,
|
||||
'VulnerabilityExports::ExportWorker' => 3,
|
||||
'VirtualRegistries::Packages::DestroyOrphanCachedResponsesWorker' => 0,
|
||||
'WaitForClusterCreationWorker' => 3,
|
||||
'WebHookWorker' => 4,
|
||||
'WebHooks::LogExecutionWorker' => 3,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,85 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe VirtualRegistries::Packages::DestroyOrphanCachedResponsesWorker, type: :worker, feature_category: :virtual_registry do
|
||||
let(:worker) { described_class.new }
|
||||
let(:model) { ::VirtualRegistries::Packages::Maven::CachedResponse }
|
||||
|
||||
it_behaves_like 'an idempotent worker' do
|
||||
let(:job_args) { [model.name] }
|
||||
end
|
||||
|
||||
it_behaves_like 'worker with data consistency', described_class, data_consistency: :sticky
|
||||
|
||||
it 'has a none deduplicate strategy' do
|
||||
expect(described_class.get_deduplicate_strategy).to eq(:none)
|
||||
end
|
||||
|
||||
describe '#perform_work' do
|
||||
subject(:perform_work) { worker.perform_work(model.name) }
|
||||
|
||||
context 'with no work to do' do
|
||||
it { is_expected.to be_nil }
|
||||
end
|
||||
|
||||
context 'with work to do' do
|
||||
let_it_be(:cached_response) { create(:virtual_registries_packages_maven_cached_response) }
|
||||
let_it_be(:orphan_cached_response) { create(:virtual_registries_packages_maven_cached_response, :orphan) }
|
||||
|
||||
it 'destroys orphan cached responses' do
|
||||
expect(worker).to receive(:log_extra_metadata_on_done).with(:cached_response_id, orphan_cached_response.id)
|
||||
expect(worker).to receive(:log_extra_metadata_on_done).with(:group_id, orphan_cached_response.group_id)
|
||||
expect(worker).to receive(:log_extra_metadata_on_done).with(:relative_path,
|
||||
orphan_cached_response.relative_path)
|
||||
expect(model).to receive(:next_pending_destruction).and_call_original
|
||||
expect { perform_work }.to change { model.count }.by(-1)
|
||||
expect { orphan_cached_response.reset }.to raise_error(ActiveRecord::RecordNotFound)
|
||||
end
|
||||
|
||||
context 'with an error during deletion' do
|
||||
before do
|
||||
allow_next_found_instance_of(model) do |instance|
|
||||
allow(instance).to receive(:destroy).and_raise(StandardError)
|
||||
end
|
||||
end
|
||||
|
||||
it 'tracks the error' do
|
||||
expect(Gitlab::ErrorTracking).to receive(:log_exception).with(
|
||||
instance_of(StandardError), class: described_class.name
|
||||
)
|
||||
|
||||
expect { perform_work }.to change { model.error.count }.by(1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when trying to update a destroyed record' do
|
||||
before do
|
||||
allow_next_found_instance_of(model) do |instance|
|
||||
destroy_method = instance.method(:destroy!)
|
||||
|
||||
allow(instance).to receive(:destroy!) do
|
||||
destroy_method.call
|
||||
|
||||
raise StandardError
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'does not change the status to error' do
|
||||
expect(Gitlab::ErrorTracking).to receive(:log_exception)
|
||||
.with(instance_of(StandardError), class: described_class.name)
|
||||
expect { perform_work }.not_to change { model.error.count }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#max_running_jobs' do
|
||||
let(:capacity) { described_class::MAX_CAPACITY }
|
||||
|
||||
subject { worker.max_running_jobs }
|
||||
|
||||
it { is_expected.to eq(capacity) }
|
||||
end
|
||||
end
|
||||
|
|
@ -1362,10 +1362,10 @@
|
|||
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.117.0.tgz#a9a45949d73e91f278e019b46220feb63105dd48"
|
||||
integrity sha512-nBFWh2UN+pFl7nBQUgaUtHRChrZSXdRNmv1J49QPLwyJYWuq51YEBXQW5mPAlvB1BGfiNJPSHSGxWALFZBI5WA==
|
||||
|
||||
"@gitlab/ui@94.0.0":
|
||||
version "94.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-94.0.0.tgz#6c17bcb37a9fbc521099a65eee8a52aba45e3163"
|
||||
integrity sha512-qO3QaQa8nbsTM1sCwlz6QKYXYQ1DntFWj9GcmlxVolDL2Ctkl9tAf8VLooPT0fqwUr6CYsim+N/jibBlOt6xnQ==
|
||||
"@gitlab/ui@94.0.1":
|
||||
version "94.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-94.0.1.tgz#4be575ab6e6583ed65ac7c78e70d827706367639"
|
||||
integrity sha512-6RD78bQ7Bff1qU3k7E8ZU/4YQ8PD68aRaE7eHojKBtAfR9ClGvPeFMFkTFAdnfV5xjBOE6tKMzR0pOhZE3V6cQ==
|
||||
dependencies:
|
||||
"@floating-ui/dom" "1.4.3"
|
||||
echarts "^5.3.2"
|
||||
|
|
|
|||
Loading…
Reference in New Issue