mirror of
https://gitlab.com/gitlab-org/gitlab-foss.git
synced 2025-07-21 23:43:41 +00:00
Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
@ -593,9 +593,6 @@ export default {
|
||||
'ee/app/assets/javascripts/tracing/details/tracing_details.vue',
|
||||
'ee/app/assets/javascripts/usage_quotas/code_suggestions/components/code_suggestions_info_card.vue',
|
||||
'ee/app/assets/javascripts/usage_quotas/code_suggestions/components/search_and_sort_bar.vue',
|
||||
'ee/app/assets/javascripts/usage_quotas/pipelines/components/app.vue',
|
||||
'ee/app/assets/javascripts/usage_quotas/pipelines/components/minutes_usage_per_project_chart.vue',
|
||||
'ee/app/assets/javascripts/usage_quotas/pipelines/components/shared_runner_usage_month_chart.vue',
|
||||
'ee/app/assets/javascripts/usage_quotas/seats/components/statistics_seats_card.vue',
|
||||
'ee/app/assets/javascripts/usage_quotas/transfer/components/usage_by_month.vue',
|
||||
'ee/app/assets/javascripts/users/identity_verification/components/credit_card_verification.vue',
|
||||
|
@ -2,27 +2,6 @@
|
||||
# Cop supports --autocorrect.
|
||||
RSpec/BeNil:
|
||||
Exclude:
|
||||
- 'ee/spec/services/app_sec/dast/profiles/update_service_spec.rb'
|
||||
- 'ee/spec/services/gitlab_subscriptions/reconciliations/calculate_seat_count_data_service_spec.rb'
|
||||
- 'ee/spec/workers/concerns/geo/skip_secondary_spec.rb'
|
||||
- 'ee/spec/workers/repository_update_mirror_worker_spec.rb'
|
||||
- 'qa/qa/specs/features/ee/browser_ui/10_govern/scan_execution_policy_vulnerabilities_spec.rb'
|
||||
- 'qa/qa/specs/features/ee/browser_ui/10_govern/scan_result_policy_license_finding_spec.rb'
|
||||
- 'qa/qa/specs/features/ee/browser_ui/10_govern/scan_result_policy_vulnerabilities_spec.rb'
|
||||
- 'qa/spec/page/element_spec.rb'
|
||||
- 'qa/spec/service/docker_run/mixins/third_party_docker_spec.rb'
|
||||
- 'qa/spec/service/shellout_spec.rb'
|
||||
- 'spec/config/object_store_settings_spec.rb'
|
||||
- 'spec/controllers/application_controller_spec.rb'
|
||||
- 'spec/dot_gitlab_ci/ci_configuration_validation/shared_context_and_examples.rb'
|
||||
- 'spec/features/admin/users/admin_impersonates_user_spec.rb'
|
||||
- 'spec/finders/container_repositories_finder_spec.rb'
|
||||
- 'spec/finders/uploader_finder_spec.rb'
|
||||
- 'spec/graphql/mutations/issues/set_due_date_spec.rb'
|
||||
- 'spec/graphql/resolvers/container_repositories_resolver_spec.rb'
|
||||
- 'spec/graphql/resolvers/paginated_tree_resolver_spec.rb'
|
||||
- 'spec/graphql/resolvers/tree_resolver_spec.rb'
|
||||
- 'spec/graphql/resolvers/users/group_count_resolver_spec.rb'
|
||||
- 'spec/helpers/namespaces_helper_spec.rb'
|
||||
- 'spec/helpers/tree_helper_spec.rb'
|
||||
- 'spec/helpers/version_check_helper_spec.rb'
|
||||
|
@ -1 +1 @@
|
||||
6af2d5f99e37feee2b7221af5f276040b8109195
|
||||
7dbf8d6fbb832f81e2bfb0a4c143a0932cbecf53
|
||||
|
@ -1 +1 @@
|
||||
93b9e36e23c2a4e51dc2012932830b72c8f838aa
|
||||
fecc9b9bcb1ab8b69fb72be11705fd47925302d2
|
||||
|
@ -1,38 +1,43 @@
|
||||
<script>
|
||||
import { GlFilteredSearch } from '@gitlab/ui';
|
||||
import { GlFilteredSearch, GlSorting } from '@gitlab/ui';
|
||||
import { setUrlParams, visitUrl } from '~/lib/utils/url_utility';
|
||||
import { TOKENS } from '../constants';
|
||||
import { initializeValuesFromQuery } from '../utils';
|
||||
import { TOKENS, SORT_OPTIONS } from '../constants';
|
||||
import { initializeValuesFromQuery, buildSortedUrl } from '../utils';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlFilteredSearch,
|
||||
GlSorting,
|
||||
},
|
||||
data() {
|
||||
return { tokens: initializeValuesFromQuery() };
|
||||
const { tokens, sorting } = initializeValuesFromQuery();
|
||||
return {
|
||||
tokens,
|
||||
sorting,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
availableTokens() {
|
||||
// Once SSH or GPG key is selected, discard the rest of the tokens
|
||||
if (this.hasKey()) {
|
||||
if (this.hasKey) {
|
||||
return TOKENS.filter(({ type }) => type === 'filter');
|
||||
}
|
||||
|
||||
return TOKENS;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
change() {
|
||||
// Once SSH or GPG key is selected, discard the rest of the tokens
|
||||
if (this.hasKey()) {
|
||||
this.tokens = this.tokens.filter(({ type }) => type === 'filter');
|
||||
}
|
||||
},
|
||||
hasKey() {
|
||||
return this.tokens.some(
|
||||
({ type, value }) => type === 'filter' && ['ssh_keys', 'gpg_keys'].includes(value.data),
|
||||
);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
change() {
|
||||
// Once SSH or GPG key is selected, discard the rest of the tokens
|
||||
if (this.hasKey) {
|
||||
this.tokens = this.tokens.filter(({ type }) => type === 'filter');
|
||||
}
|
||||
},
|
||||
search(tokens) {
|
||||
const newParams = {};
|
||||
|
||||
@ -47,21 +52,39 @@ export default {
|
||||
newParams[token.type] = token.value.data;
|
||||
}
|
||||
});
|
||||
|
||||
const newUrl = setUrlParams(newParams, window.location.href, true);
|
||||
visitUrl(newUrl);
|
||||
},
|
||||
handleSortChange(value) {
|
||||
visitUrl(buildSortedUrl(value, this.sorting.isAsc));
|
||||
},
|
||||
handleSortDirectionChange(isAsc) {
|
||||
visitUrl(buildSortedUrl(this.sorting.value, isAsc));
|
||||
},
|
||||
},
|
||||
SORT_OPTIONS,
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<gl-filtered-search
|
||||
v-model="tokens"
|
||||
:placeholder="s__('CredentialsInventory|Search or filter credentials...')"
|
||||
:available-tokens="availableTokens"
|
||||
terms-as-tokens
|
||||
@submit="search"
|
||||
@input="change"
|
||||
/>
|
||||
<div class="gl-flex gl-flex-col gl-gap-3 md:gl-flex-row">
|
||||
<gl-filtered-search
|
||||
v-model="tokens"
|
||||
:placeholder="s__('CredentialsInventory|Search or filter credentials...')"
|
||||
:available-tokens="availableTokens"
|
||||
terms-as-tokens
|
||||
@submit="search"
|
||||
@input="change"
|
||||
/>
|
||||
<gl-sorting
|
||||
v-if="!hasKey"
|
||||
block
|
||||
dropdown-class="gl-w-full"
|
||||
:is-ascending="sorting.isAsc"
|
||||
:sort-by="sorting.value"
|
||||
:sort-options="$options.SORT_OPTIONS"
|
||||
@sortByChange="handleSortChange"
|
||||
@sortDirectionChange="handleSortDirectionChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
@ -6,6 +6,10 @@ import {
|
||||
} from '~/vue_shared/components/filtered_search_bar/constants';
|
||||
import DateToken from '~/vue_shared/components/filtered_search_bar/tokens/date_token.vue';
|
||||
|
||||
export const SORT_KEY_NAME = 'name';
|
||||
export const SORT_KEY_CREATED = 'created';
|
||||
export const SORT_KEY_EXPIRES = 'expires';
|
||||
|
||||
export const TOKENS = [
|
||||
{
|
||||
icon: 'key',
|
||||
@ -73,3 +77,34 @@ export const TOKENS = [
|
||||
unique: true,
|
||||
},
|
||||
];
|
||||
|
||||
export const SORT_OPTIONS = [
|
||||
{
|
||||
text: __('Name'),
|
||||
value: SORT_KEY_NAME,
|
||||
sort: {
|
||||
asc: 'name_asc',
|
||||
desc: 'name_desc',
|
||||
},
|
||||
},
|
||||
{
|
||||
text: __('Created date'),
|
||||
value: SORT_KEY_CREATED,
|
||||
sort: {
|
||||
asc: 'created_asc',
|
||||
desc: 'created_desc',
|
||||
},
|
||||
},
|
||||
{
|
||||
text: __('Expiration date'),
|
||||
value: SORT_KEY_EXPIRES,
|
||||
sort: {
|
||||
asc: 'expires_at_asc_id_desc',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const DEFAULT_SORT = {
|
||||
value: SORT_KEY_EXPIRES,
|
||||
isAsc: true,
|
||||
};
|
||||
|
@ -1,9 +1,9 @@
|
||||
import Vue from 'vue';
|
||||
import CredentialsFilterApp from './components/credentials_filter_app.vue';
|
||||
import CredentialsFilterSortApp from './components/credentials_filter_sort_app.vue';
|
||||
|
||||
export const initCredentialsFilterApp = () => {
|
||||
export const initCredentialsFilterSortApp = () => {
|
||||
return new Vue({
|
||||
el: document.querySelector('#js-credentials-filter-app'),
|
||||
render: (createElement) => createElement(CredentialsFilterApp),
|
||||
el: document.querySelector('#js-credentials-filter-sort-app'),
|
||||
render: (createElement) => createElement(CredentialsFilterSortApp),
|
||||
});
|
||||
};
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { queryToObject } from '~/lib/utils/url_utility';
|
||||
import { queryToObject, setUrlParams } from '~/lib/utils/url_utility';
|
||||
import {
|
||||
OPERATORS_BEFORE,
|
||||
OPERATORS_AFTER,
|
||||
} from '~/vue_shared/components/filtered_search_bar/constants';
|
||||
import { TOKENS } from './constants';
|
||||
import { TOKENS, SORT_OPTIONS, DEFAULT_SORT } from './constants';
|
||||
|
||||
/**
|
||||
* @typedef {{type: string, value: {data: string, operator: string}}} Token
|
||||
@ -16,9 +16,10 @@ import { TOKENS } from './constants';
|
||||
* @returns {Array<string|Token>}
|
||||
*/
|
||||
export function initializeValuesFromQuery(query = document.location.search) {
|
||||
const tokens = [];
|
||||
const tokens = /** @type {Array<string|Token>} */ ([]);
|
||||
const sorting = DEFAULT_SORT;
|
||||
|
||||
const { search, ...terms } = queryToObject(query);
|
||||
const { search, sort, ...terms } = queryToObject(query);
|
||||
|
||||
for (const [key, value] of Object.entries(terms)) {
|
||||
const isBefore = key.endsWith('_before');
|
||||
@ -54,5 +55,18 @@ export function initializeValuesFromQuery(query = document.location.search) {
|
||||
tokens.push(search);
|
||||
}
|
||||
|
||||
return tokens;
|
||||
const sortOption = SORT_OPTIONS.find((item) => [item.sort.desc, item.sort.asc].includes(sort));
|
||||
if (sort && sortOption) {
|
||||
sorting.value = sortOption.value;
|
||||
sorting.isAsc = sortOption.sort.asc === sort;
|
||||
}
|
||||
|
||||
return { tokens, sorting };
|
||||
}
|
||||
|
||||
export function buildSortedUrl(value, isAsc) {
|
||||
const sortedOption = SORT_OPTIONS.find((sortOption) => sortOption.value === value);
|
||||
const sort = isAsc ? sortedOption.sort.asc : sortedOption.sort.desc;
|
||||
const newUrl = setUrlParams({ sort });
|
||||
return newUrl;
|
||||
}
|
||||
|
@ -639,15 +639,12 @@ export default {
|
||||
|
||||
Mousetrap.bind(keysFor(MR_PREVIOUS_FILE_IN_DIFF), () => this.jumpToFile(-1));
|
||||
Mousetrap.bind(keysFor(MR_NEXT_FILE_IN_DIFF), () => this.jumpToFile(+1));
|
||||
|
||||
if (this.commit) {
|
||||
Mousetrap.bind(keysFor(MR_COMMITS_NEXT_COMMIT), () =>
|
||||
this.moveToNeighboringCommit({ direction: 'next' }),
|
||||
);
|
||||
Mousetrap.bind(keysFor(MR_COMMITS_PREVIOUS_COMMIT), () =>
|
||||
this.moveToNeighboringCommit({ direction: 'previous' }),
|
||||
);
|
||||
}
|
||||
Mousetrap.bind(keysFor(MR_COMMITS_NEXT_COMMIT), () =>
|
||||
this.moveToNeighboringCommit({ direction: 'next' }),
|
||||
);
|
||||
Mousetrap.bind(keysFor(MR_COMMITS_PREVIOUS_COMMIT), () =>
|
||||
this.moveToNeighboringCommit({ direction: 'previous' }),
|
||||
);
|
||||
|
||||
Mousetrap.bind(['mod+f', 'mod+g'], () => {
|
||||
this.keydownTime = new Date().getTime();
|
||||
|
@ -1,5 +1,5 @@
|
||||
import initConfirmModal from '~/confirm_modal';
|
||||
import { initCredentialsFilterApp } from '~/credentials';
|
||||
import { initCredentialsFilterSortApp } from '~/credentials';
|
||||
|
||||
initConfirmModal();
|
||||
initCredentialsFilterApp();
|
||||
initCredentialsFilterSortApp();
|
||||
|
@ -1,5 +1,5 @@
|
||||
import initConfirmModal from '~/confirm_modal';
|
||||
import { initCredentialsFilterApp } from '~/credentials';
|
||||
import { initCredentialsFilterSortApp } from '~/credentials';
|
||||
|
||||
initConfirmModal();
|
||||
initCredentialsFilterApp();
|
||||
initCredentialsFilterSortApp();
|
||||
|
53
app/assets/javascripts/rapid_diffs/app/file_browser.vue
Normal file
53
app/assets/javascripts/rapid_diffs/app/file_browser.vue
Normal file
@ -0,0 +1,53 @@
|
||||
<script>
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { mapMutations } from 'vuex';
|
||||
import DiffsFileTree from '~/diffs/components/diffs_file_tree.vue';
|
||||
import * as types from '~/diffs/store/mutation_types';
|
||||
import { DIFF_FILE_MOUNTED } from '~/rapid_diffs/dom_events';
|
||||
|
||||
export default {
|
||||
name: 'FileBrowser',
|
||||
components: {
|
||||
DiffsFileTree,
|
||||
},
|
||||
props: {
|
||||
loadedFiles: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
visible: true,
|
||||
currentLoadedFiles: { ...this.loadedFiles },
|
||||
};
|
||||
},
|
||||
created() {
|
||||
document.addEventListener(DIFF_FILE_MOUNTED, this.addLoadedFile);
|
||||
},
|
||||
beforeDestroy() {
|
||||
document.removeEventListener(DIFF_FILE_MOUNTED, this.addLoadedFile);
|
||||
},
|
||||
methods: {
|
||||
...mapMutations('diffs', {
|
||||
setCurrentDiffFile: types.SET_CURRENT_DIFF_FILE,
|
||||
}),
|
||||
addLoadedFile({ target }) {
|
||||
this.currentLoadedFiles = { ...this.currentLoadedFiles, [target.id]: true };
|
||||
},
|
||||
clickFile(file) {
|
||||
this.$emit('clickFile', file);
|
||||
this.setCurrentDiffFile(file.fileHash);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<diffs-file-tree
|
||||
:visible="visible"
|
||||
:loaded-files="currentLoadedFiles"
|
||||
@toggled="visible = !visible"
|
||||
@clickFile="clickFile"
|
||||
/>
|
||||
</template>
|
@ -3,7 +3,7 @@ import { initViewSettings } from '~/rapid_diffs/app/view_settings';
|
||||
import { DiffFile } from '~/rapid_diffs/diff_file';
|
||||
import { DiffFileMounted } from '~/rapid_diffs/diff_file_mounted';
|
||||
import { useDiffsList } from '~/rapid_diffs/stores/diffs_list';
|
||||
import { initFileBrowser } from '~/rapid_diffs/app/file_browser';
|
||||
import { initFileBrowser } from '~/rapid_diffs/app/init_file_browser';
|
||||
|
||||
// This facade interface joins together all the bits and pieces of Rapid Diffs: DiffFile, Settings, File browser, etc.
|
||||
// It's a unified entrypoint for Rapid Diffs and all external communications should happen through this interface.
|
||||
|
@ -1,6 +1,8 @@
|
||||
import Vue from 'vue';
|
||||
import store from '~/mr_notes/stores';
|
||||
import DiffFileTree from '~/diffs/components/diffs_file_tree.vue';
|
||||
import { pinia } from '~/pinia/instance';
|
||||
import { DiffFile } from '~/rapid_diffs/diff_file';
|
||||
import FileBrowser from './file_browser.vue';
|
||||
|
||||
export async function initFileBrowser() {
|
||||
const el = document.querySelector('[data-file-browser]');
|
||||
@ -9,21 +11,21 @@ export async function initFileBrowser() {
|
||||
store.state.diffs.endpointMetadata = metadataEndpoint;
|
||||
await store.dispatch('diffs/fetchDiffFilesMeta');
|
||||
|
||||
const loadedFiles = Object.fromEntries(DiffFile.getAll().map((file) => [file.id, true]));
|
||||
|
||||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
el,
|
||||
data() {
|
||||
return {
|
||||
visible: true,
|
||||
};
|
||||
},
|
||||
store,
|
||||
pinia,
|
||||
render(h) {
|
||||
return h(DiffFileTree, {
|
||||
props: { visible: this.visible },
|
||||
return h(FileBrowser, {
|
||||
props: {
|
||||
loadedFiles,
|
||||
},
|
||||
on: {
|
||||
toggled: () => {
|
||||
this.visible = !this.visible;
|
||||
clickFile(file) {
|
||||
DiffFile.findByFileHash(file.fileHash).selectFile();
|
||||
},
|
||||
},
|
||||
});
|
@ -1,3 +1,4 @@
|
||||
import { DIFF_FILE_MOUNTED } from './dom_events';
|
||||
import { VIEWER_ADAPTERS } from './adapters';
|
||||
// required for easier mocking in tests
|
||||
import IntersectionObserver from './intersection_observer';
|
||||
@ -24,11 +25,11 @@ export class DiffFile extends HTMLElement {
|
||||
adapterConfig = VIEWER_ADAPTERS;
|
||||
|
||||
static findByFileHash(hash) {
|
||||
return document.querySelector(`diff-file#${hash}`);
|
||||
return document.querySelector(`diff-file[id="${hash}"]`);
|
||||
}
|
||||
|
||||
static getAll() {
|
||||
return document.querySelectorAll('diff-file');
|
||||
return Array.from(document.querySelectorAll('diff-file'));
|
||||
}
|
||||
|
||||
mount() {
|
||||
@ -38,6 +39,7 @@ export class DiffFile extends HTMLElement {
|
||||
this.observeVisibility();
|
||||
this.diffElement.addEventListener('click', this.onClick.bind(this));
|
||||
this.trigger(events.MOUNTED);
|
||||
this.dispatchEvent(new CustomEvent(DIFF_FILE_MOUNTED, { bubbles: true }));
|
||||
}
|
||||
|
||||
trigger(event, ...args) {
|
||||
@ -73,6 +75,11 @@ export class DiffFile extends HTMLElement {
|
||||
this.trigger(events.CLICK, event);
|
||||
}
|
||||
|
||||
selectFile() {
|
||||
this.scrollIntoView();
|
||||
// TODO: add outline for active file
|
||||
}
|
||||
|
||||
get data() {
|
||||
const data = { ...this.dataset };
|
||||
// viewer is dynamic, should be accessed via this.viewer
|
||||
|
1
app/assets/javascripts/rapid_diffs/dom_events.js
Normal file
1
app/assets/javascripts/rapid_diffs/dom_events.js
Normal file
@ -0,0 +1 @@
|
||||
export const DIFF_FILE_MOUNTED = 'DiffFileMounted';
|
@ -1,3 +1,10 @@
|
||||
@import 'framework/variables';
|
||||
|
||||
.rd-diff-file-component {
|
||||
// TODO: this must be defined using CSS Custom Properties to work across all pages
|
||||
scroll-margin-top: calc(#{$calc-application-header-height} + #{$mr-sticky-header-height} + #{12px});
|
||||
}
|
||||
|
||||
.rd-diff-file {
|
||||
padding-bottom: $gl-padding;
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
-# TODO: add fork suggestion (commits only)
|
||||
|
||||
%diff-file{ id: id, data: server_data }
|
||||
%diff-file.rd-diff-file-component{ id: id, data: server_data }
|
||||
.rd-diff-file
|
||||
= render RapidDiffs::DiffFileHeaderComponent.new(diff_file: @diff_file)
|
||||
-# extra wrapper needed so content-visibility: hidden doesn't require removing border or other styles
|
||||
|
@ -10,7 +10,7 @@ module RapidDiffs
|
||||
end
|
||||
|
||||
def id
|
||||
@diff_file.file_identifier_hash
|
||||
@diff_file.file_hash
|
||||
end
|
||||
|
||||
def server_data
|
||||
|
@ -21,7 +21,7 @@ class ProcessCommitWorker
|
||||
loggable_arguments 2, 3
|
||||
deduplicate :until_executed, feature_flag: :deduplicate_process_commit_worker
|
||||
|
||||
concurrency_limit -> { 1000 if Feature.enabled?(:concurrency_limit_process_commit_worker, Feature.current_request) }
|
||||
concurrency_limit -> { 1000 }
|
||||
|
||||
# project_id - The ID of the project this commit belongs to.
|
||||
# user_id - The ID of the user that pushed the commit.
|
||||
|
@ -1,9 +0,0 @@
|
||||
---
|
||||
name: concurrency_limit_process_commit_worker
|
||||
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/472602
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/171786
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/502784
|
||||
milestone: '17.6'
|
||||
group: group::source code
|
||||
type: worker
|
||||
default_enabled: false
|
@ -1,8 +1,9 @@
|
||||
---
|
||||
migration_job_name: BackfillProtectedEnvironmentApprovalRulesProtectedEnvironmentProjectId
|
||||
description: Backfills sharding key `protected_environment_approval_rules.protected_environment_project_id` from `protected_environments`.
|
||||
description: Backfills sharding key `protected_environment_approval_rules.protected_environment_project_id`
|
||||
from `protected_environments`.
|
||||
feature_category: continuous_delivery
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/162704
|
||||
milestone: '17.3'
|
||||
queued_migration_version: 20240814104154
|
||||
finalized_by: # version of the migration that finalized this BBM
|
||||
finalized_by: '20250220231747'
|
||||
|
@ -8,14 +8,6 @@ description: Notes created during the review of an MR that are not yet published
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/4213
|
||||
milestone: '11.4'
|
||||
gitlab_schema: gitlab_main_cell
|
||||
desired_sharding_key:
|
||||
project_id:
|
||||
references: projects
|
||||
backfill_via:
|
||||
parent:
|
||||
foreign_key: merge_request_id
|
||||
table: merge_requests
|
||||
sharding_key: target_project_id
|
||||
belongs_to: merge_request
|
||||
desired_sharding_key_migration_job_name: BackfillDraftNotesProjectId
|
||||
table_size: small
|
||||
sharding_key:
|
||||
project_id: projects
|
||||
|
@ -0,0 +1,9 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class AddOrganizationIdToAiDuoChatEvents < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.10'
|
||||
|
||||
def change
|
||||
add_column :ai_duo_chat_events, :organization_id, :bigint
|
||||
end
|
||||
end
|
@ -0,0 +1,13 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ValidateDraftNotesProjectIdNotNullConstraint < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.10'
|
||||
|
||||
def up
|
||||
validate_not_null_constraint :draft_notes, :project_id
|
||||
end
|
||||
|
||||
def down
|
||||
# no-op
|
||||
end
|
||||
end
|
@ -0,0 +1,18 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class AddAiDuoChatEventsOrganizationIdIndex < Gitlab::Database::Migration[2.2]
|
||||
include Gitlab::Database::PartitioningMigrationHelpers
|
||||
|
||||
disable_ddl_transaction!
|
||||
milestone '17.10'
|
||||
|
||||
INDEX_NAME = 'index_ai_duo_chat_events_on_organization_id'
|
||||
|
||||
def up
|
||||
add_concurrent_partitioned_index :ai_duo_chat_events, :organization_id, name: INDEX_NAME
|
||||
end
|
||||
|
||||
def down
|
||||
remove_concurrent_partitioned_index_by_name :ai_duo_chat_events, INDEX_NAME
|
||||
end
|
||||
end
|
@ -0,0 +1,19 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# This index was prepared in 17.9 PrepareNoteableIdNoteableTypeAndIdIndexInNotesTable migration
|
||||
class AddNoteableIdNoteableTypeAndIdIndexInNotesTable < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.10'
|
||||
disable_ddl_transaction!
|
||||
|
||||
INDEX_NAME = 'index_notes_on_noteable_id_noteable_type_and_id'
|
||||
|
||||
def up
|
||||
# rubocop:disable Migration/PreventIndexCreation -- index prepared in advance
|
||||
add_concurrent_index :notes, [:noteable_id, :noteable_type, :id], name: INDEX_NAME
|
||||
# rubocop:enable Migration/PreventIndexCreation
|
||||
end
|
||||
|
||||
def down
|
||||
remove_concurrent_index_by_name :notes, INDEX_NAME
|
||||
end
|
||||
end
|
@ -0,0 +1,21 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class FillAiDuoChatEventsOrganizationId < Gitlab::Database::Migration[2.2]
|
||||
disable_ddl_transaction!
|
||||
restrict_gitlab_migration gitlab_schema: :gitlab_main
|
||||
milestone '17.10'
|
||||
|
||||
def up
|
||||
return unless Gitlab.ee? # Only EE has proper table partitions and data.
|
||||
|
||||
chat_events = define_batchable_model(:ai_duo_chat_events)
|
||||
|
||||
chat_events.each_batch(of: 1000, column: :id) do |batch|
|
||||
batch.where(organization_id: nil).update_all(organization_id: 1) # DEFAULT_ORGANIZATION_ID
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
# no-op
|
||||
end
|
||||
end
|
@ -0,0 +1,22 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class FinalizeHkBackfillProtectedEnvironmentApprovalRulesProtectedEnvironmen < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.10'
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
restrict_gitlab_migration gitlab_schema: :gitlab_main_cell
|
||||
|
||||
def up
|
||||
ensure_batched_background_migration_is_finished(
|
||||
job_class_name: 'BackfillProtectedEnvironmentApprovalRulesProtectedEnvironmentProjectId',
|
||||
table_name: :protected_environment_approval_rules,
|
||||
column_name: :id,
|
||||
job_arguments: [:protected_environment_project_id, :protected_environments, :project_id,
|
||||
:protected_environment_id],
|
||||
finalize: true
|
||||
)
|
||||
end
|
||||
|
||||
def down; end
|
||||
end
|
1
db/schema_migrations/20250210065034
Normal file
1
db/schema_migrations/20250210065034
Normal file
@ -0,0 +1 @@
|
||||
17898021bbd8b5bdef675c73e0aef30a77b0cbbeb6348b89e75f2b4837ec58b4
|
1
db/schema_migrations/20250212110138
Normal file
1
db/schema_migrations/20250212110138
Normal file
@ -0,0 +1 @@
|
||||
1d540f78bcd88abb82caedee131a8aa7cc73c1900784af93e797162e6f0edd51
|
1
db/schema_migrations/20250212132647
Normal file
1
db/schema_migrations/20250212132647
Normal file
@ -0,0 +1 @@
|
||||
5fc9dfad9645f9d0a955e37a7fe1702e572701226ec1bdbfba754e1455e52d1a
|
1
db/schema_migrations/20250213125548
Normal file
1
db/schema_migrations/20250213125548
Normal file
@ -0,0 +1 @@
|
||||
f6e2391f8d78b18c53b5c6e4e44fb93c59355badb4bc2aedbfdcb5f6f44b1548
|
1
db/schema_migrations/20250214085204
Normal file
1
db/schema_migrations/20250214085204
Normal file
@ -0,0 +1 @@
|
||||
7984c710d624f787cf69ba62ec525875ffcf7bada3fa763966125c21c5259043
|
1
db/schema_migrations/20250220231747
Normal file
1
db/schema_migrations/20250220231747
Normal file
@ -0,0 +1 @@
|
||||
f471852ecae0f6af89d72a75897d3c13cebe57e25fdb509c1816d219d6c11cd5
|
@ -3973,6 +3973,7 @@ CREATE TABLE ai_duo_chat_events (
|
||||
event smallint NOT NULL,
|
||||
namespace_path text,
|
||||
payload jsonb,
|
||||
organization_id bigint,
|
||||
CONSTRAINT check_628cdfbf3f CHECK ((char_length(namespace_path) <= 255))
|
||||
)
|
||||
PARTITION BY RANGE ("timestamp");
|
||||
@ -13114,6 +13115,7 @@ CREATE TABLE draft_notes (
|
||||
internal boolean DEFAULT false NOT NULL,
|
||||
note_type smallint,
|
||||
project_id bigint,
|
||||
CONSTRAINT check_2a752d05fe CHECK ((project_id IS NOT NULL)),
|
||||
CONSTRAINT check_c497a94a0e CHECK ((char_length(line_code) <= 255))
|
||||
);
|
||||
|
||||
@ -27268,9 +27270,6 @@ ALTER TABLE ONLY chat_names
|
||||
ALTER TABLE ONLY chat_teams
|
||||
ADD CONSTRAINT chat_teams_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE draft_notes
|
||||
ADD CONSTRAINT check_2a752d05fe CHECK ((project_id IS NOT NULL)) NOT VALID;
|
||||
|
||||
ALTER TABLE workspaces
|
||||
ADD CONSTRAINT check_2a89035b04 CHECK ((personal_access_token_id IS NOT NULL)) NOT VALID;
|
||||
|
||||
@ -31511,6 +31510,8 @@ CREATE INDEX index_ai_conversation_threads_on_organization_id ON ai_conversation
|
||||
|
||||
CREATE INDEX index_ai_conversation_threads_on_user_id_and_last_updated_at ON ai_conversation_threads USING btree (user_id, last_updated_at);
|
||||
|
||||
CREATE INDEX index_ai_duo_chat_events_on_organization_id ON ONLY ai_duo_chat_events USING btree (organization_id);
|
||||
|
||||
CREATE INDEX index_ai_duo_chat_events_on_personal_namespace_id ON ONLY ai_duo_chat_events USING btree (personal_namespace_id);
|
||||
|
||||
CREATE INDEX index_ai_duo_chat_events_on_user_id ON ONLY ai_duo_chat_events USING btree (user_id);
|
||||
@ -33959,6 +33960,8 @@ CREATE INDEX index_notes_on_namespace_id ON notes USING btree (namespace_id);
|
||||
|
||||
CREATE INDEX index_notes_on_noteable_id_and_noteable_type_and_system ON notes USING btree (noteable_id, noteable_type, system);
|
||||
|
||||
CREATE INDEX index_notes_on_noteable_id_noteable_type_and_id ON notes USING btree (noteable_id, noteable_type, id);
|
||||
|
||||
CREATE INDEX index_notes_on_project_id_and_id_and_system_false ON notes USING btree (project_id, id) WHERE (NOT system);
|
||||
|
||||
CREATE INDEX index_notes_on_project_id_and_noteable_type ON notes USING btree (project_id, noteable_type);
|
||||
|
@ -92,7 +92,7 @@ We provide two debugging scripts to help administrators verify their self-hosted
|
||||
```
|
||||
|
||||
For a `mixtral` model running on vLLM:
|
||||
|
||||
|
||||
```shell
|
||||
poetry run troubleshoot \
|
||||
--model-family=mixtral \
|
||||
@ -114,6 +114,14 @@ Verify the output of the commands, and fix accordingly.
|
||||
If both commands are successful, but GitLab Duo Code Suggestions is still not working,
|
||||
raise an issue on the issue tracker.
|
||||
|
||||
## GitLab Duo health check is not working
|
||||
|
||||
When you [run a health check for GitLab Duo](../../user/gitlab_duo/setup.md#run-a-health-check-for-gitlab-duo), you might get an error like a `401 response from the AI gateway`.
|
||||
|
||||
To resolve, first check if GitLab Duo features are functioning correctly. For example, send a message to Duo Chat.
|
||||
|
||||
If this does not work, the error might be because of a known issue with GitLab Duo health check. For more information, see [issue 517097](https://gitlab.com/gitlab-org/gitlab/-/issues/517097).
|
||||
|
||||
## Check if GitLab can make a request to the model
|
||||
|
||||
From the GitLab Rails console, verify that GitLab can make a request to the model
|
||||
|
@ -112,3 +112,5 @@ These tests are performed:
|
||||
| Network | Tests whether your instance can connect to `customers.gitlab.com` and `cloud.gitlab.com`.<br><br>If your instance cannot connect to either destination, ensure that your firewall or proxy server settings [allow connection](setup.md). |
|
||||
| Synchronization | Tests whether your subscription: <br>- Has been activated with an activation code and can be synchronized with `customers.gitlab.com`.<br>- Has correct access credentials.<br>- Has been synchronized recently. If it hasn't or the access credentials are missing or expired, you can [manually synchronize](../../subscriptions/self_managed/_index.md#manually-synchronize-subscription-data) your subscription data. |
|
||||
| System exchange | Tests whether Code Suggestions can be used in your instance. If the system exchange assessment fails, users might not be able to use GitLab Duo features. |
|
||||
|
||||
If you are experiencing any issues with the health check, see [GitLab Duo Self-Hosted troubleshooting](../../administration/gitlab_duo_self_hosted/troubleshooting.md#gitlab-duo-health-check-is-not-working).
|
||||
|
@ -5,347 +5,348 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
||||
title: Publish packages with Yarn
|
||||
---
|
||||
|
||||
You can publish packages with [Yarn 1 (Classic)](https://classic.yarnpkg.com) and [Yarn 2+](https://yarnpkg.com).
|
||||
You can publish and install packages with [Yarn 1 (Classic)](https://classic.yarnpkg.com) and [Yarn 2+](https://yarnpkg.com).
|
||||
|
||||
To find the Yarn version used in the deployment container, run `yarn --version` in the `script` block of the CI
|
||||
To find the Yarn version used in the deployment container, run `yarn --version` in the `script` block of the CI/CD
|
||||
script job block that is responsible for calling `yarn publish`. The Yarn version is shown in the pipeline output.
|
||||
|
||||
Learn how to build a [Yarn](../workflows/build_packages.md#yarn) package.
|
||||
## Authenticating to the package registry
|
||||
|
||||
You can use the Yarn documentation to get started with
|
||||
[Yarn Classic](https://classic.yarnpkg.com/en/docs/getting-started) and
|
||||
[Yarn 2+](https://yarnpkg.com/getting-started).
|
||||
|
||||
## Publish to GitLab package registry
|
||||
|
||||
You can use Yarn to publish to the GitLab package registry.
|
||||
|
||||
### Authentication to the package registry
|
||||
|
||||
You need a token to publish a package. Different tokens are available depending on what you're trying to
|
||||
You need a token to interact with the package registry. Different tokens are available depending on what you're trying to
|
||||
achieve. For more information, review the [guidance on tokens](../package_registry/_index.md#authenticate-with-the-registry).
|
||||
|
||||
- If your organization uses two-factor authentication (2FA), you must use a
|
||||
personal access token with the scope set to `api`.
|
||||
- If you publish a package via CI/CD pipelines, you can use a CI job token in
|
||||
private runners or you can register a variable for instance runners.
|
||||
[personal access token](../../profile/personal_access_tokens.md) with the scope set to `api`.
|
||||
- If you publish a package with CI/CD pipelines, you can use a [CI/CD job token](../../../ci/jobs/ci_job_token.md) with
|
||||
private runners. You can also [register a variable](https://docs.gitlab.com/runner/register/#register-with-a-runner-authentication-token) for instance runners.
|
||||
|
||||
### Publish configuration
|
||||
### Configure Yarn for publication
|
||||
|
||||
To publish, set the following configuration in `.yarnrc.yml`. This file should be
|
||||
located in the root directory of your package project source where `package.json` is found.
|
||||
To configure Yarn to publish to the package registry, edit your `.yarnrc.yml` file.
|
||||
You can find this file in root directory of your project, in the same place as the `package.json` file.
|
||||
|
||||
```yaml
|
||||
npmScopes:
|
||||
<my-org>:
|
||||
npmPublishRegistry: 'https://<your_domain>/api/v4/projects/<your_project_id>/packages/npm/'
|
||||
npmAlwaysAuth: true
|
||||
npmAuthToken: '<your_token>'
|
||||
```
|
||||
- Edit `.yarnrc.yml` and add the following configuration:
|
||||
|
||||
In this configuration:
|
||||
```yaml
|
||||
npmScopes:
|
||||
<my-org>:
|
||||
npmPublishRegistry: 'https://<domain>/api/v4/projects/<project_id>/packages/npm/'
|
||||
npmAlwaysAuth: true
|
||||
npmAuthToken: '<token>'
|
||||
```
|
||||
|
||||
- Replace `<my-org>` with your organization scope, excluding the `@` symbol.
|
||||
- Replace `<your_domain>` with your domain name.
|
||||
- Replace `<your_project_id>` with your project's ID, which you can find on the [project overview page](../../project/working_with_projects.md#access-a-project-by-using-the-project-id).
|
||||
- Replace `<your_token>` with a deployment token, group access token, project access token, or personal access token.
|
||||
In this configuration:
|
||||
|
||||
Scoped registry does not work in Yarn Classic in `package.json` file, based on
|
||||
this [issue](https://github.com/yarnpkg/yarn/pull/7829).
|
||||
Therefore, under `publishConfig` there should be `registry` and not `@scope:registry` for Yarn Classic.
|
||||
You can publish using your command line or a CI/CD pipeline to the GitLab package registry.
|
||||
- Replace `<my-org>` with your organization scope. Do not include the `@` symbol.
|
||||
- Replace `<domain>` with your domain name.
|
||||
- Replace `<project_id>` with your project's ID, which you can find on the [project overview page](../../project/working_with_projects.md#access-a-project-by-using-the-project-id).
|
||||
- Replace `<token>` with a deployment token, group access token, project access token, or personal access token.
|
||||
|
||||
### Publishing via the command line - Manual Publish
|
||||
In Yarn Classic, scoped registries with `publishConfig["@scope:registry"]` are not supported. See [Yarn pull request 7829](https://github.com/yarnpkg/yarn/pull/7829) for more information.
|
||||
Instead, set `publishConfig` to `registry` in your `package.json` file.
|
||||
|
||||
```shell
|
||||
# Yarn 1 (Classic)
|
||||
yarn publish
|
||||
## Publish a package
|
||||
|
||||
# Yarn 2+
|
||||
yarn npm publish
|
||||
```
|
||||
You can publish a package from the command line, or with GitLab CI/CD.
|
||||
|
||||
Your package should now publish to the package registry.
|
||||
### With the command line
|
||||
|
||||
### Publishing via a CI/CD pipeline - Automated Publish
|
||||
To publish a package manually:
|
||||
|
||||
You can use pipeline variables when you use this method.
|
||||
- Run the following command:
|
||||
|
||||
You can use **instance runners** *(Default)* or **Private Runners** (Advanced).
|
||||
```shell
|
||||
# Yarn 1 (Classic)
|
||||
yarn publish
|
||||
|
||||
#### Instance runners
|
||||
# Yarn 2+
|
||||
yarn npm publish
|
||||
```
|
||||
|
||||
To create an authentication token for your project or group:
|
||||
### With CI/CD
|
||||
|
||||
1. On the left sidebar, select **Search or go to** and find your project or group.
|
||||
1. On the left sidebar, select **Settings > Repository > Deploy Tokens**.
|
||||
1. Create a deployment token with `read_package_registry` and `write_package_registry` scopes and copy the generated token.
|
||||
1. On the left sidebar, select **Settings > CI/CD > Variables**.
|
||||
1. Select `Add variable` and use the following settings:
|
||||
You can publish a package automatically with instance runners (default) or private runners (advanced).
|
||||
You can use pipeline variables when you publish with CI/CD.
|
||||
|
||||
| Field | Value |
|
||||
|--------------------|------------------------------|
|
||||
| key | `NPM_AUTH_TOKEN` |
|
||||
| value | `<DEPLOY-TOKEN-FROM-STEP-3>` |
|
||||
| type | Variable |
|
||||
| Protected variable | `CHECKED` |
|
||||
| Mask variable | `CHECKED` |
|
||||
| Expand variable | `CHECKED` |
|
||||
{{< tabs >}}
|
||||
|
||||
To use any **Protected variable**:
|
||||
{{< tab title="Instance runners" >}}
|
||||
|
||||
1. Create an authentication token for your project or group:
|
||||
|
||||
1. On the left sidebar, select **Search or go to** and find your project or group.
|
||||
1. On the left sidebar, select **Settings > Repository > Deploy Tokens**.
|
||||
1. Create a deployment token with `read_package_registry` and `write_package_registry` scopes and copy the generated token.
|
||||
1. On the left sidebar, select **Settings > CI/CD > Variables**.
|
||||
1. Select `Add variable` and use the following settings:
|
||||
|
||||
| Field | Value |
|
||||
|--------------------|------------------------------|
|
||||
| key | `NPM_AUTH_TOKEN` |
|
||||
| value | `<DEPLOY-TOKEN>` |
|
||||
| type | Variable |
|
||||
| Protected variable | `CHECKED` |
|
||||
| Mask variable | `CHECKED` |
|
||||
| Expand variable | `CHECKED` |
|
||||
|
||||
1. Optional. To use protected variables:
|
||||
|
||||
1. Go to the repository that contains the Yarn package source code.
|
||||
1. On the left sidebar, select **Settings > Repository**.
|
||||
- If you are building from branches with tags, select **Protected Tags** and add `v*` (wildcard) for semantic versioning.
|
||||
- If you are building from branches without tags, select **Protected Branches**.
|
||||
|
||||
Then add the `NPM_AUTH_TOKEN` created above, to the `.yarnrc.yml` configuration
|
||||
1. Add the `NPM_AUTH_TOKEN` you created to the `.yarnrc.yml` configuration
|
||||
in your package project root directory where `package.json` is found:
|
||||
|
||||
```yaml
|
||||
npmScopes:
|
||||
<my-org>:
|
||||
npmPublishRegistry: "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/npm/"
|
||||
npmAlwaysAuth: true
|
||||
npmAuthToken: "${NPM_AUTH_TOKEN}"
|
||||
```
|
||||
```yaml
|
||||
npmScopes:
|
||||
<my-org>:
|
||||
npmPublishRegistry: '${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/npm/'
|
||||
npmAlwaysAuth: true
|
||||
npmAuthToken: '${NPM_AUTH_TOKEN}'
|
||||
```
|
||||
|
||||
In this configuration, replace `<my-org>` with your organization scope, excluding the `@` symbol.
|
||||
In this configuration, replace `<my-org>` with your organization scope, excluding the `@` symbol.
|
||||
|
||||
#### Private runners
|
||||
{{< /tab >}}
|
||||
|
||||
Add the `CI_JOB_TOKEN` to the `.yarnrc.yml` configuration in your package project
|
||||
root directory where `package.json` is found:
|
||||
{{< tab title="Private runners" >}}
|
||||
|
||||
```yaml
|
||||
npmScopes:
|
||||
<my-org>:
|
||||
npmPublishRegistry: "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/npm/"
|
||||
npmAlwaysAuth: true
|
||||
npmAuthToken: "${CI_JOB_TOKEN}"
|
||||
```
|
||||
1. Add your `CI_JOB_TOKEN` to the `.yarnrc.yml` configuration in the root directory of your package project, where `package.json` is located:
|
||||
|
||||
In this configuration, replace `<my-org>` with your organization scope, excluding the `@` symbol.
|
||||
```yaml
|
||||
npmScopes:
|
||||
<my-org>:
|
||||
npmPublishRegistry: '${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/npm/'
|
||||
npmAlwaysAuth: true
|
||||
npmAuthToken: '${CI_JOB_TOKEN}'
|
||||
```
|
||||
|
||||
To publish the package using CI/CD pipeline, In the GitLab project that houses
|
||||
your `.yarnrc.yml`, edit or create a `.gitlab-ci.yml` file. For example to trigger
|
||||
only on any tag push:
|
||||
In this configuration, replace `<my-org>` with your organization scope, excluding the `@` symbol.
|
||||
|
||||
```yaml
|
||||
# Yarn 1
|
||||
image: node:lts
|
||||
1. In the GitLab project with your `.yarnrc.yml`, edit or create a `.gitlab-ci.yml` file.
|
||||
For example, to trigger only on any tag push:
|
||||
|
||||
stages:
|
||||
- deploy
|
||||
In Yarn 1:
|
||||
|
||||
```yaml
|
||||
image: node:lts
|
||||
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG
|
||||
stages:
|
||||
- deploy
|
||||
|
||||
deploy:
|
||||
stage: deploy
|
||||
script:
|
||||
- yarn publish
|
||||
```
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG
|
||||
|
||||
```yaml
|
||||
# Yarn 2+
|
||||
image: node:lts
|
||||
deploy:
|
||||
stage: deploy
|
||||
script:
|
||||
- yarn publish
|
||||
```
|
||||
|
||||
stages:
|
||||
- deploy
|
||||
In Yarn 2 and higher:
|
||||
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG
|
||||
```yaml
|
||||
image: node:lts
|
||||
|
||||
deploy:
|
||||
stage: deploy
|
||||
before_script:
|
||||
- corepack enable
|
||||
- yarn set version stable
|
||||
script:
|
||||
- yarn npm publish
|
||||
```
|
||||
stages:
|
||||
- deploy
|
||||
|
||||
Your package should now publish to the package registry when the pipeline runs.
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG
|
||||
|
||||
deploy:
|
||||
stage: deploy
|
||||
before_script:
|
||||
- corepack enable
|
||||
- yarn set version stable
|
||||
script:
|
||||
- yarn npm publish
|
||||
```
|
||||
|
||||
When the pipeline runs, your package is added to the package registry.
|
||||
|
||||
{{< /tab >}}
|
||||
|
||||
{{< /tabs >}}
|
||||
|
||||
## Install a package
|
||||
|
||||
{{< alert type="note" >}}
|
||||
You can install from an instance or project. If multiple packages have the same name and version,
|
||||
only the most recently published package is retrieved when you install a package.
|
||||
|
||||
If multiple packages have the same name and version, the most recently-published
|
||||
package is retrieved when you install a package.
|
||||
### Scoped package names
|
||||
|
||||
{{< /alert >}}
|
||||
To install from an instance, a package must be named with a [scope](https://docs.npmjs.com/misc/scope/).
|
||||
You can set up the scope for your package in the `.yarnrc.yml` file and with the `publishConfig` option in the `package.json`.
|
||||
You don't need to follow package naming conventions if you install from a project or group.
|
||||
|
||||
You can use one of two API endpoints to install packages:
|
||||
A package scope begins with a `@` and follows the format `@owner/package-name`:
|
||||
|
||||
- **Instance-level**: Best used when working with many packages in an organization scope.
|
||||
- The `@owner` is the top-level project that hosts the packages, not the root of the project with the package source code.
|
||||
- The package name can be anything.
|
||||
|
||||
- If you plan to install a package through the [instance level](#install-from-the-instance-level),
|
||||
then you must name your package with a [scope](https://docs.npmjs.com/misc/scope/).
|
||||
Scoped packages begin with a `@` and have the `@owner/package-name` format. You can set up
|
||||
the scope for your package in the `.yarnrc.yml` file and by using the `publishConfig`
|
||||
option in the `package.json`.
|
||||
For example:
|
||||
|
||||
- The value used for the `@scope` is the organization root (top-level project) `...com/my-org`
|
||||
*(@my-org)* that hosts the packages, not the root of the project with the package's source code.
|
||||
- The scope is always lowercase.
|
||||
- The package name can be anything you want `@my-org/any-name`.
|
||||
|
||||
- **Project-level**: For when you have a one-off package.
|
||||
|
||||
If you plan to install a package through the [project level](#install-from-the-project-level),
|
||||
you do not have to adhere to the naming convention.
|
||||
|
||||
| Project URL | Package registry | Organization Scope | Full package name |
|
||||
| Project URL | Package registry | Organization scope | Full package name |
|
||||
|-------------------------------------------------------------------|----------------------|--------------------|-----------------------------|
|
||||
| `https://gitlab.com/<my-org>/<group-name>/<package-name-example>` | Package Name Example | `@my-org` | `@my-org/package-name` |
|
||||
| `https://gitlab.com/<example-org>/<group-name>/<project-name>` | Project Name | `@example-org` | `@example-org/project-name` |
|
||||
|
||||
You can install from the instance level or from the project level.
|
||||
### Install from the instance
|
||||
|
||||
The configurations for `.yarnrc.yml` can be added per package consuming project
|
||||
root where `package.json` is located, or you can use a global
|
||||
configuration located in your system user home directory.
|
||||
If you're working with many packages in the same organization scope, consider installing from the instance.
|
||||
|
||||
### Install from the instance level
|
||||
1. Configure your organization scope. In your `.yarnrc.yml` file, add the following:
|
||||
|
||||
Use these steps for global configuration in the `.yarnrc.yml` file:
|
||||
```yaml
|
||||
npmScopes:
|
||||
<my-org>:
|
||||
npmRegistryServer: 'https://<domain_name>/api/v4/packages/npm'
|
||||
```
|
||||
|
||||
1. [Configure organization scope](#configure-organization-scope).
|
||||
1. [Set the registry](#set-the-registry).
|
||||
- Replace `<my-org>` with the root level group of the project you're installing to the package from excluding the `@` symbol.
|
||||
- Replace `<domain_name>` with your domain name, for example, `gitlab.com`.
|
||||
|
||||
#### Configure organization scope
|
||||
1. Optional. If your package is private, you must configure access to the package registry:
|
||||
|
||||
```yaml
|
||||
npmScopes:
|
||||
<my-org>:
|
||||
npmRegistryServer: "https://<your_domain_name>/api/v4/packages/npm"
|
||||
```
|
||||
```yaml
|
||||
npmRegistries:
|
||||
//<domain_name>/api/v4/packages/npm:
|
||||
npmAlwaysAuth: true
|
||||
npmAuthToken: '<token>'
|
||||
```
|
||||
|
||||
- Replace `<my-org>` with the root level group of the project you're installing to the package from excluding the `@` symbol.
|
||||
- Replace `<your_domain_name>` with your domain name, for example, `gitlab.com`.
|
||||
- Replace `<domain_name>` with your domain name, for example, `gitlab.com`.
|
||||
- Replace `<token>` with a deployment token (recommended), group access token, project access token, or personal access token.
|
||||
|
||||
#### Set the registry
|
||||
1. [Install the package with Yarn](#install-with-yarn).
|
||||
|
||||
Skip this step if your package is public not private.
|
||||
### Install from a group or project
|
||||
|
||||
```yaml
|
||||
npmRegistries:
|
||||
//<your_domain_name>/api/v4/packages/npm:
|
||||
npmAlwaysAuth: true
|
||||
npmAuthToken: "<your_token>"
|
||||
```
|
||||
If you have a one-off package, you can install it from a group or project.
|
||||
|
||||
- Replace `<your_domain_name>` with your domain name, for example, `gitlab.com`.
|
||||
- Replace `<your_token>` with a deployment token (recommended), group access token, project access token, or personal access token.
|
||||
{{< tabs >}}
|
||||
|
||||
### Install from the group level
|
||||
{{< tab title="From a group" >}}
|
||||
|
||||
Use these steps for global configuration in the `.yarnrc.yml` file:
|
||||
1. Configure the group scope. In your `.yarnrc.yml` file, add the following:
|
||||
|
||||
1. [Configure group scope](#configure-group-scope)
|
||||
1. [Set the registry](#set-the-registry-group-level)
|
||||
```yaml
|
||||
npmScopes:
|
||||
<my-org>:
|
||||
npmRegistryServer: 'https://<domain_name>/api/v4/groups/<group_id>/-/packages/npm'
|
||||
```
|
||||
|
||||
#### Configure group scope
|
||||
- Replace `<my-org>` with the top-level group that contains the group you want to install from. Exclude the `@` symbol.
|
||||
- Replace `<domain_name>` with your domain name, for example, `gitlab.com`.
|
||||
- Replace `<group_id>` with your group ID, found on the [group overview page](../../group#access-a-group-by-using-the-group-id).
|
||||
|
||||
```yaml
|
||||
npmScopes:
|
||||
<my-org>:
|
||||
npmRegistryServer: "https://<your_domain_name>/api/v4/groups/<your_group_id>/-/packages/npm"
|
||||
```
|
||||
1. Optional. If your package is private, you must set the registry:
|
||||
|
||||
- Replace `<my-org>` with the root level group of the project you're installing to the package from excluding the `@` symbol.
|
||||
- Replace `<your_domain_name>` with your domain name, for example, `gitlab.com`.
|
||||
- Replace `<your_group_id>` with your group ID, found on the [group overview page](../../group#access-a-group-by-using-the-group-id).
|
||||
```yaml
|
||||
npmRegistries:
|
||||
//<domain_name>/api/v4/groups/<group_id>/-/packages/npm:
|
||||
npmAlwaysAuth: true
|
||||
npmAuthToken: "<token>"
|
||||
```
|
||||
|
||||
#### Set the registry (group level)
|
||||
- Replace `<domain_name>` with your domain name, for example, `gitlab.com`.
|
||||
- Replace `<token>` with a deployment token (recommended), group access token, project access token, or personal access token.
|
||||
- Replace `<group_id>` with your group ID, found on the [group overview page](../../group#access-a-group-by-using-the-group-id).
|
||||
|
||||
```yaml
|
||||
npmRegistries:
|
||||
//<your_domain_name>/api/v4/groups/<your_group_id>/-/packages/npm:
|
||||
npmAlwaysAuth: true
|
||||
npmAuthToken: "<your_token>"
|
||||
```
|
||||
1. [Install the package with Yarn](#install-with-yarn).
|
||||
|
||||
- Replace `<my-org>` with the root level group of the project you're installing to the package from excluding the `@` symbol.
|
||||
- Replace `<your_domain_name>` with your domain name, for example, `gitlab.com`.
|
||||
- Replace `<your_group_id>` with your group ID, found on the [group overview page](../../group#access-a-group-by-using-the-group-id).
|
||||
{{< /tab >}}
|
||||
|
||||
### Install from the project level
|
||||
{{< tab title="From a project" >}}
|
||||
|
||||
Use these steps for each project in the `.yarnrc.yml` file:
|
||||
1. Configure the project scope. In your `.yarnrc.yml` file, add the following:
|
||||
|
||||
1. [Configure project scope](#configure-project-scope).
|
||||
1. [Set the registry](#set-the-registry-project-level).
|
||||
```yaml
|
||||
npmScopes:
|
||||
<my-org>:
|
||||
npmRegistryServer: "https://<domain_name>/api/v4/projects/<project_id>/packages/npm"
|
||||
```
|
||||
|
||||
#### Configure project scope
|
||||
- Replace `<my-org>` with the top-level group that contains the project you want to install from. Exclude the `@` symbol.
|
||||
- Replace `<domain_name>` with your domain name, for example, `gitlab.com`.
|
||||
- Replace `<project_id>` with your project ID, found on the [project overview page](../../project/working_with_projects.md#access-a-project-by-using-the-project-id).
|
||||
|
||||
```yaml
|
||||
npmScopes:
|
||||
<my-org>:
|
||||
npmRegistryServer: "https://<your_domain_name>/api/v4/projects/<your_project_id>/packages/npm"
|
||||
```
|
||||
1. Optional. If your package is private, you must set the registry:
|
||||
|
||||
- Replace `<my-org>` with the root level group of the project you're installing to the package from excluding the `@` symbol.
|
||||
- Replace `<your_domain_name>` with your domain name, for example, `gitlab.com`.
|
||||
- Replace `<your_project_id>` with your project ID, found on the [project overview page](../../project/working_with_projects.md#access-a-project-by-using-the-project-id).
|
||||
```yaml
|
||||
npmRegistries:
|
||||
//<domain_name>/api/v4/projects/<project_id>/packages/npm:
|
||||
npmAlwaysAuth: true
|
||||
npmAuthToken: "<token>"
|
||||
```
|
||||
|
||||
#### Set the registry (project level)
|
||||
- Replace `<domain_name>` with your domain name, for example, `gitlab.com`.
|
||||
- Replace `<token>` with a deployment token (recommended), group access token, project access token, or personal access token.
|
||||
- Replace `<project_id>` with your project ID, found on the [project overview page](../../project/working_with_projects.md#access-a-project-by-using-the-project-id).
|
||||
|
||||
Skip this step if your package is public not private.
|
||||
1. [Install the package with Yarn](#install-with-yarn).
|
||||
|
||||
```yaml
|
||||
npmRegistries:
|
||||
//<your_domain_name>/api/v4/projects/<your_project_id>/packages/npm:
|
||||
npmAlwaysAuth: true
|
||||
npmAuthToken: "<your_token>"
|
||||
```
|
||||
{{< /tab >}}
|
||||
|
||||
- Replace `<your_domain_name>` with your domain name, for example, `gitlab.com`.
|
||||
- Replace `<your_token>` with a deployment token (recommended), group access token, project access token, or personal access token.
|
||||
- Replace `<your_project_id>` with your project ID, found on the [project overview page](../../project/working_with_projects.md#access-a-project-by-using-the-project-id).
|
||||
{{< /tabs >}}
|
||||
|
||||
### Install the package
|
||||
### Install with Yarn
|
||||
|
||||
For Yarn 2+, use `yarn add` either in the command line or in the CI/CD pipelines to install your packages:
|
||||
{{< tabs >}}
|
||||
|
||||
{{< tab title="Yarn 2 or later" >}}
|
||||
|
||||
- Run `yarn add` either from the command line, or from a CI/CD pipeline:
|
||||
|
||||
```shell
|
||||
yarn add @scope/my-package
|
||||
```
|
||||
|
||||
#### For Yarn Classic
|
||||
{{< /tab >}}
|
||||
|
||||
The Yarn Classic setup, requires both `.npmrc` and `.yarnrc` files as
|
||||
[mentioned in issue](https://github.com/yarnpkg/yarn/issues/4451#issuecomment-753670295):
|
||||
{{< tab title="Yarn Classic" >}}
|
||||
|
||||
- Place credentials in the `.npmrc` file.
|
||||
- Place the scoped registry in the `.yarnrc` file.
|
||||
Yarn Classic requires both a `.npmrc` and a `.yarnrc` file.
|
||||
See [Yarn issue 4451](https://github.com/yarnpkg/yarn/issues/4451#issuecomment-753670295) for more information.
|
||||
|
||||
```shell
|
||||
# .npmrc
|
||||
## Instance level
|
||||
//<your_domain_name>/api/v4/packages/npm/:_authToken="<your_token>"
|
||||
## Group level
|
||||
//<your_domain_name>/api/v4/groups/<your_group_id>/-/packages/npm/:_authToken="<your_token>"
|
||||
## Project level
|
||||
//<your_domain_name>/api/v4/projects/<your_project_id>/packages/npm/:_authToken="<your_token>"
|
||||
1. Place your credentials in the `.npmrc` file, and the scoped registry in the `.yarnrc` file:
|
||||
|
||||
# .yarnrc
|
||||
## Instance level
|
||||
"@scope:registry" "https://<your_domain_name>/api/v4/packages/npm/"
|
||||
## Group level
|
||||
"@scope:registry" "https://<your_domain_name>/api/v4/groups/<your_group_id>/-/packages/npm/"
|
||||
## Project level
|
||||
"@scope:registry" "https://<your_domain_name>/api/v4/projects/<your_project_id>/packages/npm/"
|
||||
```
|
||||
```shell
|
||||
# .npmrc
|
||||
## For the instance
|
||||
//<domain_name>/api/v4/packages/npm/:_authToken='<token>'
|
||||
## For the group
|
||||
//<domain_name>/api/v4/groups/<group_id>/-/packages/npm/:_authToken='<token>'
|
||||
## For the project
|
||||
//<domain_name>/api/v4/projects/<project_id>/packages/npm/:_authToken='<token>'
|
||||
|
||||
Then you can use `yarn add` to install your packages.
|
||||
# .yarnrc
|
||||
## For the instance
|
||||
'@scope:registry' 'https://<domain_name>/api/v4/packages/npm/'
|
||||
## For the group
|
||||
'@scope:registry' 'https://<domain_name>/api/v4/groups/<group_id>/-/packages/npm/'
|
||||
## For the project
|
||||
'@scope:registry' 'https://<domain_name>/api/v4/projects/<project_id>/packages/npm/'
|
||||
```
|
||||
|
||||
1. Run `yarn add` either from the command line, or from a CI/CD pipeline:
|
||||
|
||||
```shell
|
||||
yarn add @scope/my-package
|
||||
```
|
||||
|
||||
{{< /tab >}}
|
||||
|
||||
{{< /tabs >}}
|
||||
|
||||
## Related topics
|
||||
|
||||
- [npm documentation](../npm_registry/_index.md#helpful-hints)
|
||||
- [npm package registry documentation](../npm_registry/_index.md#helpful-hints)
|
||||
- [Yarn Migration Guide](https://yarnpkg.com/migration/guide)
|
||||
- [Build a Yarn package](../workflows/build_packages.md#yarn)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
@ -365,11 +366,11 @@ info If you think this is a bug, please open a bug report with the information p
|
||||
info Visit https://classic.yarnpkg.com/en/docs/cli/install for documentation about this command
|
||||
```
|
||||
|
||||
In this case, the following commands creates a file called `.yarnrc` in the current directory. Make sure to be in either your user home directory for global configuration or your project root for per-project configuration:
|
||||
In this case, the following commands create a file called `.yarnrc` in the current directory. Make sure to be in either your user home directory for global configuration or your project root for per-project configuration:
|
||||
|
||||
```shell
|
||||
yarn config set '//gitlab.example.com/api/v4/projects/<your_project_id>/packages/npm/:_authToken' "<your_token>"
|
||||
yarn config set '//gitlab.example.com/api/v4/packages/npm/:_authToken' "<your_token>"
|
||||
yarn config set '//gitlab.example.com/api/v4/projects/<project_id>/packages/npm/:_authToken' '<token>'
|
||||
yarn config set '//gitlab.example.com/api/v4/packages/npm/:_authToken' '<token>'
|
||||
```
|
||||
|
||||
### `yarn install` fails to clone repository as a dependency
|
||||
|
@ -9693,9 +9693,6 @@ msgstr ""
|
||||
msgid "Billing|User successfully scheduled for removal. This process might take some time. Refresh the page to see the changes."
|
||||
msgstr ""
|
||||
|
||||
msgid "Billing|User was successfully removed"
|
||||
msgstr ""
|
||||
|
||||
msgid "Billing|You are about to remove user %{username} from your subscription. If you continue, the user will be removed from the %{namespace} group and all its subgroups and projects. This action can't be undone."
|
||||
msgstr ""
|
||||
|
||||
@ -19742,6 +19739,9 @@ msgstr ""
|
||||
msgid "Dependency list"
|
||||
msgstr ""
|
||||
|
||||
msgid "DependencyListExport|License Identifiers"
|
||||
msgstr ""
|
||||
|
||||
msgid "DependencyListExport|Location"
|
||||
msgstr ""
|
||||
|
||||
@ -19751,9 +19751,18 @@ msgstr ""
|
||||
msgid "DependencyListExport|Packager"
|
||||
msgstr ""
|
||||
|
||||
msgid "DependencyListExport|Project"
|
||||
msgstr ""
|
||||
|
||||
msgid "DependencyListExport|Version"
|
||||
msgstr ""
|
||||
|
||||
msgid "DependencyListExport|Vulnerabilities Detected"
|
||||
msgstr ""
|
||||
|
||||
msgid "DependencyListExport|Vulnerability IDs"
|
||||
msgstr ""
|
||||
|
||||
msgid "DependencyProxy|%{docLinkStart}See the documentation%{docLinkEnd} for other ways to store Docker images in Dependency Proxy cache."
|
||||
msgstr ""
|
||||
|
||||
@ -64399,7 +64408,7 @@ msgstr ""
|
||||
msgid "Vulnerability|The CVSS (Common Vulnerability Scoring System) is a standardized framework for assessing and communicating the severity of security vulnerabilities in software. It provides a numerical score (ranging from 0.0 to 10.0) to indicate the severity risk of the vulnerability."
|
||||
msgstr ""
|
||||
|
||||
msgid "Vulnerability|The Exploit Prediction Scoring System model produces a probability score between 0 and 1 indicating the likelihood that a vulnerability will be exploited in the next 30 days."
|
||||
msgid "Vulnerability|The Exploit Prediction Scoring System model produces a percentage value between 0 and 100 that represents the likelihood that a vulnerability will be exploited in the next 30 days."
|
||||
msgstr ""
|
||||
|
||||
msgid "Vulnerability|The scanner determined this vulnerability to be a false positive. Verify the evaluation before changing its status. %{linkStart}Learn more about false positive detection.%{linkEnd}"
|
||||
|
@ -115,7 +115,7 @@ module QA
|
||||
|
||||
def runner_auth_token
|
||||
runner_list = shell("docker exec #{@name} sh -c 'gitlab-runner list'")
|
||||
runner_list.match(/Token\e\[0;m=([a-zA-Z0-9_-]+)/i)&.[](1)
|
||||
runner_list.match(/Token\e\[0;m=([^ ]+)/)&.[](1)
|
||||
end
|
||||
|
||||
def unregister_command
|
||||
|
@ -53,7 +53,7 @@ RSpec.describe QA::Page::Element do
|
||||
subject { described_class.new(:something) }
|
||||
|
||||
it 'has no attribute[pattern]' do
|
||||
expect(subject.attributes[:pattern]).to be(nil)
|
||||
expect(subject.attributes[:pattern]).to be_nil
|
||||
end
|
||||
|
||||
it 'is not required by default' do
|
||||
|
@ -52,7 +52,7 @@ module QA
|
||||
end
|
||||
|
||||
it 'resolving the registry returns nil' do
|
||||
expect(service.third_party_registry).to be(nil)
|
||||
expect(service.third_party_registry).to be_nil
|
||||
end
|
||||
|
||||
it 'throws if environment is missing' do
|
||||
|
@ -40,7 +40,7 @@ module QA
|
||||
expect(wait_thread).to receive(:value).twice.and_return(non_errored_wait)
|
||||
|
||||
subject.shell('docker login -u user -p secret', mask_secrets: %w[secret user]) do |output|
|
||||
expect(output).not_to be(nil)
|
||||
expect(output).not_to be_nil
|
||||
expect(output).to eql('logged in as **** with password ****')
|
||||
end
|
||||
end
|
||||
|
@ -30,7 +30,6 @@ ee/spec/frontend/boards/components/epic_board_content_sidebar_spec.js
|
||||
ee/spec/frontend/boards/components/epics_swimlanes_spec.js
|
||||
ee/spec/frontend/ci/pipeline_details/header/pipeline_header_spec.js
|
||||
ee/spec/frontend/ci/runner/components/runner_usage_spec.js
|
||||
ee/spec/frontend/ci/secrets/components/secrets_app_spec.js
|
||||
ee/spec/frontend/ci/secrets/components/secrets_breadcrumbs_spec.js
|
||||
ee/spec/frontend/ci/secrets/router_spec.js
|
||||
ee/spec/frontend/compliance_dashboard/components/frameworks_report/edit_framework/components/policies_section_spec.js
|
||||
|
@ -331,8 +331,8 @@ RSpec.describe ObjectStoreSettings, feature_category: :shared do
|
||||
|
||||
expect(settings['enabled']).to be false
|
||||
expect(settings['direct_upload']).to be true
|
||||
expect(settings['remote_directory']).to be nil
|
||||
expect(settings['bucket_prefix']).to be nil
|
||||
expect(settings['remote_directory']).to be_nil
|
||||
expect(settings['bucket_prefix']).to be_nil
|
||||
end
|
||||
|
||||
it 'respects original values' do
|
||||
@ -346,7 +346,7 @@ RSpec.describe ObjectStoreSettings, feature_category: :shared do
|
||||
expect(settings['enabled']).to be true
|
||||
expect(settings['direct_upload']).to be true
|
||||
expect(settings['remote_directory']).to eq 'artifacts'
|
||||
expect(settings['bucket_prefix']).to be nil
|
||||
expect(settings['bucket_prefix']).to be_nil
|
||||
end
|
||||
|
||||
it 'supports bucket prefixes' do
|
||||
|
@ -775,7 +775,7 @@ RSpec.describe ApplicationController, feature_category: :shared do
|
||||
it 'sets stream headers', :aggregate_failures do
|
||||
subject
|
||||
|
||||
expect(response.headers['Content-Length']).to be nil
|
||||
expect(response.headers['Content-Length']).to be_nil
|
||||
expect(response.headers['X-Accel-Buffering']).to eq 'no'
|
||||
expect(response.headers['Last-Modified']).to eq '0'
|
||||
end
|
||||
|
@ -63,7 +63,7 @@ RSpec.describe 'Database schema',
|
||||
abuse_reports: %w[reporter_id user_id],
|
||||
abuse_report_notes: %w[discussion_id],
|
||||
ai_code_suggestion_events: %w[user_id],
|
||||
ai_duo_chat_events: %w[user_id],
|
||||
ai_duo_chat_events: %w[user_id organization_id],
|
||||
application_settings: %w[performance_bar_allowed_group_id slack_app_id snowplow_app_id eks_account_id
|
||||
eks_access_key_id],
|
||||
approvals: %w[user_id project_id],
|
||||
|
@ -104,7 +104,7 @@ end
|
||||
|
||||
RSpec.shared_examples 'default branch pipeline' do
|
||||
it 'is valid' do
|
||||
expect(pipeline.yaml_errors).to be nil
|
||||
expect(pipeline.yaml_errors).to be_nil
|
||||
expect(pipeline.errors).to be_empty
|
||||
expect(pipeline.status).to eq('created')
|
||||
expect(jobs).to include(expected_job_name)
|
||||
@ -113,7 +113,7 @@ end
|
||||
|
||||
RSpec.shared_examples 'merge request pipeline' do
|
||||
it "succeeds with expected job" do
|
||||
expect(pipeline.yaml_errors).to be nil
|
||||
expect(pipeline.yaml_errors).to be_nil
|
||||
expect(pipeline.errors).to be_empty
|
||||
expect(pipeline.status).to eq('created')
|
||||
expect(jobs).to include(expected_job_name)
|
||||
@ -124,7 +124,7 @@ RSpec.shared_examples 'merge train pipeline' do
|
||||
let(:ci_merge_request_event_type) { 'merge_train' }
|
||||
|
||||
it "succeeds with expected job" do
|
||||
expect(pipeline.yaml_errors).to be nil
|
||||
expect(pipeline.yaml_errors).to be_nil
|
||||
expect(pipeline.errors).to be_empty
|
||||
expect(pipeline.status).to eq('created')
|
||||
expect(jobs).to include('pre-merge-checks')
|
||||
|
@ -135,7 +135,7 @@ RSpec.describe 'Admin impersonates user', feature_category: :user_management do
|
||||
subject
|
||||
|
||||
icon = first('[data-testid="incognito-icon"]')
|
||||
expect(icon).not_to be nil
|
||||
expect(icon).not_to be_nil
|
||||
end
|
||||
|
||||
context 'when viewing the confirm email warning', :js do
|
||||
|
@ -102,7 +102,7 @@ RSpec.describe ContainerRepositoriesFinder do
|
||||
project.project_feature.update!(container_registry_access_level: ProjectFeature::DISABLED)
|
||||
end
|
||||
|
||||
it { is_expected.to be nil }
|
||||
it { is_expected.to be_nil }
|
||||
end
|
||||
end
|
||||
|
||||
@ -119,13 +119,13 @@ RSpec.describe ContainerRepositoriesFinder do
|
||||
context 'when subject_type is group' do
|
||||
let(:subject_type) { group }
|
||||
|
||||
it { is_expected.to be nil }
|
||||
it { is_expected.to be_nil }
|
||||
end
|
||||
|
||||
context 'when subject_type is project' do
|
||||
let(:subject_type) { project }
|
||||
|
||||
it { is_expected.to be nil }
|
||||
it { is_expected.to be_nil }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -38,7 +38,7 @@ RSpec.describe UploaderFinder, feature_category: :shared do
|
||||
end
|
||||
|
||||
it 'returns nil' do
|
||||
expect(subject).to be(nil)
|
||||
expect(subject).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1,8 +1,10 @@
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import { nextTick } from 'vue';
|
||||
import { GlFilteredSearch } from '@gitlab/ui';
|
||||
import CredentialsFilterApp from '~/credentials/components/credentials_filter_app.vue';
|
||||
import { GlFilteredSearch, GlSorting } from '@gitlab/ui';
|
||||
import CredentialsFilterSortApp from '~/credentials/components/credentials_filter_sort_app.vue';
|
||||
import { visitUrl, getBaseURL } from '~/lib/utils/url_utility';
|
||||
import setWindowLocation from 'helpers/set_window_location_helper';
|
||||
import { SORT_KEY_NAME } from '~/credentials/constants';
|
||||
|
||||
const mockFilters = [
|
||||
'dummy',
|
||||
@ -35,15 +37,28 @@ jest.mock('~/lib/utils/url_utility', () => {
|
||||
};
|
||||
});
|
||||
|
||||
describe('CredentialsFilterApp', () => {
|
||||
describe('CredentialsFilterSortApp', () => {
|
||||
let wrapper;
|
||||
const URL_HOST = 'https://localhost/';
|
||||
|
||||
const createComponent = () => {
|
||||
wrapper = shallowMount(CredentialsFilterApp);
|
||||
wrapper = mount(CredentialsFilterSortApp, {
|
||||
stubs: {
|
||||
GlFilteredSearch: true,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
setWindowLocation(URL_HOST);
|
||||
});
|
||||
|
||||
const findFilteredSearch = () => wrapper.findComponent(GlFilteredSearch);
|
||||
const findAvailableTokens = () => findFilteredSearch().props('availableTokens');
|
||||
const findSortingComponent = () => wrapper.findComponent(GlSorting);
|
||||
const findSortDirectionToggle = () =>
|
||||
findSortingComponent().find('button[title^="Sort direction"]');
|
||||
const findDropdownToggle = () => findSortingComponent().find('button[aria-haspopup="listbox"]');
|
||||
|
||||
describe('Mounts GlFilteredSearch with corresponding filters', () => {
|
||||
it.each`
|
||||
@ -126,4 +141,92 @@ describe('CredentialsFilterApp', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('renders CredentialsSortApp component', () => {
|
||||
it('when url has filter param with value personal_access_tokens', async () => {
|
||||
setWindowLocation('?filter=personal_access_tokens');
|
||||
createComponent();
|
||||
await nextTick();
|
||||
|
||||
expect(findSortingComponent().exists()).toBe(true);
|
||||
});
|
||||
it('when url has no filter param', async () => {
|
||||
createComponent();
|
||||
await nextTick();
|
||||
|
||||
expect(findSortingComponent().exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sort dropdown', () => {
|
||||
it('defaults to sorting by "Created date" in ascending order', async () => {
|
||||
createComponent();
|
||||
await nextTick();
|
||||
expect(findSortingComponent().props('isAscending')).toBe(true);
|
||||
expect(findDropdownToggle().text()).toBe('Expiration date');
|
||||
});
|
||||
|
||||
it('sets the sort label correctly', () => {
|
||||
setWindowLocation('?sort=name_asc');
|
||||
|
||||
createComponent();
|
||||
|
||||
expect(findDropdownToggle().text()).toBe('Name');
|
||||
});
|
||||
|
||||
describe('new sort option is selected', () => {
|
||||
beforeEach(async () => {
|
||||
visitUrl.mockImplementation(() => {});
|
||||
createComponent();
|
||||
|
||||
findSortingComponent().vm.$emit('sortByChange', SORT_KEY_NAME);
|
||||
await nextTick();
|
||||
});
|
||||
|
||||
it('sorts by new option', () => {
|
||||
expect(visitUrl).toHaveBeenCalledWith(`${URL_HOST}?sort=name_asc`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('sort direction toggle', () => {
|
||||
beforeEach(() => {
|
||||
visitUrl.mockImplementation(() => {});
|
||||
});
|
||||
|
||||
describe('when current sort direction is ascending', () => {
|
||||
beforeEach(() => {
|
||||
setWindowLocation('?sort=name_asc');
|
||||
|
||||
createComponent();
|
||||
});
|
||||
|
||||
describe('when sort direction toggle is clicked', () => {
|
||||
beforeEach(() => {
|
||||
findSortDirectionToggle().trigger('click');
|
||||
});
|
||||
|
||||
it('sorts in descending order', () => {
|
||||
expect(visitUrl).toHaveBeenCalledWith(`${URL_HOST}?sort=name_desc`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when current sort direction is descending', () => {
|
||||
beforeEach(() => {
|
||||
setWindowLocation('?sort=name_desc');
|
||||
|
||||
createComponent();
|
||||
});
|
||||
|
||||
describe('when sort direction toggle is clicked', () => {
|
||||
beforeEach(() => {
|
||||
findSortDirectionToggle().trigger('click');
|
||||
});
|
||||
|
||||
it('sorts in ascending order', () => {
|
||||
expect(visitUrl).toHaveBeenCalledWith(`${URL_HOST}?sort=name_asc`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
11
spec/frontend/credentials/utils_spec.js
Normal file
11
spec/frontend/credentials/utils_spec.js
Normal file
@ -0,0 +1,11 @@
|
||||
import { buildSortedUrl } from '~/credentials/utils';
|
||||
|
||||
describe('buildSortedUrl', () => {
|
||||
it('builds correct URL for ascending sort', () => {
|
||||
expect(buildSortedUrl('name', false)).toBe('http://test.host/?sort=name_desc');
|
||||
});
|
||||
|
||||
it('builds correct URL for descending sort', () => {
|
||||
expect(buildSortedUrl('created', true)).toBe('http://test.host/?sort=created_asc');
|
||||
});
|
||||
});
|
@ -6,10 +6,10 @@ import { DiffFile } from '~/rapid_diffs/diff_file';
|
||||
import { DiffFileMounted } from '~/rapid_diffs/diff_file_mounted';
|
||||
import { useDiffsList } from '~/rapid_diffs/stores/diffs_list';
|
||||
import { pinia } from '~/pinia/instance';
|
||||
import { initFileBrowser } from '~/rapid_diffs/app/file_browser';
|
||||
import { initFileBrowser } from '~/rapid_diffs/app/init_file_browser';
|
||||
|
||||
jest.mock('~/rapid_diffs/app/view_settings');
|
||||
jest.mock('~/rapid_diffs/app/file_browser');
|
||||
jest.mock('~/rapid_diffs/app/init_file_browser');
|
||||
|
||||
describe('Rapid Diffs App', () => {
|
||||
let app;
|
||||
|
53
spec/frontend/rapid_diffs/app/file_browser_spec.js
Normal file
53
spec/frontend/rapid_diffs/app/file_browser_spec.js
Normal file
@ -0,0 +1,53 @@
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import FileBrowser from '~/rapid_diffs/app/file_browser.vue';
|
||||
import DiffsFileTree from '~/diffs/components/diffs_file_tree.vue';
|
||||
import store from '~/mr_notes/stores';
|
||||
import * as types from '~/diffs/store/mutation_types';
|
||||
|
||||
describe('FileBrowser', () => {
|
||||
let wrapper;
|
||||
let commit;
|
||||
|
||||
const createComponent = ({ loadedFiles = {}, ...rest } = {}) => {
|
||||
wrapper = shallowMount(FileBrowser, {
|
||||
store,
|
||||
propsData: {
|
||||
loadedFiles,
|
||||
...rest,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
commit = jest.spyOn(store, 'commit');
|
||||
});
|
||||
|
||||
it('passes down loaded files', () => {
|
||||
const loadedFiles = { foo: 1 };
|
||||
createComponent({ loadedFiles });
|
||||
expect(wrapper.findComponent(DiffsFileTree).props('loadedFiles')).toStrictEqual(loadedFiles);
|
||||
});
|
||||
|
||||
it('is visible by default', () => {
|
||||
createComponent();
|
||||
expect(wrapper.findComponent(DiffsFileTree).props('visible')).toBe(true);
|
||||
});
|
||||
|
||||
it('toggles visibility', async () => {
|
||||
createComponent();
|
||||
await wrapper.findComponent(DiffsFileTree).vm.$emit('toggled');
|
||||
expect(wrapper.findComponent(DiffsFileTree).props('visible')).toBe(false);
|
||||
});
|
||||
|
||||
it('handles click', async () => {
|
||||
const file = { fileHash: 'foo' };
|
||||
createComponent();
|
||||
await wrapper.findComponent(DiffsFileTree).vm.$emit('clickFile', file);
|
||||
expect(wrapper.emitted('clickFile')).toStrictEqual([[file]]);
|
||||
expect(commit).toHaveBeenCalledWith(
|
||||
`diffs/${types.SET_CURRENT_DIFF_FILE}`,
|
||||
file.fileHash,
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
});
|
75
spec/frontend/rapid_diffs/app/init_file_browser_spec.js
Normal file
75
spec/frontend/rapid_diffs/app/init_file_browser_spec.js
Normal file
@ -0,0 +1,75 @@
|
||||
import { resetHTMLFixture, setHTMLFixture } from 'helpers/fixtures';
|
||||
import store from '~/mr_notes/stores';
|
||||
import { initFileBrowser } from '~/rapid_diffs/app/init_file_browser';
|
||||
import createEventHub from '~/helpers/event_hub_factory';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import { DiffFile } from '~/rapid_diffs/diff_file';
|
||||
|
||||
jest.mock('~/rapid_diffs/app/file_browser.vue', () => ({
|
||||
props: jest.requireActual('~/rapid_diffs/app/file_browser.vue').default.props,
|
||||
render(h) {
|
||||
return h('div', {
|
||||
attrs: {
|
||||
'data-file-browser-component': true,
|
||||
'data-loaded-files': JSON.stringify(this.loadedFiles),
|
||||
},
|
||||
on: {
|
||||
click: () => {
|
||||
this.$emit('clickFile', { fileHash: 'first' });
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
}));
|
||||
|
||||
describe('Init file browser', () => {
|
||||
let dispatch;
|
||||
|
||||
const getMountElement = () => document.querySelector('[data-file-browser]');
|
||||
const getFileBrowser = () => document.querySelector('[data-file-browser-component]');
|
||||
|
||||
beforeEach(() => {
|
||||
dispatch = jest.spyOn(store, 'dispatch').mockResolvedValue();
|
||||
window.mrTabs = { eventHub: createEventHub() };
|
||||
setHTMLFixture(
|
||||
`
|
||||
<div data-file-browser data-metadata-endpoint="/metadata"></div>
|
||||
<diff-file id="first"></diff-file>
|
||||
`,
|
||||
);
|
||||
});
|
||||
|
||||
beforeAll(() => {
|
||||
customElements.define('diff-file', DiffFile);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
resetHTMLFixture();
|
||||
});
|
||||
|
||||
it('sets metadata endpoint', () => {
|
||||
initFileBrowser();
|
||||
expect(store.state.diffs.endpointMetadata).toBe(getMountElement().dataset.metadataEndpoint);
|
||||
});
|
||||
|
||||
it('fetches metadata', () => {
|
||||
initFileBrowser();
|
||||
expect(dispatch).toHaveBeenCalledWith('diffs/fetchDiffFilesMeta');
|
||||
});
|
||||
|
||||
it('provides already loaded files', async () => {
|
||||
initFileBrowser();
|
||||
await waitForPromises();
|
||||
expect(JSON.parse(getFileBrowser().dataset.loadedFiles)).toStrictEqual({ first: true });
|
||||
});
|
||||
|
||||
it('handles file clicks', async () => {
|
||||
const selectFile = jest.fn();
|
||||
const spy = jest.spyOn(DiffFile, 'findByFileHash').mockReturnValue({ selectFile });
|
||||
initFileBrowser();
|
||||
await waitForPromises();
|
||||
getFileBrowser().click();
|
||||
expect(spy).toHaveBeenCalledWith('first');
|
||||
expect(selectFile).toHaveBeenCalled();
|
||||
});
|
||||
});
|
@ -1,5 +1,6 @@
|
||||
import { DiffFile } from '~/rapid_diffs/diff_file';
|
||||
import IS from '~/rapid_diffs/intersection_observer';
|
||||
import { DIFF_FILE_MOUNTED } from '~/rapid_diffs/dom_events';
|
||||
|
||||
// We have to use var here because jest hoists mock calls, so let would be uninitialized at this point
|
||||
// eslint-disable-next-line no-var
|
||||
@ -61,19 +62,36 @@ describe('DiffFile Web Component', () => {
|
||||
invisible: jest.fn(),
|
||||
mounted: jest.fn(),
|
||||
});
|
||||
getWebComponentElement().mount();
|
||||
});
|
||||
|
||||
it('observes diff element', () => {
|
||||
getWebComponentElement().mount();
|
||||
expect(IS.prototype.observe).toHaveBeenCalledWith(getWebComponentElement());
|
||||
});
|
||||
|
||||
it('triggers mounted event', () => {
|
||||
let emitted = false;
|
||||
document.addEventListener(DIFF_FILE_MOUNTED, () => {
|
||||
emitted = true;
|
||||
});
|
||||
getWebComponentElement().mount();
|
||||
expect(adapter.mounted).toHaveBeenCalled();
|
||||
expect(adapter.mounted.mock.instances[0]).toStrictEqual(getContext());
|
||||
expect(emitted).toBe(true);
|
||||
});
|
||||
|
||||
it('#selectFile', () => {
|
||||
getWebComponentElement().mount();
|
||||
const spy = jest.spyOn(getWebComponentElement(), 'scrollIntoView');
|
||||
getWebComponentElement().selectFile();
|
||||
expect(spy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('when visible', () => {
|
||||
beforeEach(() => {
|
||||
getWebComponentElement().mount();
|
||||
});
|
||||
|
||||
it('handles all clicks', () => {
|
||||
triggerVisibility(true);
|
||||
getDiffElement().click();
|
||||
@ -102,11 +120,11 @@ describe('DiffFile Web Component', () => {
|
||||
});
|
||||
|
||||
describe('static methods', () => {
|
||||
it('findByFileHash', () => {
|
||||
it('#findByFileHash', () => {
|
||||
expect(DiffFile.findByFileHash('fileHash')).toBeInstanceOf(DiffFile);
|
||||
});
|
||||
|
||||
it('getAll', () => {
|
||||
it('#getAll', () => {
|
||||
document.body.innerHTML = `<diff-file></diff-file><diff-file></diff-file>`;
|
||||
const instances = DiffFile.getAll();
|
||||
expect(instances.length).toBe(2);
|
||||
|
@ -36,7 +36,7 @@ RSpec.describe Mutations::Issues::SetDueDate, feature_category: :api do
|
||||
let(:due_date) { nil }
|
||||
|
||||
it 'updates due date to be nil' do
|
||||
expect(mutated_issue.due_date).to be nil
|
||||
expect(mutated_issue.due_date).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
@ -44,7 +44,7 @@ RSpec.describe Mutations::Issues::SetDueDate, feature_category: :api do
|
||||
let(:due_date) { 'test' }
|
||||
|
||||
it 'updates due date to be nil' do
|
||||
expect(mutated_issue.due_date).to be nil
|
||||
expect(mutated_issue.due_date).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -88,7 +88,7 @@ RSpec.describe Resolvers::ContainerRepositoriesResolver do
|
||||
end
|
||||
|
||||
context 'with unauthorized user' do
|
||||
it { is_expected.to be nil }
|
||||
it { is_expected.to be_nil }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -57,7 +57,7 @@ RSpec.describe Resolvers::PaginatedTreeResolver, feature_category: :source_code_
|
||||
end
|
||||
|
||||
it 'returns nil' do
|
||||
is_expected.to be(nil)
|
||||
is_expected.to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
@ -67,7 +67,7 @@ RSpec.describe Resolvers::PaginatedTreeResolver, feature_category: :source_code_
|
||||
end
|
||||
|
||||
it 'returns nil' do
|
||||
is_expected.to be(nil)
|
||||
is_expected.to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -30,7 +30,7 @@ RSpec.describe Resolvers::TreeResolver do
|
||||
|
||||
result = resolve_repository({ ref: "master" })
|
||||
|
||||
expect(result).to be(nil)
|
||||
expect(result).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -42,7 +42,7 @@ RSpec.describe Resolvers::Users::GroupCountResolver do
|
||||
it do
|
||||
result = batch_sync { resolve_group_count(user1, user2) }
|
||||
|
||||
expect(result).to be nil
|
||||
expect(result).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
@ -50,7 +50,7 @@ RSpec.describe Resolvers::Users::GroupCountResolver do
|
||||
it do
|
||||
result = batch_sync { resolve_group_count(user1, nil) }
|
||||
|
||||
expect(result).to be nil
|
||||
expect(result).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -186,6 +186,7 @@ RSpec.describe 'new tables missing sharding_key', feature_category: :cell do
|
||||
WHERE c.column_name = 'organization_id'
|
||||
AND (fk.referenced_table_name = 'organizations' OR fk.referenced_table_name IS NULL)
|
||||
AND (c.column_default IS NOT NULL OR c.is_nullable::boolean OR fk.name IS NULL OR NOT fk.is_valid)
|
||||
AND (c.table_schema = 'public')
|
||||
ORDER BY c.table_name;
|
||||
SQL
|
||||
|
||||
@ -202,7 +203,8 @@ RSpec.describe 'new tables missing sharding_key', feature_category: :cell do
|
||||
"oauth_openid_requests" => "https://gitlab.com/gitlab-org/gitlab/-/issues/496717",
|
||||
"oauth_device_grants" => "https://gitlab.com/gitlab-org/gitlab/-/issues/496717",
|
||||
"uploads" => "https://gitlab.com/gitlab-org/gitlab/-/issues/398199",
|
||||
"bulk_import_trackers" => "https://gitlab.com/gitlab-org/gitlab/-/issues/517823"
|
||||
"bulk_import_trackers" => "https://gitlab.com/gitlab-org/gitlab/-/issues/517823",
|
||||
"ai_duo_chat_events" => "https://gitlab.com/gitlab-org/gitlab/-/issues/516140"
|
||||
}
|
||||
|
||||
has_lfk = ->(lfks) { lfks.any? { |k| k.options[:column] == 'organization_id' && k.to_table == 'organizations' } }
|
||||
|
@ -81,8 +81,8 @@ RSpec.describe 'Merge Requests Diffs stream', feature_category: :code_review_wor
|
||||
it 'streams diffs except the offset' do
|
||||
go(offset: offset)
|
||||
|
||||
offset_file_identifier_hashes = diff_files.to_a.take(offset).map(&:file_identifier_hash)
|
||||
remaining_file_identifier_hashes = diff_files.to_a.slice(offset..).map(&:file_identifier_hash)
|
||||
offset_file_identifier_hashes = diff_files.to_a.take(offset).map(&:file_hash)
|
||||
remaining_file_identifier_hashes = diff_files.to_a.slice(offset..).map(&:file_hash)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:success)
|
||||
expect(response.body).not_to include(*offset_file_identifier_hashes)
|
||||
|
@ -7,7 +7,7 @@ RSpec.shared_examples 'with diffs_blobs param' do
|
||||
go(diff_blobs: true)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:success)
|
||||
expect(response.body).to include(*diff_files.to_a.map(&:file_identifier_hash))
|
||||
expect(response.body).to include(*diff_files.to_a.map(&:file_hash))
|
||||
end
|
||||
end
|
||||
|
||||
@ -17,8 +17,8 @@ RSpec.shared_examples 'with diffs_blobs param' do
|
||||
it 'streams diffs except the offset' do
|
||||
go(diff_blobs: true, offset: offset)
|
||||
|
||||
offset_file_identifier_hashes = diff_files.to_a.take(offset).map(&:file_identifier_hash)
|
||||
remaining_file_identifier_hashes = diff_files.to_a.slice(offset..).map(&:file_identifier_hash)
|
||||
offset_file_identifier_hashes = diff_files.to_a.take(offset).map(&:file_hash)
|
||||
remaining_file_identifier_hashes = diff_files.to_a.slice(offset..).map(&:file_hash)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:success)
|
||||
expect(response.body).not_to include(*offset_file_identifier_hashes)
|
||||
@ -28,6 +28,6 @@ RSpec.shared_examples 'with diffs_blobs param' do
|
||||
end
|
||||
|
||||
def file_identifier_hashes(diff)
|
||||
diff.diffs.diff_files.to_a.map(&:file_identifier_hash)
|
||||
diff.diffs.diff_files.to_a.map(&:file_hash)
|
||||
end
|
||||
end
|
||||
|
@ -22,16 +22,6 @@ RSpec.describe ProcessCommitWorker, feature_category: :source_code_management do
|
||||
expect(::Gitlab::SidekiqMiddleware::ConcurrencyLimit::WorkersMap.limit_for(worker: described_class)).to eq(1000)
|
||||
end
|
||||
|
||||
context 'when concurrency_limit_process_commit_worker is disabled' do
|
||||
before do
|
||||
stub_feature_flags(concurrency_limit_process_commit_worker: false)
|
||||
end
|
||||
|
||||
it 'does not have a concurrency limit' do
|
||||
expect(::Gitlab::SidekiqMiddleware::ConcurrencyLimit::WorkersMap.limit_for(worker: described_class)).to eq(0)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#perform' do
|
||||
subject(:perform) { worker.perform(project_id, user_id, commit.to_hash, default) }
|
||||
|
||||
|
Reference in New Issue
Block a user