Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
4edee7ef8b
commit
1f5a6f7200
2
Gemfile
2
Gemfile
|
|
@ -49,7 +49,7 @@ gem 'responders', '~> 3.0' # rubocop:todo Gemfile/MissingFeatureCategory
|
|||
|
||||
gem 'sprockets', '~> 3.7.0' # rubocop:todo Gemfile/MissingFeatureCategory
|
||||
|
||||
gem 'view_component', '~> 3.11.0' # rubocop:todo Gemfile/MissingFeatureCategory
|
||||
gem 'view_component', '~> 3.12.1' # rubocop:todo Gemfile/MissingFeatureCategory
|
||||
|
||||
# Supported DBs
|
||||
gem 'pg', '~> 1.5.6' # rubocop:todo Gemfile/MissingFeatureCategory
|
||||
|
|
|
|||
|
|
@ -719,7 +719,7 @@
|
|||
{"name":"validates_hostname","version":"1.0.13","platform":"ruby","checksum":"eac40178cc0b4f727df9cc6a5cb5bc2550718ad8d9bb3728df9aba6354bdda19"},
|
||||
{"name":"version_gem","version":"1.1.0","platform":"ruby","checksum":"6b009518020db57f51ec7b410213fae2bf692baea9f1b51770db97fbc93d9a80"},
|
||||
{"name":"version_sorter","version":"2.3.0","platform":"ruby","checksum":"2147f2a1a3804fbb8f60d268b7d7c1ec717e6dd727ffe2c165b4e05e82efe1da"},
|
||||
{"name":"view_component","version":"3.11.0","platform":"ruby","checksum":"6994c3dcc6f8da1fab42420a367f5071c00291241bc5a56f73a34ec8c10fc5ff"},
|
||||
{"name":"view_component","version":"3.12.1","platform":"ruby","checksum":"f2ce2ad2945389f4bbd4ff77465605e9019041e5c804d16d093791be2542b18b"},
|
||||
{"name":"virtus","version":"2.0.0","platform":"ruby","checksum":"8841dae4eb7fcc097320ba5ea516bf1839e5d056c61ee27138aa4bddd6e3d1c2"},
|
||||
{"name":"vite_rails","version":"3.0.17","platform":"ruby","checksum":"b90e85a3e55802981cbdb43a4101d944b1e7055bfe85599d9cb7de0f1ea58bcc"},
|
||||
{"name":"vite_ruby","version":"3.5.0","platform":"ruby","checksum":"a3e5da3fdd816f831cb1530c4001a790aac862c89f74c09f48d5a3cfed3dea73"},
|
||||
|
|
|
|||
|
|
@ -1867,7 +1867,7 @@ GEM
|
|||
activesupport (>= 3.0)
|
||||
version_gem (1.1.0)
|
||||
version_sorter (2.3.0)
|
||||
view_component (3.11.0)
|
||||
view_component (3.12.1)
|
||||
activesupport (>= 5.2.0, < 8.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
method_source (~> 1.0)
|
||||
|
|
@ -2272,7 +2272,7 @@ DEPENDENCIES
|
|||
valid_email (~> 0.1)
|
||||
validates_hostname (~> 1.0.13)
|
||||
version_sorter (~> 2.3)
|
||||
view_component (~> 3.11.0)
|
||||
view_component (~> 3.12.1)
|
||||
vite_rails (~> 3.0.17)
|
||||
vite_ruby (~> 3.5.0)
|
||||
vmstat (~> 2.3.0)
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ export default {
|
|||
this.noteData.noteable_id = this.getNoteableData.id;
|
||||
},
|
||||
methods: {
|
||||
...mapActions('batchComments', ['publishReview']),
|
||||
...mapActions('batchComments', ['publishReview', 'clearDrafts']),
|
||||
repositionDropdown() {
|
||||
this.$refs.submitDropdown?.$refs.dropdown?.updatePopper();
|
||||
},
|
||||
|
|
@ -157,6 +157,8 @@ export default {
|
|||
scrollToElement(document.getElementById(`note_${this.getCurrentUserLastNote.id}`)),
|
||||
);
|
||||
}
|
||||
|
||||
this.clearDrafts();
|
||||
} catch (e) {
|
||||
if (e.data?.message) {
|
||||
createAlert({ message: e.data.message, captureError: true });
|
||||
|
|
|
|||
|
|
@ -41,7 +41,6 @@ export default {
|
|||
},
|
||||
[types.RECEIVE_PUBLISH_REVIEW_SUCCESS](state) {
|
||||
state.isPublishing = false;
|
||||
state.drafts = [];
|
||||
},
|
||||
[types.RECEIVE_PUBLISH_REVIEW_ERROR](state) {
|
||||
state.isPublishing = false;
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ export default {
|
|||
return {
|
||||
id: runner.id,
|
||||
search: search.length >= SHORT_SEARCH_LENGTH ? search : '',
|
||||
sort: 'ID_ASC',
|
||||
...getPaginationVariables(this.pagination, RUNNER_DETAILS_PROJECTS_PAGE_SIZE),
|
||||
};
|
||||
},
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
query getRunnerProjects(
|
||||
$id: CiRunnerID!
|
||||
$search: String
|
||||
$sort: String
|
||||
$first: Int
|
||||
$last: Int
|
||||
$before: String
|
||||
|
|
@ -14,7 +15,14 @@ query getRunnerProjects(
|
|||
id
|
||||
}
|
||||
projectCount
|
||||
projects(search: $search, first: $first, last: $last, before: $before, after: $after) {
|
||||
projects(
|
||||
search: $search
|
||||
sort: $sort
|
||||
first: $first
|
||||
last: $last
|
||||
before: $before
|
||||
after: $after
|
||||
) {
|
||||
nodes {
|
||||
id
|
||||
avatarUrl
|
||||
|
|
|
|||
|
|
@ -1,22 +1,91 @@
|
|||
<script>
|
||||
import TitleArea from '~/vue_shared/components/registry/title_area.vue';
|
||||
import * as Sentry from '~/sentry/sentry_browser_wrapper';
|
||||
import getModelVersionQuery from '~/ml/model_registry/graphql/queries/get_model_version.query.graphql';
|
||||
import { convertToGraphQLId } from '~/graphql_shared/utils';
|
||||
import { makeLoadVersionsErrorMessage } from '~/ml/model_registry/translations';
|
||||
import ModelVersionDetail from '../components/model_version_detail.vue';
|
||||
import LoadOrErrorOrShow from '../components/load_or_error_or_show.vue';
|
||||
|
||||
export default {
|
||||
name: 'ShowMlModelVersionApp',
|
||||
components: {
|
||||
LoadOrErrorOrShow,
|
||||
ModelVersionDetail,
|
||||
TitleArea,
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
projectPath: this.projectPath,
|
||||
};
|
||||
},
|
||||
props: {
|
||||
modelVersion: {
|
||||
type: Object,
|
||||
modelId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
modelVersionId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
versionName: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
modelName: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
projectPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
apollo: {
|
||||
modelWithModelVersion: {
|
||||
query: getModelVersionQuery,
|
||||
variables() {
|
||||
return this.queryVariables;
|
||||
},
|
||||
update(data) {
|
||||
return data?.mlModel;
|
||||
},
|
||||
error(error) {
|
||||
this.handleError(error);
|
||||
},
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
modelWithModelVersion: {},
|
||||
errorMessage: '',
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
modelVersion() {
|
||||
return this.modelWithModelVersion?.version;
|
||||
},
|
||||
isLoading() {
|
||||
return this.$apollo.queries.modelWithModelVersion.loading;
|
||||
},
|
||||
title() {
|
||||
return `${this.modelVersion.model.name} / ${this.modelVersion.version}`;
|
||||
return `${this.modelName} / ${this.versionName}`;
|
||||
},
|
||||
queryVariables() {
|
||||
return {
|
||||
modelId: convertToGraphQLId('Ml::Model', this.modelId),
|
||||
modelVersionId: convertToGraphQLId('Ml::ModelVersion', this.modelVersionId),
|
||||
};
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleError(error) {
|
||||
this.errorMessage = makeLoadVersionsErrorMessage(error.message);
|
||||
Sentry.captureException(error, {
|
||||
tags: {
|
||||
vue_component: 'show_ml_model_version',
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
@ -25,6 +94,8 @@ export default {
|
|||
<template>
|
||||
<div>
|
||||
<title-area :title="title" />
|
||||
<model-version-detail :model-version="modelVersion" />
|
||||
<load-or-error-or-show :is-loading="isLoading" :error-message="errorMessage">
|
||||
<model-version-detail :model-version="modelVersion" />
|
||||
</load-or-error-or-show>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<script>
|
||||
import { convertToGraphQLId } from '~/graphql_shared/utils';
|
||||
import { convertCandidateFromGraphql } from '~/ml/model_registry/utils';
|
||||
import { convertToGraphQLId, isGid } from '~/graphql_shared/utils';
|
||||
import { TYPENAME_PACKAGES_PACKAGE } from '~/graphql_shared/constants';
|
||||
import * as i18n from '../translations';
|
||||
import CandidateDetail from './candidate_detail.vue';
|
||||
|
|
@ -11,6 +12,7 @@ export default {
|
|||
import('~/packages_and_registries/package_registry/components/details/package_files.vue'),
|
||||
CandidateDetail,
|
||||
},
|
||||
inject: ['projectPath'],
|
||||
props: {
|
||||
modelVersion: {
|
||||
type: Object,
|
||||
|
|
@ -18,15 +20,26 @@ export default {
|
|||
},
|
||||
},
|
||||
computed: {
|
||||
packageId() {
|
||||
return convertToGraphQLId(TYPENAME_PACKAGES_PACKAGE, this.modelVersion.packageId);
|
||||
},
|
||||
projectPath() {
|
||||
return this.modelVersion.projectPath;
|
||||
},
|
||||
packageType() {
|
||||
return 'ml_model';
|
||||
},
|
||||
isFromGraphql() {
|
||||
return isGid(this.modelVersion.id);
|
||||
},
|
||||
candidate() {
|
||||
if (this.isFromGraphql) {
|
||||
return convertCandidateFromGraphql(this.modelVersion.candidate);
|
||||
}
|
||||
|
||||
return this.modelVersion.candidate;
|
||||
},
|
||||
packageId() {
|
||||
if (this.isFromGraphql) {
|
||||
return this.modelVersion.packageId;
|
||||
}
|
||||
|
||||
return convertToGraphQLId(TYPENAME_PACKAGES_PACKAGE, this.modelVersion.packageId);
|
||||
},
|
||||
},
|
||||
i18n,
|
||||
};
|
||||
|
|
@ -53,9 +66,9 @@ export default {
|
|||
|
||||
<div class="gl-mt-5">
|
||||
<span class="gl-font-weight-bold">{{ $options.i18n.MLFLOW_ID_LABEL }}:</span>
|
||||
{{ modelVersion.candidate.info.eid }}
|
||||
{{ candidate.info.eid }}
|
||||
</div>
|
||||
|
||||
<candidate-detail :candidate="modelVersion.candidate" :show-info-section="false" />
|
||||
<candidate-detail :candidate="candidate" :show-info-section="false" />
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,69 @@
|
|||
query getModelVersion($modelId: MlModelID!, $modelVersionId: MlModelVersionID!) {
|
||||
mlModel(id: $modelId) {
|
||||
id
|
||||
name
|
||||
version(modelVersionId: $modelVersionId) {
|
||||
id
|
||||
version
|
||||
packageId
|
||||
description
|
||||
candidate {
|
||||
id
|
||||
name
|
||||
iid
|
||||
eid
|
||||
status
|
||||
params {
|
||||
nodes {
|
||||
id
|
||||
name
|
||||
value
|
||||
}
|
||||
}
|
||||
metadata {
|
||||
nodes {
|
||||
id
|
||||
name
|
||||
value
|
||||
}
|
||||
}
|
||||
metrics {
|
||||
nodes {
|
||||
id
|
||||
name
|
||||
value
|
||||
step
|
||||
}
|
||||
}
|
||||
ciJob {
|
||||
id
|
||||
webPath
|
||||
name
|
||||
pipeline {
|
||||
id
|
||||
mergeRequest {
|
||||
id
|
||||
iid
|
||||
title
|
||||
webUrl
|
||||
}
|
||||
user {
|
||||
id
|
||||
avatarUrl
|
||||
webUrl
|
||||
username
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
_links {
|
||||
showPath
|
||||
artifactPath
|
||||
}
|
||||
}
|
||||
_links {
|
||||
showPath
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
export function convertCandidateFromGraphql(graphqlCandidate) {
|
||||
const { iid, eid, status, ciJob } = graphqlCandidate;
|
||||
const links = graphqlCandidate._links;
|
||||
|
||||
let ciJobValues = null;
|
||||
|
||||
if (ciJob) {
|
||||
let userInfo = null;
|
||||
let mergeRequestInfo = null;
|
||||
const user = ciJob?.pipeline.user;
|
||||
const mr = ciJob?.pipeline.mergeRequest;
|
||||
|
||||
if (user) {
|
||||
userInfo = {
|
||||
avatar: user.avatarUrl,
|
||||
path: user.webUrl,
|
||||
username: user.username,
|
||||
name: user.name,
|
||||
};
|
||||
}
|
||||
|
||||
if (mr) {
|
||||
mergeRequestInfo = {
|
||||
title: mr.title,
|
||||
path: mr.webUrl,
|
||||
iid: mr.iid,
|
||||
};
|
||||
}
|
||||
|
||||
ciJobValues = {
|
||||
name: ciJob.name,
|
||||
path: ciJob.webPath,
|
||||
user: userInfo,
|
||||
mergeRequest: mergeRequestInfo,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
info: {
|
||||
iid,
|
||||
eid,
|
||||
status,
|
||||
experimentName: '',
|
||||
pathToExperiment: '',
|
||||
pathToArtifact: links.artifactPath,
|
||||
path: links.showPath,
|
||||
ciJob: ciJobValues,
|
||||
},
|
||||
metrics: graphqlCandidate.metrics.nodes,
|
||||
params: graphqlCandidate.params.nodes,
|
||||
metadata: graphqlCandidate.metadata.nodes,
|
||||
};
|
||||
}
|
||||
|
|
@ -4,8 +4,8 @@ import createProtectionRuleMutation from '~/packages_and_registries/settings/pro
|
|||
import { s__, __ } from '~/locale';
|
||||
|
||||
const GRAPHQL_ACCESS_LEVEL_VALUE_MAINTAINER = 'MAINTAINER';
|
||||
const GRAPHQL_ACCESS_LEVEL_VALUE_DEVELOPER = 'DEVELOPER';
|
||||
const GRAPHQL_ACCESS_LEVEL_VALUE_OWNER = 'OWNER';
|
||||
const GRAPHQL_ACCESS_LEVEL_VALUE_ADMIN = 'ADMIN';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
|
@ -26,8 +26,8 @@ export default {
|
|||
return {
|
||||
protectionRuleFormData: {
|
||||
repositoryPathPattern: '',
|
||||
pushProtectedUpToAccessLevel: GRAPHQL_ACCESS_LEVEL_VALUE_DEVELOPER,
|
||||
deleteProtectedUpToAccessLevel: GRAPHQL_ACCESS_LEVEL_VALUE_DEVELOPER,
|
||||
minimumAccessLevelForPush: GRAPHQL_ACCESS_LEVEL_VALUE_MAINTAINER,
|
||||
minimumAccessLevelForDelete: GRAPHQL_ACCESS_LEVEL_VALUE_MAINTAINER,
|
||||
},
|
||||
updateInProgress: false,
|
||||
alertErrorMessage: '',
|
||||
|
|
@ -50,15 +50,15 @@ export default {
|
|||
return {
|
||||
projectPath: this.projectPath,
|
||||
repositoryPathPattern: this.protectionRuleFormData.repositoryPathPattern,
|
||||
pushProtectedUpToAccessLevel: this.protectionRuleFormData.pushProtectedUpToAccessLevel,
|
||||
deleteProtectedUpToAccessLevel: this.protectionRuleFormData.deleteProtectedUpToAccessLevel,
|
||||
minimumAccessLevelForPush: this.protectionRuleFormData.minimumAccessLevelForPush,
|
||||
minimumAccessLevelForDelete: this.protectionRuleFormData.minimumAccessLevelForDelete,
|
||||
};
|
||||
},
|
||||
protectedUpToAccessLevelOptions() {
|
||||
minimumAccessLevelOptions() {
|
||||
return [
|
||||
{ value: GRAPHQL_ACCESS_LEVEL_VALUE_DEVELOPER, text: __('Developer') },
|
||||
{ value: GRAPHQL_ACCESS_LEVEL_VALUE_MAINTAINER, text: __('Maintainer') },
|
||||
{ value: GRAPHQL_ACCESS_LEVEL_VALUE_OWNER, text: __('Owner') },
|
||||
{ value: GRAPHQL_ACCESS_LEVEL_VALUE_ADMIN, text: __('Admin') },
|
||||
];
|
||||
},
|
||||
},
|
||||
|
|
@ -130,28 +130,28 @@ export default {
|
|||
</gl-form-group>
|
||||
|
||||
<gl-form-group
|
||||
:label="s__('ContainerRegistry|Maximum access level prevented from pushing')"
|
||||
label-for="input-push-protected-up-to-access-level"
|
||||
:label="s__('ContainerRegistry|Minimum access level for push')"
|
||||
label-for="input-minimum-access-level-for-push"
|
||||
:disabled="isFieldDisabled"
|
||||
>
|
||||
<gl-form-select
|
||||
id="input-push-protected-up-to-access-level"
|
||||
v-model="protectionRuleFormData.pushProtectedUpToAccessLevel"
|
||||
:options="protectedUpToAccessLevelOptions"
|
||||
id="input-minimum-access-level-for-push"
|
||||
v-model="protectionRuleFormData.minimumAccessLevelForPush"
|
||||
:options="minimumAccessLevelOptions"
|
||||
:disabled="isFieldDisabled"
|
||||
required
|
||||
/>
|
||||
</gl-form-group>
|
||||
|
||||
<gl-form-group
|
||||
:label="s__('ContainerRegistry|Maximum access level prevented from deleting')"
|
||||
label-for="input-delete-protected-up-to-access-level"
|
||||
:label="s__('ContainerRegistry|Minimum access level for delete')"
|
||||
label-for="input-minimum-access-level-for-delete"
|
||||
:disabled="isFieldDisabled"
|
||||
>
|
||||
<gl-form-select
|
||||
id="input-delete-protected-up-to-access-level"
|
||||
v-model="protectionRuleFormData.deleteProtectedUpToAccessLevel"
|
||||
:options="protectedUpToAccessLevelOptions"
|
||||
id="input-minimum-access-level-for-delete"
|
||||
v-model="protectionRuleFormData.minimumAccessLevelForDelete"
|
||||
:options="minimumAccessLevelOptions"
|
||||
:disabled="isFieldDisabled"
|
||||
required
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -17,17 +17,15 @@ import { s__, __ } from '~/locale';
|
|||
|
||||
const PAGINATION_DEFAULT_PER_PAGE = 10;
|
||||
|
||||
const I18N_PUSH_PROTECTED_UP_TO_ACCESS_LEVEL = s__(
|
||||
'ContainerRegistry|Push protected up to access level',
|
||||
);
|
||||
const I18N_DELETE_PROTECTED_UP_TO_ACCESS_LEVEL = s__(
|
||||
'ContainerRegistry|Delete protected up to access level',
|
||||
const I18N_MINIMUM_ACCESS_LEVEL_FOR_PUSH = s__('ContainerRegistry|Minimum access level for push');
|
||||
const I18N_MINIMUM_ACCESS_LEVEL_FOR_DELETE = s__(
|
||||
'ContainerRegistry|Minimum access level for delete',
|
||||
);
|
||||
|
||||
const ACCESS_LEVEL_GRAPHQL_VALUE_TO_LABEL = {
|
||||
DEVELOPER: __('Developer'),
|
||||
MAINTAINER: __('Maintainer'),
|
||||
OWNER: __('Owner'),
|
||||
ADMIN: __('Admin'),
|
||||
};
|
||||
|
||||
export default {
|
||||
|
|
@ -93,10 +91,10 @@ export default {
|
|||
return this.protectionRulesQueryResult.map((protectionRule) => {
|
||||
return {
|
||||
id: protectionRule.id,
|
||||
deleteProtectedUpToAccessLevel:
|
||||
ACCESS_LEVEL_GRAPHQL_VALUE_TO_LABEL[protectionRule.deleteProtectedUpToAccessLevel],
|
||||
pushProtectedUpToAccessLevel:
|
||||
ACCESS_LEVEL_GRAPHQL_VALUE_TO_LABEL[protectionRule.pushProtectedUpToAccessLevel],
|
||||
minimumAccessLevelForDelete:
|
||||
ACCESS_LEVEL_GRAPHQL_VALUE_TO_LABEL[protectionRule.minimumAccessLevelForDelete],
|
||||
minimumAccessLevelForPush:
|
||||
ACCESS_LEVEL_GRAPHQL_VALUE_TO_LABEL[protectionRule.minimumAccessLevelForPush],
|
||||
repositoryPathPattern: protectionRule.repositoryPathPattern,
|
||||
};
|
||||
});
|
||||
|
|
@ -166,7 +164,7 @@ export default {
|
|||
this.protectionRuleMutationItem = null;
|
||||
this.protectionRuleMutationInProgress = false;
|
||||
},
|
||||
isProtectionRulePushProtectedUpToAccessLevelFormSelectDisabled(item) {
|
||||
isProtectionRuleMinimumAccessLevelForPushFormSelectDisabled(item) {
|
||||
return this.isProtectionRuleMutationInProgress(item);
|
||||
},
|
||||
isProtectionRuleDeleteButtonDisabled(item) {
|
||||
|
|
@ -209,13 +207,13 @@ export default {
|
|||
tdClass: 'gl-vertical-align-middle!',
|
||||
},
|
||||
{
|
||||
key: 'pushProtectedUpToAccessLevel',
|
||||
label: I18N_PUSH_PROTECTED_UP_TO_ACCESS_LEVEL,
|
||||
key: 'minimumAccessLevelForPush',
|
||||
label: I18N_MINIMUM_ACCESS_LEVEL_FOR_PUSH,
|
||||
tdClass: 'gl-vertical-align-middle!',
|
||||
},
|
||||
{
|
||||
key: 'deleteProtectedUpToAccessLevel',
|
||||
label: I18N_DELETE_PROTECTED_UP_TO_ACCESS_LEVEL,
|
||||
key: 'minimumAccessLevelForDelete',
|
||||
label: I18N_MINIMUM_ACCESS_LEVEL_FOR_DELETE,
|
||||
tdClass: 'gl-vertical-align-middle!',
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ mutation createContainerProtectionRule($input: CreateContainerRegistryProtection
|
|||
containerRegistryProtectionRule {
|
||||
id
|
||||
repositoryPathPattern
|
||||
pushProtectedUpToAccessLevel
|
||||
deleteProtectedUpToAccessLevel
|
||||
minimumAccessLevelForPush
|
||||
minimumAccessLevelForDelete
|
||||
}
|
||||
errors
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ mutation deleteContainerRegistryProtectionRule(
|
|||
containerRegistryProtectionRule {
|
||||
id
|
||||
repositoryPathPattern
|
||||
pushProtectedUpToAccessLevel
|
||||
deleteProtectedUpToAccessLevel
|
||||
minimumAccessLevelForPush
|
||||
minimumAccessLevelForDelete
|
||||
}
|
||||
errors
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ query getProjectContainerProtectionRules(
|
|||
nodes {
|
||||
id
|
||||
repositoryPathPattern
|
||||
pushProtectedUpToAccessLevel
|
||||
deleteProtectedUpToAccessLevel
|
||||
minimumAccessLevelForPush
|
||||
minimumAccessLevelForDelete
|
||||
}
|
||||
pageInfo {
|
||||
...PageInfo
|
||||
|
|
|
|||
|
|
@ -7,18 +7,7 @@ export const todoLabel = (hasTodo) => {
|
|||
return hasTodo ? __('Mark as done') : __('Add a to do');
|
||||
};
|
||||
|
||||
export const updateGlobalTodoCount = (additionalTodoCount) => {
|
||||
const countContainer = document.querySelector('.js-todos-count');
|
||||
|
||||
if (countContainer === null) return;
|
||||
|
||||
const currentCount = parseInt(countContainer.innerText, 10) || 0;
|
||||
|
||||
const todoToggleEvent = new CustomEvent('todo:toggle', {
|
||||
detail: {
|
||||
count: Math.max(currentCount + additionalTodoCount, 0),
|
||||
},
|
||||
});
|
||||
|
||||
document.dispatchEvent(todoToggleEvent);
|
||||
export const updateGlobalTodoCount = (delta) => {
|
||||
// Optimistic update of user counts
|
||||
document.dispatchEvent(new CustomEvent('todo:toggle', { detail: { delta } }));
|
||||
};
|
||||
|
|
|
|||
|
|
@ -86,17 +86,10 @@ export default {
|
|||
Object.assign(userCounts, this.sidebarData.user_counts);
|
||||
createUserCountsManager();
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener('todo:toggle', this.updateTodos);
|
||||
},
|
||||
beforeDestroy() {
|
||||
document.removeEventListener('todo:toggle', this.updateTodos);
|
||||
destroyUserCountsManager();
|
||||
},
|
||||
methods: {
|
||||
updateTodos(e) {
|
||||
userCounts.todos = e.detail.count || 0;
|
||||
},
|
||||
hideSearchTooltip() {
|
||||
this.searchTooltip = '';
|
||||
},
|
||||
|
|
|
|||
|
|
@ -41,8 +41,17 @@ async function retrieveUserCountsFromApi() {
|
|||
}
|
||||
}
|
||||
|
||||
function updateTodos(e) {
|
||||
if (Number.isSafeInteger(e?.detail?.count)) {
|
||||
userCounts.todos = Math.max(e.detail.count, 0);
|
||||
} else if (Number.isSafeInteger(e?.detail?.delta)) {
|
||||
userCounts.todos = Math.max(userCounts.todos + e.detail.delta, 0);
|
||||
}
|
||||
}
|
||||
|
||||
export function destroyUserCountsManager() {
|
||||
document.removeEventListener('userCounts:fetch', retrieveUserCountsFromApi);
|
||||
document.removeEventListener('todo:toggle', updateTodos);
|
||||
broadcastChannel?.close();
|
||||
broadcastChannel = null;
|
||||
}
|
||||
|
|
@ -58,6 +67,7 @@ export function destroyUserCountsManager() {
|
|||
export function createUserCountsManager() {
|
||||
destroyUserCountsManager();
|
||||
document.addEventListener('userCounts:fetch', retrieveUserCountsFromApi);
|
||||
document.addEventListener('todo:toggle', updateTodos);
|
||||
|
||||
if (window.BroadcastChannel && gon?.current_user_id) {
|
||||
broadcastChannel = new BroadcastChannel(`user_counts_${gon?.current_user_id}`);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import produce from 'immer';
|
|||
import todoMarkDoneMutation from '~/graphql_shared/mutations/todo_mark_done.mutation.graphql';
|
||||
import { s__ } from '~/locale';
|
||||
import Todo from '~/sidebar/components/todo_toggle/todo.vue';
|
||||
import { updateGlobalTodoCount } from '~/sidebar/utils';
|
||||
import createAlertTodoMutation from '../../graphql/mutations/alert_todo_create.mutation.graphql';
|
||||
import alertQuery from '../../graphql/queries/alert_sidebar_details.query.graphql';
|
||||
|
||||
|
|
@ -52,17 +53,6 @@ export default {
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
updateToDoCount(add) {
|
||||
const oldCount = parseInt(document.querySelector('.js-todos-count').innerText, 10) || 0;
|
||||
const count = add ? oldCount + 1 : oldCount - 1;
|
||||
const headerTodoEvent = new CustomEvent('todo:toggle', {
|
||||
detail: {
|
||||
count: Math.max(count, 0),
|
||||
},
|
||||
});
|
||||
|
||||
document.dispatchEvent(headerTodoEvent);
|
||||
},
|
||||
addToDo() {
|
||||
this.isUpdating = true;
|
||||
return this.$apollo
|
||||
|
|
@ -78,7 +68,7 @@ export default {
|
|||
this.throwError(errors[0]);
|
||||
return;
|
||||
}
|
||||
this.updateToDoCount(true);
|
||||
updateGlobalTodoCount(1);
|
||||
})
|
||||
.catch(() => {
|
||||
this.throwError();
|
||||
|
|
@ -102,7 +92,7 @@ export default {
|
|||
this.throwError(errors[0]);
|
||||
return;
|
||||
}
|
||||
this.updateToDoCount(false);
|
||||
updateGlobalTodoCount(-1);
|
||||
})
|
||||
.catch(() => {
|
||||
this.throwError();
|
||||
|
|
|
|||
|
|
@ -25,19 +25,19 @@ module Mutations
|
|||
'Container repository path pattern protected by the protection rule. ' \
|
||||
'For example `my-project/my-container-*`. Wildcard character `*` allowed.'
|
||||
|
||||
argument :push_protected_up_to_access_level,
|
||||
argument :minimum_access_level_for_push,
|
||||
Types::ContainerRegistry::Protection::RuleAccessLevelEnum,
|
||||
required: true,
|
||||
description:
|
||||
'Max GitLab access level to prevent from pushing container images to the container registry. ' \
|
||||
'For example `DEVELOPER`, `MAINTAINER`, `OWNER`.'
|
||||
'Minimum GitLab access level to allow to push container images to the container registry. ' \
|
||||
'For example `MAINTAINER`, `OWNER`, or `ADMIN`.'
|
||||
|
||||
argument :delete_protected_up_to_access_level,
|
||||
argument :minimum_access_level_for_delete,
|
||||
Types::ContainerRegistry::Protection::RuleAccessLevelEnum,
|
||||
required: true,
|
||||
description:
|
||||
'Max GitLab access level to prevent from deleting container images in the container registry. ' \
|
||||
'For example `DEVELOPER`, `MAINTAINER`, `OWNER`.'
|
||||
'Minimum GitLab access level to allow to delete container images in the container registry. ' \
|
||||
'For example `MAINTAINER`, `OWNER`, or `ADMIN`.'
|
||||
|
||||
field :container_registry_protection_rule,
|
||||
Types::ContainerRegistry::Protection::RuleType,
|
||||
|
|
|
|||
|
|
@ -26,21 +26,21 @@ module Mutations
|
|||
'For example, `my-scope/my-project/container-dev-*`. ' \
|
||||
'Wildcard character `*` allowed.'
|
||||
|
||||
argument :delete_protected_up_to_access_level,
|
||||
argument :minimum_access_level_for_delete,
|
||||
Types::ContainerRegistry::Protection::RuleAccessLevelEnum,
|
||||
required: false,
|
||||
validates: { allow_blank: false },
|
||||
description:
|
||||
'Maximum GitLab access level prevented from deleting a container. ' \
|
||||
'For example, `DEVELOPER`, `MAINTAINER`, `OWNER`.'
|
||||
'Minimum GitLab access level allowed to delete container images to the container registry. ' \
|
||||
'For example, `DEVELOPER`, `MAINTAINER`, `OWNER`, or `ADMIN`.'
|
||||
|
||||
argument :push_protected_up_to_access_level,
|
||||
argument :minimum_access_level_for_push,
|
||||
Types::ContainerRegistry::Protection::RuleAccessLevelEnum,
|
||||
required: false,
|
||||
validates: { allow_blank: false },
|
||||
description:
|
||||
'Maximum GitLab access level prevented from pushing a container. ' \
|
||||
'For example, `DEVELOPER`, `MAINTAINER`, `OWNER`.'
|
||||
'Minimum GitLab access level allowed to push container images to the container registry. ' \
|
||||
'For example, `DEVELOPER`, `MAINTAINER`, `OWNER`, or `ADMIN`.'
|
||||
|
||||
field :container_registry_protection_rule,
|
||||
Types::ContainerRegistry::Protection::RuleType,
|
||||
|
|
|
|||
|
|
@ -13,17 +13,6 @@ module Resolvers
|
|||
|
||||
alias_method :runner, :object
|
||||
|
||||
argument :sort, GraphQL::Types::String,
|
||||
required: false,
|
||||
default_value: 'id_asc', # TODO: Remove in %17.0 and move :sort to ProjectSearchArguments, see https://gitlab.com/gitlab-org/gitlab/-/issues/372117
|
||||
deprecated: {
|
||||
reason: 'Default sort order will change in GitLab 17.0. ' \
|
||||
'Specify `"id_asc"` if you require the query results to be ordered by ascending IDs',
|
||||
milestone: '15.4'
|
||||
},
|
||||
description: "Sort order of results. Format: `<field_name>_<sort_direction>`, " \
|
||||
"for example: `id_desc` or `name_asc`"
|
||||
|
||||
def resolve_with_lookahead(**args)
|
||||
return unless runner.project_type?
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,12 @@ module ProjectSearchArguments
|
|||
argument :personal, GraphQL::Types::Boolean,
|
||||
required: false,
|
||||
description: 'Return only personal projects.'
|
||||
|
||||
argument :sort, GraphQL::Types::String,
|
||||
required: false,
|
||||
default_value: 'id_desc',
|
||||
description: "Sort order of results. Format: `<field_name>_<sort_direction>`, " \
|
||||
"for example: `id_desc` or `name_asc`"
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
|||
|
|
@ -15,11 +15,6 @@ module Resolvers
|
|||
required: false,
|
||||
description: 'Filter projects by full paths. You cannot provide more than 50 full paths.'
|
||||
|
||||
argument :sort, GraphQL::Types::String,
|
||||
required: false,
|
||||
description: "Sort order of results. Format: `<field_name>_<sort_direction>`, " \
|
||||
"for example: `id_desc` or `name_asc`"
|
||||
|
||||
argument :with_issues_enabled, GraphQL::Types::Boolean,
|
||||
required: false,
|
||||
description: "Return only projects with issues enabled."
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ module Types
|
|||
graphql_name 'ContainerRegistryProtectionRuleAccessLevel'
|
||||
description 'Access level of a container registry protection rule resource'
|
||||
|
||||
::ContainerRegistry::Protection::Rule.push_protected_up_to_access_levels.each_key do |access_level_key|
|
||||
::ContainerRegistry::Protection::Rule.minimum_access_level_for_pushes.each_key do |access_level_key|
|
||||
value access_level_key.upcase, value: access_level_key.to_s,
|
||||
description: "#{access_level_key.capitalize} access."
|
||||
end
|
||||
|
|
|
|||
|
|
@ -22,19 +22,19 @@ module Types
|
|||
'Container repository path pattern protected by the protection rule. ' \
|
||||
'For example `my-project/my-container-*`. Wildcard character `*` allowed.'
|
||||
|
||||
field :push_protected_up_to_access_level,
|
||||
field :minimum_access_level_for_push,
|
||||
Types::ContainerRegistry::Protection::RuleAccessLevelEnum,
|
||||
null: false,
|
||||
description:
|
||||
'Max GitLab access level to prevent from pushing container images to the container registry. ' \
|
||||
'For example `DEVELOPER`, `MAINTAINER`, `OWNER`.'
|
||||
'Minimum GitLab access level to allow to push container images to the container registry. ' \
|
||||
'For example `DEVELOPER`, `MAINTAINER`, or `OWNER`.'
|
||||
|
||||
field :delete_protected_up_to_access_level,
|
||||
field :minimum_access_level_for_delete,
|
||||
Types::ContainerRegistry::Protection::RuleAccessLevelEnum,
|
||||
null: false,
|
||||
description:
|
||||
'Max GitLab access level to prevent from pushing container images to the container registry. ' \
|
||||
'For example `DEVELOPER`, `MAINTAINER`, `OWNER`.'
|
||||
'Minimum GitLab access level to allow to push container images to the container registry. ' \
|
||||
'For example `DEVELOPER`, `MAINTAINER`, or `OWNER`.'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,21 +4,10 @@ module Types
|
|||
class Namespace::SharedRunnersSettingEnum < BaseEnum
|
||||
graphql_name 'SharedRunnersSetting'
|
||||
|
||||
DEPRECATED_SETTINGS = [::Namespace::SR_DISABLED_WITH_OVERRIDE].freeze
|
||||
|
||||
::Namespace::SHARED_RUNNERS_SETTINGS.excluding(DEPRECATED_SETTINGS).each do |type|
|
||||
::Namespace::SHARED_RUNNERS_SETTINGS.each do |type|
|
||||
value type.upcase,
|
||||
description: "Sharing of runners is #{type.tr('_', ' ')}.",
|
||||
value: type
|
||||
end
|
||||
|
||||
value ::Namespace::SR_DISABLED_WITH_OVERRIDE.upcase,
|
||||
description: "Sharing of runners is disabled and overridable.",
|
||||
value: ::Namespace::SR_DISABLED_WITH_OVERRIDE,
|
||||
deprecated: {
|
||||
reason: :renamed,
|
||||
replacement: ::Namespace::SR_DISABLED_AND_OVERRIDABLE,
|
||||
milestone: "17.0"
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -39,6 +39,21 @@ module Projects
|
|||
to_json(data)
|
||||
end
|
||||
|
||||
def show_ml_model_version_data(model_version, user)
|
||||
project = model_version.project
|
||||
|
||||
data = {
|
||||
project_path: project.full_path,
|
||||
model_id: model_version.model.id,
|
||||
model_version_id: model_version.id,
|
||||
model_name: model_version.name,
|
||||
version_name: model_version.version,
|
||||
can_write_model_registry: can_write_model_registry?(user, project)
|
||||
}
|
||||
|
||||
to_json(data)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def can_write_model_registry?(user, project)
|
||||
|
|
|
|||
|
|
@ -3,12 +3,16 @@
|
|||
module ContainerRegistry
|
||||
module Protection
|
||||
class Rule < ApplicationRecord
|
||||
enum delete_protected_up_to_access_level:
|
||||
Gitlab::Access.sym_options_with_owner.slice(:maintainer, :owner, :developer),
|
||||
_prefix: :delete_protected_up_to
|
||||
enum push_protected_up_to_access_level:
|
||||
Gitlab::Access.sym_options_with_owner.slice(:maintainer, :owner, :developer),
|
||||
_prefix: :push_protected_up_to
|
||||
include IgnorableColumns
|
||||
ignore_columns %i[push_protected_up_to_access_level delete_protected_up_to_access_level],
|
||||
remove_with: '17.2', remove_after: '2024-06-22'
|
||||
|
||||
enum minimum_access_level_for_delete:
|
||||
Gitlab::Access.sym_options_with_admin.slice(:maintainer, :owner, :admin),
|
||||
_prefix: :minimum_access_level_for_delete
|
||||
enum minimum_access_level_for_push:
|
||||
Gitlab::Access.sym_options_with_admin.slice(:maintainer, :owner, :admin),
|
||||
_prefix: :minimum_access_level_for_push
|
||||
|
||||
belongs_to :project, inverse_of: :container_registry_protection_rules
|
||||
|
||||
|
|
@ -21,8 +25,8 @@ module ContainerRegistry
|
|||
message:
|
||||
->(_object, _data) { _('should be a valid container repository path with optional wildcard characters.') }
|
||||
}
|
||||
validates :delete_protected_up_to_access_level, presence: true
|
||||
validates :push_protected_up_to_access_level, presence: true
|
||||
validates :minimum_access_level_for_delete, presence: true
|
||||
validates :minimum_access_level_for_push, presence: true
|
||||
|
||||
validate :path_pattern_starts_with_project_full_path, if: :repository_path_pattern_changed?
|
||||
|
||||
|
|
@ -38,7 +42,7 @@ module ContainerRegistry
|
|||
def self.for_push_exists?(access_level:, repository_path:)
|
||||
return false if access_level.blank? || repository_path.blank?
|
||||
|
||||
where(push_protected_up_to_access_level: access_level..)
|
||||
where(':access_level < minimum_access_level_for_push', access_level: access_level)
|
||||
.for_repository_path(repository_path)
|
||||
.exists?
|
||||
end
|
||||
|
|
|
|||
|
|
@ -38,11 +38,9 @@ class Namespace < ApplicationRecord
|
|||
NUMBER_OF_ANCESTORS_ALLOWED = 20
|
||||
|
||||
SR_DISABLED_AND_UNOVERRIDABLE = 'disabled_and_unoverridable'
|
||||
# DISABLED_WITH_OVERRIDE is deprecated in favour of DISABLED_AND_OVERRIDABLE.
|
||||
SR_DISABLED_WITH_OVERRIDE = 'disabled_with_override'
|
||||
SR_DISABLED_AND_OVERRIDABLE = 'disabled_and_overridable'
|
||||
SR_ENABLED = 'enabled'
|
||||
SHARED_RUNNERS_SETTINGS = [SR_DISABLED_AND_UNOVERRIDABLE, SR_DISABLED_WITH_OVERRIDE, SR_DISABLED_AND_OVERRIDABLE, SR_ENABLED].freeze
|
||||
SHARED_RUNNERS_SETTINGS = [SR_DISABLED_AND_UNOVERRIDABLE, SR_DISABLED_AND_OVERRIDABLE, SR_ENABLED].freeze
|
||||
URL_MAX_LENGTH = 255
|
||||
|
||||
cache_markdown_field :description, pipeline: :description
|
||||
|
|
@ -631,10 +629,10 @@ class Namespace < ApplicationRecord
|
|||
case other_setting
|
||||
when SR_ENABLED
|
||||
false
|
||||
when SR_DISABLED_WITH_OVERRIDE, SR_DISABLED_AND_OVERRIDABLE
|
||||
when SR_DISABLED_AND_OVERRIDABLE
|
||||
shared_runners_setting == SR_ENABLED
|
||||
when SR_DISABLED_AND_UNOVERRIDABLE
|
||||
shared_runners_setting == SR_ENABLED || shared_runners_setting == SR_DISABLED_AND_OVERRIDABLE || shared_runners_setting == SR_DISABLED_WITH_OVERRIDE
|
||||
shared_runners_setting == SR_ENABLED || shared_runners_setting == SR_DISABLED_AND_OVERRIDABLE
|
||||
else
|
||||
raise ArgumentError
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ module ContainerRegistry
|
|||
class CreateRuleService < BaseService
|
||||
ALLOWED_ATTRIBUTES = %i[
|
||||
repository_path_pattern
|
||||
push_protected_up_to_access_level
|
||||
delete_protected_up_to_access_level
|
||||
minimum_access_level_for_push
|
||||
minimum_access_level_for_delete
|
||||
].freeze
|
||||
|
||||
def execute
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ module ContainerRegistry
|
|||
|
||||
ALLOWED_ATTRIBUTES = %i[
|
||||
repository_path_pattern
|
||||
delete_protected_up_to_access_level
|
||||
push_protected_up_to_access_level
|
||||
minimum_access_level_for_delete
|
||||
minimum_access_level_for_push
|
||||
].freeze
|
||||
|
||||
def initialize(container_registry_protection_rule, current_user:, params:)
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ module Groups
|
|||
case params[:shared_runners_setting]
|
||||
when Namespace::SR_DISABLED_AND_UNOVERRIDABLE
|
||||
set_shared_runners_enabled!(false)
|
||||
when Namespace::SR_DISABLED_WITH_OVERRIDE, Namespace::SR_DISABLED_AND_OVERRIDABLE
|
||||
when Namespace::SR_DISABLED_AND_OVERRIDABLE
|
||||
disable_shared_runners_and_allow_override!
|
||||
when Namespace::SR_ENABLED
|
||||
set_shared_runners_enabled!(true)
|
||||
|
|
|
|||
|
|
@ -3,4 +3,4 @@
|
|||
- breadcrumb_title @model_version.version
|
||||
- page_title "#{@model_version.name} / #{@model_version.version}"
|
||||
|
||||
= render(Projects::Ml::ShowMlModelVersionComponent.new(model_version: @model_version, current_user: current_user))
|
||||
#js-mount-show-ml-model-version{ data: { view_model: show_ml_model_version_data(@model_version, @current_user) } }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
name: internal_events_batching
|
||||
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/413064
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/149979
|
||||
rollout_issue_url:
|
||||
milestone: '17.0'
|
||||
group: group::analytics instrumentation
|
||||
type: ops
|
||||
default_enabled: true
|
||||
|
|
@ -1,15 +1,12 @@
|
|||
---
|
||||
data_category: optional
|
||||
key_path: search_unique_visits.search_unique_visits_for_any_target_monthly
|
||||
description: Total unique users for i_search_total, i_search_advanced, i_search_paid
|
||||
for recent 28 days. This metric is redundant because advanced will be a subset of
|
||||
paid and paid will be a subset of total. i_search_total is more appropriate if you
|
||||
just want the total
|
||||
description: Removed as duplicate of redis_hll_counters.search.search_total_unique_counts_monthly
|
||||
product_section: enablement
|
||||
product_stage: enablement
|
||||
product_group: global_search
|
||||
value_type: number
|
||||
status: active
|
||||
status: removed
|
||||
time_frame: 28d
|
||||
data_source: redis_hll
|
||||
instrumentation_class: RedisHLLMetric
|
||||
|
|
@ -28,3 +25,5 @@ tier:
|
|||
- premium
|
||||
- ultimate
|
||||
milestone: "<13.9"
|
||||
milestone_removed: "17.0"
|
||||
removed_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/150302
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
---
|
||||
data_category: optional
|
||||
key_path: search_unique_visits.i_search_total
|
||||
description: Calculated unique users to perform Basic or Advanced searches by week
|
||||
description: Removed as duplicate of redis_hll_counters.search.i_search_total_weekly
|
||||
product_section: enablement
|
||||
product_stage: enablement
|
||||
product_group: global_search
|
||||
value_type: number
|
||||
status: active
|
||||
status: removed
|
||||
time_frame: 7d
|
||||
data_source: redis_hll
|
||||
instrumentation_class: RedisHLLMetric
|
||||
|
|
@ -21,3 +21,5 @@ tier:
|
|||
- premium
|
||||
- ultimate
|
||||
milestone: "<13.9"
|
||||
milestone_removed: "17.0"
|
||||
removed_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/150302
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RenameContainerProtectionRulesProtectedAccessLevelToMinimumAccessLevel < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.0'
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
TABLE = :container_registry_protection_rules
|
||||
|
||||
def up
|
||||
rename_column_concurrently TABLE, :push_protected_up_to_access_level, :minimum_access_level_for_push
|
||||
rename_column_concurrently TABLE, :delete_protected_up_to_access_level, :minimum_access_level_for_delete
|
||||
end
|
||||
|
||||
def down
|
||||
undo_rename_column_concurrently TABLE, :push_protected_up_to_access_level, :minimum_access_level_for_push
|
||||
undo_rename_column_concurrently TABLE, :delete_protected_up_to_access_level, :minimum_access_level_for_delete
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class MigrateContainerProtectionRulesMinimumAccessLevel < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.0'
|
||||
|
||||
restrict_gitlab_migration gitlab_schema: :gitlab_main
|
||||
|
||||
DEVELOPER = 30
|
||||
MAINTAINER = 40
|
||||
OWNER = 50
|
||||
ADMIN = 60
|
||||
|
||||
class ContainerProtectionRule < MigrationRecord
|
||||
self.table_name = 'container_registry_protection_rules'
|
||||
end
|
||||
|
||||
def migrate_minimum_access_level_for_push(old_access_level, new_access_level)
|
||||
ContainerProtectionRule.where(push_protected_up_to_access_level: old_access_level)
|
||||
.update_all(minimum_access_level_for_push: new_access_level)
|
||||
end
|
||||
|
||||
def undo_migrate_minimum_access_level_for_push(old_access_level, new_access_level)
|
||||
ContainerProtectionRule.where(minimum_access_level_for_push: new_access_level)
|
||||
.update_all(push_protected_up_to_access_level: old_access_level)
|
||||
end
|
||||
|
||||
def migrate_minimum_access_level_for_delete(old_access_level, new_access_level)
|
||||
ContainerProtectionRule.where(delete_protected_up_to_access_level: old_access_level)
|
||||
.update_all(minimum_access_level_for_delete: new_access_level)
|
||||
end
|
||||
|
||||
def undo_migrate_minimum_access_level_for_delete(old_access_level, new_access_level)
|
||||
ContainerProtectionRule.where(minimum_access_level_for_delete: new_access_level)
|
||||
.update_all(delete_protected_up_to_access_level: old_access_level)
|
||||
end
|
||||
|
||||
def up
|
||||
migrate_minimum_access_level_for_push(OWNER, ADMIN)
|
||||
migrate_minimum_access_level_for_push(MAINTAINER, OWNER)
|
||||
migrate_minimum_access_level_for_push(DEVELOPER, MAINTAINER)
|
||||
|
||||
migrate_minimum_access_level_for_delete(OWNER, ADMIN)
|
||||
migrate_minimum_access_level_for_delete(MAINTAINER, OWNER)
|
||||
migrate_minimum_access_level_for_delete(DEVELOPER, MAINTAINER)
|
||||
end
|
||||
|
||||
def down
|
||||
undo_migrate_minimum_access_level_for_push(DEVELOPER, MAINTAINER)
|
||||
undo_migrate_minimum_access_level_for_push(MAINTAINER, OWNER)
|
||||
undo_migrate_minimum_access_level_for_push(OWNER, ADMIN)
|
||||
|
||||
undo_migrate_minimum_access_level_for_delete(DEVELOPER, MAINTAINER)
|
||||
undo_migrate_minimum_access_level_for_delete(MAINTAINER, OWNER)
|
||||
undo_migrate_minimum_access_level_for_delete(OWNER, ADMIN)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CleanupContainerRegistryProtectionRuleProtectedUpToAccessLevelsRename < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.0'
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
TABLE = :container_registry_protection_rules
|
||||
|
||||
def up
|
||||
cleanup_concurrent_column_rename TABLE, :push_protected_up_to_access_level, :minimum_access_level_for_push
|
||||
cleanup_concurrent_column_rename TABLE, :delete_protected_up_to_access_level, :minimum_access_level_for_delete
|
||||
end
|
||||
|
||||
def down
|
||||
undo_cleanup_concurrent_column_rename TABLE, :push_protected_up_to_access_level, :minimum_access_level_for_push
|
||||
undo_cleanup_concurrent_column_rename TABLE, :delete_protected_up_to_access_level, :minimum_access_level_for_delete
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
547893dac5a7f811804bf94201da966c8d3d71804851c66db37ef76d7bfacf42
|
||||
|
|
@ -0,0 +1 @@
|
|||
134ea93b7ebfcec84d0d5c5b19f1b2a1e11ff0d042c2bd52a86e680af85fa0dd
|
||||
|
|
@ -0,0 +1 @@
|
|||
a166217f45ce83e9c0b5aea3292fc6f97f8f21719f1eee0f3879a333884c206c
|
||||
|
|
@ -7418,11 +7418,13 @@ CREATE TABLE container_registry_protection_rules (
|
|||
project_id bigint NOT NULL,
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
updated_at timestamp with time zone NOT NULL,
|
||||
delete_protected_up_to_access_level smallint NOT NULL,
|
||||
push_protected_up_to_access_level smallint NOT NULL,
|
||||
repository_path_pattern text,
|
||||
minimum_access_level_for_push smallint,
|
||||
minimum_access_level_for_delete smallint,
|
||||
CONSTRAINT check_3658b31291 CHECK ((repository_path_pattern IS NOT NULL)),
|
||||
CONSTRAINT check_d53a270af5 CHECK ((char_length(repository_path_pattern) <= 255))
|
||||
CONSTRAINT check_d53a270af5 CHECK ((char_length(repository_path_pattern) <= 255)),
|
||||
CONSTRAINT check_d82c1eb825 CHECK ((minimum_access_level_for_delete IS NOT NULL)),
|
||||
CONSTRAINT check_f684912b48 CHECK ((minimum_access_level_for_push IS NOT NULL))
|
||||
);
|
||||
|
||||
CREATE SEQUENCE container_registry_protection_rules_id_seq
|
||||
|
|
|
|||
|
|
@ -2959,9 +2959,9 @@ Input type: `CreateContainerRegistryProtectionRuleInput`
|
|||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationcreatecontainerregistryprotectionruleclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationcreatecontainerregistryprotectionruledeleteprotecteduptoaccesslevel"></a>`deleteProtectedUpToAccessLevel` | [`ContainerRegistryProtectionRuleAccessLevel!`](#containerregistryprotectionruleaccesslevel) | Max GitLab access level to prevent from deleting container images in the container registry. For example `DEVELOPER`, `MAINTAINER`, `OWNER`. |
|
||||
| <a id="mutationcreatecontainerregistryprotectionruleminimumaccesslevelfordelete"></a>`minimumAccessLevelForDelete` | [`ContainerRegistryProtectionRuleAccessLevel!`](#containerregistryprotectionruleaccesslevel) | Minimum GitLab access level to allow to delete container images in the container registry. For example `MAINTAINER`, `OWNER`, or `ADMIN`. |
|
||||
| <a id="mutationcreatecontainerregistryprotectionruleminimumaccesslevelforpush"></a>`minimumAccessLevelForPush` | [`ContainerRegistryProtectionRuleAccessLevel!`](#containerregistryprotectionruleaccesslevel) | Minimum GitLab access level to allow to push container images to the container registry. For example `MAINTAINER`, `OWNER`, or `ADMIN`. |
|
||||
| <a id="mutationcreatecontainerregistryprotectionruleprojectpath"></a>`projectPath` | [`ID!`](#id) | Full path of the project where a protection rule is located. |
|
||||
| <a id="mutationcreatecontainerregistryprotectionrulepushprotecteduptoaccesslevel"></a>`pushProtectedUpToAccessLevel` | [`ContainerRegistryProtectionRuleAccessLevel!`](#containerregistryprotectionruleaccesslevel) | Max GitLab access level to prevent from pushing container images to the container registry. For example `DEVELOPER`, `MAINTAINER`, `OWNER`. |
|
||||
| <a id="mutationcreatecontainerregistryprotectionrulerepositorypathpattern"></a>`repositoryPathPattern` | [`String!`](#string) | Container repository path pattern protected by the protection rule. For example `my-project/my-container-*`. Wildcard character `*` allowed. |
|
||||
|
||||
#### Fields
|
||||
|
|
@ -8416,9 +8416,9 @@ Input type: `UpdateContainerRegistryProtectionRuleInput`
|
|||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationupdatecontainerregistryprotectionruleclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationupdatecontainerregistryprotectionruledeleteprotecteduptoaccesslevel"></a>`deleteProtectedUpToAccessLevel` | [`ContainerRegistryProtectionRuleAccessLevel`](#containerregistryprotectionruleaccesslevel) | Maximum GitLab access level prevented from deleting a container. For example, `DEVELOPER`, `MAINTAINER`, `OWNER`. |
|
||||
| <a id="mutationupdatecontainerregistryprotectionruleid"></a>`id` | [`ContainerRegistryProtectionRuleID!`](#containerregistryprotectionruleid) | Global ID of the container registry protection rule to be updated. |
|
||||
| <a id="mutationupdatecontainerregistryprotectionrulepushprotecteduptoaccesslevel"></a>`pushProtectedUpToAccessLevel` | [`ContainerRegistryProtectionRuleAccessLevel`](#containerregistryprotectionruleaccesslevel) | Maximum GitLab access level prevented from pushing a container. For example, `DEVELOPER`, `MAINTAINER`, `OWNER`. |
|
||||
| <a id="mutationupdatecontainerregistryprotectionruleminimumaccesslevelfordelete"></a>`minimumAccessLevelForDelete` | [`ContainerRegistryProtectionRuleAccessLevel`](#containerregistryprotectionruleaccesslevel) | Minimum GitLab access level allowed to delete container images to the container registry. For example, `DEVELOPER`, `MAINTAINER`, `OWNER`, or `ADMIN`. |
|
||||
| <a id="mutationupdatecontainerregistryprotectionruleminimumaccesslevelforpush"></a>`minimumAccessLevelForPush` | [`ContainerRegistryProtectionRuleAccessLevel`](#containerregistryprotectionruleaccesslevel) | Minimum GitLab access level allowed to push container images to the container registry. For example, `DEVELOPER`, `MAINTAINER`, `OWNER`, or `ADMIN`. |
|
||||
| <a id="mutationupdatecontainerregistryprotectionrulerepositorypathpattern"></a>`repositoryPathPattern` | [`String`](#string) | Container's repository path pattern of the protection rule. For example, `my-scope/my-project/container-dev-*`. Wildcard character `*` allowed. |
|
||||
|
||||
#### Fields
|
||||
|
|
@ -17373,7 +17373,7 @@ four standard [pagination arguments](#pagination-arguments):
|
|||
| <a id="cirunnerprojectspersonal"></a>`personal` | [`Boolean`](#boolean) | Return only personal projects. |
|
||||
| <a id="cirunnerprojectssearch"></a>`search` | [`String`](#string) | Search query, which can be for the project name, a path, or a description. |
|
||||
| <a id="cirunnerprojectssearchnamespaces"></a>`searchNamespaces` | [`Boolean`](#boolean) | Include namespace in project search. |
|
||||
| <a id="cirunnerprojectssort"></a>`sort` **{warning-solid}** | [`String`](#string) | **Deprecated** in GitLab 15.4. Default sort order will change in GitLab 17.0. Specify `"id_asc"` if you require the query results to be ordered by ascending IDs. |
|
||||
| <a id="cirunnerprojectssort"></a>`sort` | [`String`](#string) | Sort order of results. Format: `<field_name>_<sort_direction>`, for example: `id_desc` or `name_asc`. |
|
||||
| <a id="cirunnerprojectstopics"></a>`topics` | [`[String!]`](#string) | Filter projects by topics. |
|
||||
|
||||
##### `CiRunner.status`
|
||||
|
|
@ -18038,9 +18038,9 @@ A container registry protection rule designed to prevent users with a certain ac
|
|||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="containerregistryprotectionruledeleteprotecteduptoaccesslevel"></a>`deleteProtectedUpToAccessLevel` | [`ContainerRegistryProtectionRuleAccessLevel!`](#containerregistryprotectionruleaccesslevel) | Max GitLab access level to prevent from pushing container images to the container registry. For example `DEVELOPER`, `MAINTAINER`, `OWNER`. |
|
||||
| <a id="containerregistryprotectionruleid"></a>`id` | [`ContainerRegistryProtectionRuleID!`](#containerregistryprotectionruleid) | ID of the container registry protection rule. |
|
||||
| <a id="containerregistryprotectionrulepushprotecteduptoaccesslevel"></a>`pushProtectedUpToAccessLevel` | [`ContainerRegistryProtectionRuleAccessLevel!`](#containerregistryprotectionruleaccesslevel) | Max GitLab access level to prevent from pushing container images to the container registry. For example `DEVELOPER`, `MAINTAINER`, `OWNER`. |
|
||||
| <a id="containerregistryprotectionruleminimumaccesslevelfordelete"></a>`minimumAccessLevelForDelete` | [`ContainerRegistryProtectionRuleAccessLevel!`](#containerregistryprotectionruleaccesslevel) | Minimum GitLab access level to allow to push container images to the container registry. For example `DEVELOPER`, `MAINTAINER`, or `OWNER`. |
|
||||
| <a id="containerregistryprotectionruleminimumaccesslevelforpush"></a>`minimumAccessLevelForPush` | [`ContainerRegistryProtectionRuleAccessLevel!`](#containerregistryprotectionruleaccesslevel) | Minimum GitLab access level to allow to push container images to the container registry. For example `DEVELOPER`, `MAINTAINER`, or `OWNER`. |
|
||||
| <a id="containerregistryprotectionrulerepositorypathpattern"></a>`repositoryPathPattern` | [`String!`](#string) | Container repository path pattern protected by the protection rule. For example `my-project/my-container-*`. Wildcard character `*` allowed. |
|
||||
|
||||
### `ContainerRepository`
|
||||
|
|
@ -32288,7 +32288,7 @@ Access level of a container registry protection rule resource.
|
|||
|
||||
| Value | Description |
|
||||
| ----- | ----------- |
|
||||
| <a id="containerregistryprotectionruleaccessleveldeveloper"></a>`DEVELOPER` | Developer access. |
|
||||
| <a id="containerregistryprotectionruleaccessleveladmin"></a>`ADMIN` | Admin access. |
|
||||
| <a id="containerregistryprotectionruleaccesslevelmaintainer"></a>`MAINTAINER` | Maintainer access. |
|
||||
| <a id="containerregistryprotectionruleaccesslevelowner"></a>`OWNER` | Owner access. |
|
||||
|
||||
|
|
@ -33933,7 +33933,6 @@ How to format SHA strings.
|
|||
| ----- | ----------- |
|
||||
| <a id="sharedrunnerssettingdisabled_and_overridable"></a>`DISABLED_AND_OVERRIDABLE` | Sharing of runners is disabled and overridable. |
|
||||
| <a id="sharedrunnerssettingdisabled_and_unoverridable"></a>`DISABLED_AND_UNOVERRIDABLE` | Sharing of runners is disabled and unoverridable. |
|
||||
| <a id="sharedrunnerssettingdisabled_with_override"></a>`DISABLED_WITH_OVERRIDE` **{warning-solid}** | **Deprecated** in GitLab 17.0. This was renamed. Use: `disabled_and_overridable`. |
|
||||
| <a id="sharedrunnerssettingenabled"></a>`ENABLED` | Sharing of runners is enabled. |
|
||||
|
||||
### `SnippetBlobActionEnum`
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ that should be kept confidential. Examples of a secret include:
|
|||
|
||||
Secrets that are the most sensitive and under the strictest policies should be stored
|
||||
in a secrets manager. When using a secrets manager solution, secrets are stored outside
|
||||
of the GitLab instance. There are a number of providres in this space, including
|
||||
of the GitLab instance. There are a number of providers in this space, including
|
||||
[HashiCorp's Vault](https://www.vaultproject.io), [Azure Key Vault](https://azure.microsoft.com/en-us/products/key-vault),
|
||||
and [Google Cloud Secret Manager](https://cloud.google.com/security/products/secret-manager).
|
||||
|
||||
|
|
|
|||
|
|
@ -37,11 +37,12 @@ If you have any new or updated prompts, ask members of AI Framework team to revi
|
|||
When working with Chat locally, you might run into an error. Most commons problems are documented in this section.
|
||||
If you find an undocumented issue, you should document it in this section after you find a solution.
|
||||
|
||||
| Problem | Solution |
|
||||
| ----------------------------------------------------- | -------- |
|
||||
| There is no Chat button in the GitLab UI. | Make sure your user is a part of a group with enabled Experimental and Beta features. |
|
||||
| Chat replies with "Forbidden by auth provider" error. | Backend can't access LLMs. Make sure your [AI Gateway](index.md#local-setup) is setup correctly. |
|
||||
| Requests takes too long to appear in UI | Consider restarting Sidekiq by running `gdk restart rails-background-jobs`. If that doesn't work, try `gdk kill` and then `gdk start`. Alternatively, you can bypass Sidekiq entirely. To do that temporary alter `Llm::CompletionWorker.perform_async` statements with `Llm::CompletionWorker.perform_inline` |
|
||||
| Problem | Solution |
|
||||
|-----------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| There is no Chat button in the GitLab UI. | Make sure your user is a part of a group with enabled Experimental and Beta features. |
|
||||
| Chat replies with "Forbidden by auth provider" error. | Backend can't access LLMs. Make sure your [AI Gateway](index.md#local-setup) is set up correctly. |
|
||||
| Requests take too long to appear in UI | Consider restarting Sidekiq by running `gdk restart rails-background-jobs`. If that doesn't work, try `gdk kill` and then `gdk start`. Alternatively, you can bypass Sidekiq entirely. To do that temporary alter `Llm::CompletionWorker.perform_async` statements with `Llm::CompletionWorker.perform_inline` |
|
||||
| There is no chat button in GitLab UI when GDK is running on non-SaaS mode | You do not have cloud connector access token record or seat assigned. To create cloud connector access record, in rails console put following code: `CloudConnector::Access.new(data: { available_services: [{ name: "duo_chat", serviceStartTime: ":date_in_the_future" }] }).save`. |
|
||||
|
||||
## Contributing to GitLab Duo Chat
|
||||
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ Prerequisites:
|
|||
1. Select **New role**.
|
||||
1. In **Base role to use as template**, select an existing default role.
|
||||
1. In **Role name**, enter the custom role's title.
|
||||
1. Optional. In **Description**, enter a description for the custom role. 255 characters max.
|
||||
1. In **Description**, enter a description for the custom role. 255 characters max.
|
||||
1. Select the **Permissions** for the new custom role.
|
||||
1. Select **Create role**.
|
||||
|
||||
|
|
@ -80,7 +80,7 @@ After you create a custom role for your self-managed instance, you can assign th
|
|||
1. Select **New role**.
|
||||
1. In **Base role to use as template**, select an existing default role.
|
||||
1. In **Role name**, enter the custom role's title.
|
||||
1. Optional. In **Description**, enter a description for the custom role. 255 characters max.
|
||||
1. In **Description**, enter a description for the custom role. 255 characters max.
|
||||
1. Select the **Permissions** for the new custom role.
|
||||
1. Select **Create role**.
|
||||
|
||||
|
|
@ -93,6 +93,36 @@ In **Settings > Roles and Permissions**, the list of all custom roles displays t
|
|||
|
||||
To create a custom role, you can also [use the API](../api/graphql/reference/index.md#mutationmemberrolecreate).
|
||||
|
||||
## Edit a custom role
|
||||
|
||||
Custom roles can be edited after they are created. The base role can't be changed. If you need to change the base role, you will need to create a new custom role.
|
||||
|
||||
### GitLab SaaS
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- You must have the Owner role for the group.
|
||||
|
||||
1. On the left sidebar, select **Search or go to** and find your group.
|
||||
1. Select **Settings > Roles and Permissions**.
|
||||
1. Select the three dots for the custom role, then select **Edit role**.
|
||||
1. Modify the role as needed.
|
||||
1. Select **Save role** to update the role.
|
||||
|
||||
### GitLab self-managed
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- You must be an administrator for the self-managed instance.
|
||||
|
||||
1. On the left sidebar, at the bottom, select **Admin Area**.
|
||||
1. Select **Settings > Roles and Permissions**.
|
||||
1. Select the three dots for the custom role, then select **Edit role**.
|
||||
1. Modify the role as needed.
|
||||
1. Select **Save role** to update the role.
|
||||
|
||||
To edit a custom role, you can also [use the API](../api/graphql/reference/index.md#mutationmemberroleupdate).
|
||||
|
||||
## Delete the custom role
|
||||
|
||||
Prerequisites:
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ To create a group:
|
|||
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
|
||||
For details about groups, watch [GitLab Namespaces (users, groups and subgroups)](https://youtu.be/r0sJgjR2f5A).
|
||||
|
||||
## Edit group name and description
|
||||
## Edit group name, description, and avatar
|
||||
|
||||
You can edit your group details from the group general settings.
|
||||
|
||||
|
|
@ -148,6 +148,7 @@ To edit group details:
|
|||
1. In the **Group name** text box, enter your group name. See the [limitations on group names](../../user/reserved_names.md).
|
||||
1. Optional. In the **Group description (optional)** text box, enter your group description.
|
||||
The description is limited to 500 characters.
|
||||
1. Optional. Under **Group avatar**, select **Choose file**, then select an image. The ideal image size is 192 x 192 pixels, and the maximum file size allowed is 200 KB.
|
||||
1. Select **Save changes**.
|
||||
|
||||
## Leave a group
|
||||
|
|
|
|||
|
|
@ -407,6 +407,33 @@ To view your activity:
|
|||
- **Designs**: Designs you added, updated, and removed in your projects.
|
||||
- **Team**: Projects you joined and left.
|
||||
|
||||
## Sign-in services
|
||||
|
||||
Instead of using a regular username and password to sign in to GitLab, you can use a sign-in service instead.
|
||||
|
||||
### Connect a sign-in service
|
||||
|
||||
To connect a sign-in service to use for signing in to GitLab:
|
||||
|
||||
1. On the left sidebar, select your avatar.
|
||||
1. Select **Edit profile**.
|
||||
1. Select **Account**.
|
||||
1. Locate the **Service sign-in** section.
|
||||
1. Under the **Connected Accounts** section, select the button that corresponds with the service you want to sign in
|
||||
with.
|
||||
1. Follow the instructions for the selected service to start signing in with it.
|
||||
|
||||
### Disconnect a sign-in service
|
||||
|
||||
To disconnect a sign-in service used for signing in to GitLab:
|
||||
|
||||
1. On the left sidebar, select your avatar.
|
||||
1. Select **Edit profile**.
|
||||
1. Select **Account**.
|
||||
1. Locate the **Service sign-in** section.
|
||||
1. Under the **Connected Accounts** section, select **Disconnect** next to the button that corresponds with the service
|
||||
you no longer want to sign in with.
|
||||
|
||||
## Session duration
|
||||
|
||||
### Stay signed in for two weeks
|
||||
|
|
|
|||
|
|
@ -52,13 +52,11 @@ module API
|
|||
end
|
||||
|
||||
# Stores some Git-specific env thread-safely
|
||||
env = parse_env
|
||||
Gitlab::Git::HookEnv.set(gl_repository, env) if container
|
||||
|
||||
#
|
||||
# Snapshot repositories have different relative path than the main repository. For access
|
||||
# checks that need quarantined objects the relative path in also sent with Gitaly RPCs
|
||||
# calls as a header.
|
||||
populate_relative_path(params[:relative_path])
|
||||
Gitlab::Git::HookEnv.set(gl_repository, params[:relative_path], parse_env) if container
|
||||
|
||||
actor.update_last_used_at!
|
||||
|
||||
|
|
@ -104,12 +102,6 @@ module API
|
|||
end
|
||||
# rubocop: enable Metrics/AbcSize
|
||||
|
||||
def populate_relative_path(relative_path)
|
||||
return unless Gitlab::SafeRequestStore.active?
|
||||
|
||||
Gitlab::SafeRequestStore[:gitlab_git_relative_path] = relative_path
|
||||
end
|
||||
|
||||
def validate_actor(actor)
|
||||
return 'Could not find the given key' unless actor.key
|
||||
|
||||
|
|
|
|||
|
|
@ -75,6 +75,10 @@ module Gitlab
|
|||
sym_options.merge(owner: OWNER)
|
||||
end
|
||||
|
||||
def sym_options_with_admin
|
||||
sym_options_with_owner.merge(admin: ADMIN)
|
||||
end
|
||||
|
||||
def protection_options
|
||||
[
|
||||
{
|
||||
|
|
|
|||
|
|
@ -19,13 +19,28 @@ module Gitlab
|
|||
GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE
|
||||
].freeze
|
||||
|
||||
def self.set(gl_repository, env)
|
||||
# set stores the quarantining variables into request store.
|
||||
#
|
||||
# relative_path is sent from Gitaly to Rails when invoking internal API. In production it points to the
|
||||
# transaction's snapshot repository. Tests should pass the original relative path of the repository as
|
||||
# Gitaly is stubbed out from the invokation loop and doesn't create a transaction snapshot.
|
||||
def self.set(gl_repository, relative_path, env)
|
||||
return unless Gitlab::SafeRequestStore.active?
|
||||
|
||||
raise "missing gl_repository" if gl_repository.blank?
|
||||
|
||||
Gitlab::SafeRequestStore[:gitlab_git_env] ||= {}
|
||||
Gitlab::SafeRequestStore[:gitlab_git_env][gl_repository] = allowlist_git_env(env)
|
||||
Gitlab::SafeRequestStore[:gitlab_git_relative_path] = relative_path
|
||||
end
|
||||
|
||||
# get_relative_path returns the relative path of the repository this hook call is triggered for.
|
||||
# This is the repository's relative path in the transaction's snapshot and is passed back to Gitaly
|
||||
# in quarantined calls.
|
||||
def self.get_relative_path
|
||||
return unless Gitlab::SafeRequestStore.active?
|
||||
|
||||
Gitlab::SafeRequestStore.fetch(:gitlab_git_relative_path)
|
||||
end
|
||||
|
||||
def self.all(gl_repository)
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ module Gitlab
|
|||
InvalidPropertyTypeError = Class.new(StandardError)
|
||||
|
||||
SNOWPLOW_EMITTER_BUFFER_SIZE = 100
|
||||
DEFAULT_BUFFER_SIZE = 1
|
||||
ALLOWED_ADDITIONAL_PROPERTIES = {
|
||||
label: [String],
|
||||
property: [String],
|
||||
|
|
@ -161,7 +162,8 @@ module Gitlab
|
|||
|
||||
return unless app_id.present? && host.present?
|
||||
|
||||
GitlabSDK::Client.new(app_id: app_id, host: host, buffer_size: SNOWPLOW_EMITTER_BUFFER_SIZE)
|
||||
buffer_size = Feature.enabled?(:internal_events_batching) ? SNOWPLOW_EMITTER_BUFFER_SIZE : DEFAULT_BUFFER_SIZE
|
||||
GitlabSDK::Client.new(app_id: app_id, host: host, buffer_size: buffer_size)
|
||||
end
|
||||
strong_memoize_attr :gitlab_sdk_client
|
||||
end
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ module Gitlab
|
|||
return @client.push(job_hash) unless Gitlab::SidekiqSharding::Router.enabled?
|
||||
|
||||
job_class = job_hash["class"].to_s.safe_constantize
|
||||
store_name = if job_class.nil? || job_class.ancestors.exclude?(ApplicationWorker)
|
||||
store_name = if unroutable_class?(job_class)
|
||||
'main'
|
||||
else
|
||||
job_class.get_sidekiq_options['store']
|
||||
|
|
@ -25,6 +25,15 @@ module Gitlab
|
|||
_, pool = Gitlab::SidekiqSharding::Router.get_shard_instance(store_name)
|
||||
Sidekiq::Client.new(config: @config, pool: pool).push(job_hash)
|
||||
end
|
||||
|
||||
def unroutable_class?(klass)
|
||||
klass.nil? ||
|
||||
(klass.ancestors.exclude?(ApplicationWorker) &&
|
||||
# ActionMailer's ActiveJob pushes a job hash with
|
||||
# class: ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper into
|
||||
# the schedule set.
|
||||
klass != ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper)
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(container)
|
||||
|
|
|
|||
|
|
@ -3352,6 +3352,9 @@ msgstr ""
|
|||
msgid "Adjust how frequently the GitLab UI polls for updates."
|
||||
msgstr ""
|
||||
|
||||
msgid "Admin"
|
||||
msgstr ""
|
||||
|
||||
msgid "Admin Area"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -13970,9 +13973,6 @@ msgstr ""
|
|||
msgid "ContainerRegistry|Delete image repository?"
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|Delete protected up to access level"
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|Delete rule"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -14039,10 +14039,10 @@ msgstr ""
|
|||
msgid "ContainerRegistry|Manifest digest: %{digest}"
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|Maximum access level prevented from deleting"
|
||||
msgid "ContainerRegistry|Minimum access level for delete"
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|Maximum access level prevented from pushing"
|
||||
msgid "ContainerRegistry|Minimum access level for push"
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|Missing or insufficient permission, delete button disabled"
|
||||
|
|
@ -14078,9 +14078,6 @@ msgstr ""
|
|||
msgid "ContainerRegistry|Push an image"
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|Push protected up to access level"
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|Remember to run %{docLinkStart}garbage collection%{docLinkEnd} to remove the stale data from storage."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ require 'optparse'
|
|||
require 'open3'
|
||||
require 'fileutils'
|
||||
require 'uri'
|
||||
require 'rainbow/ext/string'
|
||||
|
||||
class SchemaRegenerator
|
||||
##
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ FactoryBot.define do
|
|||
factory :container_registry_protection_rule, class: 'ContainerRegistry::Protection::Rule' do
|
||||
project
|
||||
repository_path_pattern { project.full_path }
|
||||
delete_protected_up_to_access_level { :developer }
|
||||
push_protected_up_to_access_level { :developer }
|
||||
minimum_access_level_for_delete { :maintainer }
|
||||
minimum_access_level_for_push { :maintainer }
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -74,14 +74,6 @@ describe('Batch comments mutations', () => {
|
|||
});
|
||||
|
||||
describe(types.RECEIVE_PUBLISH_REVIEW_SUCCESS, () => {
|
||||
it('resets drafts', () => {
|
||||
state.drafts.push('test');
|
||||
|
||||
mutations[types.RECEIVE_PUBLISH_REVIEW_SUCCESS](state);
|
||||
|
||||
expect(state.drafts).toEqual([]);
|
||||
});
|
||||
|
||||
it('sets isPublishing to false', () => {
|
||||
state.isPublishing = true;
|
||||
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ describe('RunnerProjects', () => {
|
|||
expect(mockRunnerProjectsQuery).toHaveBeenCalledWith({
|
||||
id: mockRunner.id,
|
||||
search: '',
|
||||
sort: 'ID_ASC',
|
||||
first: RUNNER_DETAILS_PROJECTS_PAGE_SIZE,
|
||||
});
|
||||
});
|
||||
|
|
@ -108,7 +109,6 @@ describe('RunnerProjects', () => {
|
|||
name,
|
||||
fullName: nameWithNamespace,
|
||||
avatarUrl,
|
||||
isOwner: true, // first project is always owner
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -124,6 +124,7 @@ describe('RunnerProjects', () => {
|
|||
expect(mockRunnerProjectsQuery).toHaveBeenLastCalledWith({
|
||||
id: mockRunner.id,
|
||||
search: '',
|
||||
sort: 'ID_ASC',
|
||||
first: RUNNER_DETAILS_PROJECTS_PAGE_SIZE,
|
||||
after: 'AFTER_CURSOR',
|
||||
});
|
||||
|
|
@ -138,6 +139,7 @@ describe('RunnerProjects', () => {
|
|||
expect(mockRunnerProjectsQuery).toHaveBeenLastCalledWith({
|
||||
id: mockRunner.id,
|
||||
search: '',
|
||||
sort: 'ID_ASC',
|
||||
last: RUNNER_DETAILS_PROJECTS_PAGE_SIZE,
|
||||
before: 'BEFORE_CURSOR',
|
||||
});
|
||||
|
|
@ -151,6 +153,7 @@ describe('RunnerProjects', () => {
|
|||
expect(mockRunnerProjectsQuery).toHaveBeenLastCalledWith({
|
||||
id: mockRunner.id,
|
||||
search: 'my search',
|
||||
sort: 'ID_ASC',
|
||||
first: RUNNER_DETAILS_PROJECTS_PAGE_SIZE,
|
||||
});
|
||||
});
|
||||
|
|
@ -167,6 +170,7 @@ describe('RunnerProjects', () => {
|
|||
expect(mockRunnerProjectsQuery).toHaveBeenLastCalledWith({
|
||||
id: mockRunner.id,
|
||||
search: 'my search',
|
||||
sort: 'ID_ASC',
|
||||
first: RUNNER_DETAILS_PROJECTS_PAGE_SIZE,
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -24,8 +24,6 @@ const defaultMockDiscussion = {
|
|||
notes,
|
||||
};
|
||||
|
||||
const DEFAULT_TODO_COUNT = 2;
|
||||
|
||||
describe('Design discussions component', () => {
|
||||
let wrapper;
|
||||
|
||||
|
|
@ -175,9 +173,7 @@ describe('Design discussions component', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
dispatchEventSpy = jest.spyOn(document, 'dispatchEvent');
|
||||
jest.spyOn(document, 'querySelector').mockReturnValue({
|
||||
innerText: DEFAULT_TODO_COUNT,
|
||||
});
|
||||
|
||||
createComponent({
|
||||
props: {
|
||||
discussion: {
|
||||
|
|
@ -229,7 +225,7 @@ describe('Design discussions component', () => {
|
|||
const dispatchedEvent = dispatchEventSpy.mock.calls[0][0];
|
||||
|
||||
expect(dispatchEventSpy).toHaveBeenCalledTimes(1);
|
||||
expect(dispatchedEvent.detail).toEqual({ count: DEFAULT_TODO_COUNT });
|
||||
expect(dispatchedEvent.detail).toEqual({ delta: 0 });
|
||||
expect(dispatchedEvent.type).toBe('todo:toggle');
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -70,9 +70,6 @@ describe('Design management design todo button', () => {
|
|||
|
||||
beforeEach(async () => {
|
||||
dispatchEventSpy = jest.spyOn(document, 'dispatchEvent');
|
||||
jest.spyOn(document, 'querySelector').mockReturnValue({
|
||||
innerText: 2,
|
||||
});
|
||||
|
||||
createComponent({ design: mockDesignWithPendingTodos }, { mountFn: mount });
|
||||
wrapper.trigger('click');
|
||||
|
|
@ -96,7 +93,7 @@ describe('Design management design todo button', () => {
|
|||
const dispatchedEvent = dispatchEventSpy.mock.calls[0][0];
|
||||
|
||||
expect(dispatchEventSpy).toHaveBeenCalledTimes(1);
|
||||
expect(dispatchedEvent.detail).toEqual({ count: 1 });
|
||||
expect(dispatchedEvent.detail).toEqual({ delta: -1 });
|
||||
expect(dispatchedEvent.type).toBe('todo:toggle');
|
||||
});
|
||||
});
|
||||
|
|
@ -116,9 +113,6 @@ describe('Design management design todo button', () => {
|
|||
|
||||
beforeEach(async () => {
|
||||
dispatchEventSpy = jest.spyOn(document, 'dispatchEvent');
|
||||
jest.spyOn(document, 'querySelector').mockReturnValue({
|
||||
innerText: 2,
|
||||
});
|
||||
|
||||
createComponent({}, { mountFn: mount });
|
||||
wrapper.trigger('click');
|
||||
|
|
@ -147,7 +141,7 @@ describe('Design management design todo button', () => {
|
|||
const dispatchedEvent = dispatchEventSpy.mock.calls[0][0];
|
||||
|
||||
expect(dispatchEventSpy).toHaveBeenCalledTimes(1);
|
||||
expect(dispatchedEvent.detail).toEqual({ count: 3 });
|
||||
expect(dispatchedEvent.detail).toEqual({ delta: 1 });
|
||||
expect(dispatchedEvent.type).toBe('todo:toggle');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,25 +1,84 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import Vue from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import { ShowMlModelVersion } from '~/ml/model_registry/apps';
|
||||
import ModelVersionDetail from '~/ml/model_registry/components/model_version_detail.vue';
|
||||
import TitleArea from '~/vue_shared/components/registry/title_area.vue';
|
||||
import { MODEL_VERSION } from '../mock_data';
|
||||
import LoadOrErrorOrShow from '~/ml/model_registry/components/load_or_error_or_show.vue';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import getModelVersionQuery from '~/ml/model_registry/graphql/queries/get_model_version.query.graphql';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import * as Sentry from '~/sentry/sentry_browser_wrapper';
|
||||
import { modelVersionQuery, modelVersionWithCandidate } from '../graphql_mock_data';
|
||||
|
||||
let wrapper;
|
||||
const createWrapper = () => {
|
||||
wrapper = shallowMount(ShowMlModelVersion, { propsData: { modelVersion: MODEL_VERSION } });
|
||||
};
|
||||
|
||||
const findTitleArea = () => wrapper.findComponent(TitleArea);
|
||||
const findModelVersionDetail = () => wrapper.findComponent(ModelVersionDetail);
|
||||
Vue.use(VueApollo);
|
||||
|
||||
describe('ml/model_registry/apps/show_model_version.vue', () => {
|
||||
beforeEach(() => createWrapper());
|
||||
let wrapper;
|
||||
let apolloProvider;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.spyOn(Sentry, 'captureException').mockImplementation();
|
||||
});
|
||||
|
||||
const createWrapper = (resolver = jest.fn().mockResolvedValue(modelVersionQuery)) => {
|
||||
const requestHandlers = [[getModelVersionQuery, resolver]];
|
||||
apolloProvider = createMockApollo(requestHandlers);
|
||||
|
||||
wrapper = mount(ShowMlModelVersion, {
|
||||
propsData: {
|
||||
modelName: 'blah',
|
||||
versionName: '1.2.3',
|
||||
modelId: 1,
|
||||
modelVersionId: 2,
|
||||
projectPath: 'path/to/project',
|
||||
},
|
||||
apolloProvider,
|
||||
});
|
||||
};
|
||||
|
||||
const findTitleArea = () => wrapper.findComponent(TitleArea);
|
||||
const findModelVersionDetail = () => wrapper.findComponent(ModelVersionDetail);
|
||||
const findLoadOrErrorOrShow = () => wrapper.findComponent(LoadOrErrorOrShow);
|
||||
|
||||
it('renders the title', () => {
|
||||
createWrapper();
|
||||
|
||||
expect(findTitleArea().props('title')).toBe('blah / 1.2.3');
|
||||
});
|
||||
|
||||
it('renders the model version detail', () => {
|
||||
expect(findModelVersionDetail().props('modelVersion')).toBe(MODEL_VERSION);
|
||||
it('Requests data with the right parameters', async () => {
|
||||
const resolver = jest.fn().mockResolvedValue(modelVersionQuery);
|
||||
|
||||
createWrapper(resolver);
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(resolver).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
modelId: 'gid://gitlab/Ml::Model/1',
|
||||
modelVersionId: 'gid://gitlab/Ml::ModelVersion/2',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('Displays data when loaded', async () => {
|
||||
createWrapper();
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(findModelVersionDetail().props('modelVersion')).toMatchObject(modelVersionWithCandidate);
|
||||
});
|
||||
|
||||
it('Shows error message on error', async () => {
|
||||
const error = new Error('Failure!');
|
||||
createWrapper(jest.fn().mockRejectedValue(error));
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(findLoadOrErrorOrShow().props('errorMessage')).toBe(
|
||||
'Failed to load model versions with error: Failure!',
|
||||
);
|
||||
expect(Sentry.captureException).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,62 +5,125 @@ import ModelVersionDetail from '~/ml/model_registry/components/model_version_det
|
|||
import PackageFiles from '~/packages_and_registries/package_registry/components/details/package_files.vue';
|
||||
import CandidateDetail from '~/ml/model_registry/components/candidate_detail.vue';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import { convertCandidateFromGraphql } from '~/ml/model_registry/utils';
|
||||
import { modelVersionWithCandidate } from '../graphql_mock_data';
|
||||
import { makeModelVersion, MODEL_VERSION } from '../mock_data';
|
||||
|
||||
Vue.use(VueApollo);
|
||||
|
||||
const makeGraphqlModelVersion = (overrides = {}) => {
|
||||
return { ...modelVersionWithCandidate, ...overrides };
|
||||
};
|
||||
|
||||
let wrapper;
|
||||
const createWrapper = (modelVersion = MODEL_VERSION) => {
|
||||
const createWrapper = (modelVersion = modelVersionWithCandidate) => {
|
||||
const apolloProvider = createMockApollo([]);
|
||||
wrapper = shallowMount(ModelVersionDetail, { apolloProvider, propsData: { modelVersion } });
|
||||
wrapper = shallowMount(ModelVersionDetail, {
|
||||
apolloProvider,
|
||||
propsData: { modelVersion },
|
||||
provide: {
|
||||
projectPath: 'path/to/project',
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const findPackageFiles = () => wrapper.findComponent(PackageFiles);
|
||||
const findCandidateDetail = () => wrapper.findComponent(CandidateDetail);
|
||||
|
||||
describe('ml/model_registry/components/model_version_detail.vue', () => {
|
||||
describe('base behaviour', () => {
|
||||
beforeEach(() => createWrapper());
|
||||
describe('When passing modelVersion passed on page load', () => {
|
||||
describe('base behaviour', () => {
|
||||
beforeEach(() => createWrapper(MODEL_VERSION));
|
||||
|
||||
it('shows the description', () => {
|
||||
expect(wrapper.text()).toContain(MODEL_VERSION.description);
|
||||
it('shows the description', () => {
|
||||
expect(wrapper.text()).toContain(MODEL_VERSION.description);
|
||||
});
|
||||
|
||||
it('shows the candidate', () => {
|
||||
expect(findCandidateDetail().props('candidate')).toMatchObject(MODEL_VERSION.candidate);
|
||||
});
|
||||
|
||||
it('shows the mlflow label string', () => {
|
||||
expect(wrapper.text()).toContain('MLflow run ID');
|
||||
});
|
||||
|
||||
it('shows the mlflow id', () => {
|
||||
expect(wrapper.text()).toContain(MODEL_VERSION.candidate.info.eid);
|
||||
});
|
||||
|
||||
it('renders files', () => {
|
||||
expect(findPackageFiles().props()).toEqual({
|
||||
packageId: 'gid://gitlab/Packages::Package/12',
|
||||
projectPath: 'path/to/project',
|
||||
packageType: 'ml_model',
|
||||
canDelete: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('shows the candidate', () => {
|
||||
expect(findCandidateDetail().props('candidate')).toBe(MODEL_VERSION.candidate);
|
||||
describe('if package does not exist', () => {
|
||||
beforeEach(() => createWrapper(makeModelVersion({ packageId: 0 })));
|
||||
|
||||
it('does not render files', () => {
|
||||
expect(findPackageFiles().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('shows the mlflow label string', () => {
|
||||
expect(wrapper.text()).toContain('MLflow run ID');
|
||||
});
|
||||
describe('if model version does not have description', () => {
|
||||
beforeEach(() => createWrapper(makeModelVersion({ description: null })));
|
||||
|
||||
it('shows the mlflow id', () => {
|
||||
expect(wrapper.text()).toContain(MODEL_VERSION.candidate.info.eid);
|
||||
});
|
||||
|
||||
it('renders files', () => {
|
||||
expect(findPackageFiles().props()).toEqual({
|
||||
packageId: 'gid://gitlab/Packages::Package/12',
|
||||
projectPath: MODEL_VERSION.projectPath,
|
||||
packageType: 'ml_model',
|
||||
canDelete: false,
|
||||
it('renders no description provided label', () => {
|
||||
expect(wrapper.text()).toContain('No description provided');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('if package does not exist', () => {
|
||||
beforeEach(() => createWrapper(makeModelVersion({ packageId: 0 })));
|
||||
describe('When passing modelVersion fetched from graphql', () => {
|
||||
describe('base behaviour', () => {
|
||||
beforeEach(() => createWrapper());
|
||||
|
||||
it('does not render files', () => {
|
||||
expect(findPackageFiles().exists()).toBe(false);
|
||||
it('shows the description', () => {
|
||||
expect(wrapper.text()).toContain('A model version description');
|
||||
});
|
||||
|
||||
it('shows the candidate', () => {
|
||||
expect(findCandidateDetail().props('candidate')).toMatchObject(
|
||||
convertCandidateFromGraphql(modelVersionWithCandidate.candidate),
|
||||
);
|
||||
});
|
||||
|
||||
it('shows the mlflow label string', () => {
|
||||
expect(wrapper.text()).toContain('MLflow run ID');
|
||||
});
|
||||
|
||||
it('shows the mlflow id', () => {
|
||||
expect(wrapper.text()).toContain(modelVersionWithCandidate.candidate.eid);
|
||||
});
|
||||
|
||||
it('renders files', () => {
|
||||
expect(findPackageFiles().props()).toEqual({
|
||||
packageId: 'gid://gitlab/Packages::Package/12',
|
||||
projectPath: 'path/to/project',
|
||||
packageType: 'ml_model',
|
||||
canDelete: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('if model version does not have description', () => {
|
||||
beforeEach(() => createWrapper(makeModelVersion({ description: null })));
|
||||
describe('if package does not exist', () => {
|
||||
beforeEach(() => createWrapper(makeGraphqlModelVersion({ packageId: 0 })));
|
||||
|
||||
it('renders no description provided label', () => {
|
||||
expect(wrapper.text()).toContain('No description provided');
|
||||
it('does not render files', () => {
|
||||
expect(findPackageFiles().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('if model version does not have description', () => {
|
||||
beforeEach(() => createWrapper(makeGraphqlModelVersion({ description: null })));
|
||||
|
||||
it('renders no description provided label', () => {
|
||||
expect(wrapper.text()).toContain('No description provided');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -41,6 +41,78 @@ export const modelVersionsQuery = (versions = graphqlModelVersions) => ({
|
|||
},
|
||||
});
|
||||
|
||||
export const candidate = {
|
||||
id: 'gid://gitlab/Ml::Candidate/1',
|
||||
name: 'hare-zebra-cobra-9745',
|
||||
iid: 1,
|
||||
eid: 'e9a71521-45c6-4b0a-b0c3-21f0b4528a5c',
|
||||
status: 'running',
|
||||
params: {
|
||||
nodes: [
|
||||
{
|
||||
id: 'gid://gitlab/Ml::CandidateParam/1',
|
||||
name: 'param1',
|
||||
value: 'value1',
|
||||
},
|
||||
],
|
||||
},
|
||||
metadata: {
|
||||
nodes: [
|
||||
{
|
||||
id: 'gid://gitlab/Ml::CandidateMetadata/1',
|
||||
name: 'metadata1',
|
||||
value: 'metadataValue1',
|
||||
},
|
||||
],
|
||||
},
|
||||
metrics: {
|
||||
nodes: [
|
||||
{
|
||||
id: 'gid://gitlab/Ml::CandidateMetric/1',
|
||||
name: 'metric1',
|
||||
value: 0.3,
|
||||
step: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
ciJob: {
|
||||
id: 'gid://gitlab/Ci::Build/1',
|
||||
webPath: '/gitlab-org/gitlab-test/-/jobs/1',
|
||||
name: 'build:linux',
|
||||
pipeline: {
|
||||
id: 'gid://gitlab/Ci::Pipeline/1',
|
||||
mergeRequest: {
|
||||
id: 'gid://gitlab/MergeRequest/1',
|
||||
title: 'Merge Request 1',
|
||||
webUrl: 'path/to/mr',
|
||||
iid: 1,
|
||||
},
|
||||
user: {
|
||||
id: 'gid://gitlab/User/1',
|
||||
avatarUrl: 'path/to/avatar',
|
||||
webUrl: 'path/to/user/1',
|
||||
username: 'user1',
|
||||
name: 'User 1',
|
||||
},
|
||||
},
|
||||
},
|
||||
_links: {
|
||||
showPath: '/root/test-project/-/ml/candidates/1',
|
||||
artifactPath: '/root/test-project/-/packages/1',
|
||||
},
|
||||
};
|
||||
|
||||
export const modelVersionWithCandidate = {
|
||||
id: 'gid://gitlab/Ml::ModelVersion/1',
|
||||
version: '1.0.4999',
|
||||
packageId: 'gid://gitlab/Packages::Package/12',
|
||||
description: 'A model version description',
|
||||
candidate,
|
||||
_links: {
|
||||
showPath: '/root/test-project/-/ml/models/1/versions/5000',
|
||||
},
|
||||
};
|
||||
|
||||
export const graphqlCandidates = [
|
||||
{
|
||||
id: 'gid://gitlab/Ml::Candidate/1',
|
||||
|
|
@ -201,6 +273,21 @@ export const modelWithoutVersion = {
|
|||
},
|
||||
};
|
||||
|
||||
export const model = {
|
||||
id: 'gid://gitlab/Ml::Model/1',
|
||||
description: 'A model description',
|
||||
name: 'gitlab_amazing_model',
|
||||
versionCount: 1,
|
||||
candidateCount: 0,
|
||||
latestVersion: modelVersionWithCandidate,
|
||||
};
|
||||
|
||||
export const modelDetailQuery = {
|
||||
data: {
|
||||
mlModel: model,
|
||||
},
|
||||
};
|
||||
|
||||
export const modelsQuery = (
|
||||
models = [modelWithOneVersion, modelWithoutVersion],
|
||||
pageInfo = graphqlPageInfo,
|
||||
|
|
@ -216,3 +303,13 @@ export const modelsQuery = (
|
|||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const modelVersionQuery = {
|
||||
data: {
|
||||
mlModel: {
|
||||
id: 'gid://gitlab/Ml::Model/1',
|
||||
name: 'blah',
|
||||
version: modelVersionWithCandidate,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,60 @@
|
|||
import { convertCandidateFromGraphql } from '~/ml/model_registry/utils';
|
||||
import { candidate } from './graphql_mock_data';
|
||||
|
||||
describe('~/ml/model_registry/utils', () => {
|
||||
describe('convertCandidateFromGraphql', () => {
|
||||
it('converts from graphql response', () => {
|
||||
const converted = convertCandidateFromGraphql(candidate);
|
||||
const expectedResponse = {
|
||||
info: {
|
||||
iid: 1,
|
||||
eid: 'e9a71521-45c6-4b0a-b0c3-21f0b4528a5c',
|
||||
status: 'running',
|
||||
experimentName: '',
|
||||
pathToExperiment: '',
|
||||
pathToArtifact: '/root/test-project/-/packages/1',
|
||||
path: '/root/test-project/-/ml/candidates/1',
|
||||
ciJob: {
|
||||
mergeRequest: {
|
||||
iid: 1,
|
||||
path: 'path/to/mr',
|
||||
title: 'Merge Request 1',
|
||||
},
|
||||
name: 'build:linux',
|
||||
path: '/gitlab-org/gitlab-test/-/jobs/1',
|
||||
user: {
|
||||
avatar: 'path/to/avatar',
|
||||
name: 'User 1',
|
||||
path: 'path/to/user/1',
|
||||
username: 'user1',
|
||||
},
|
||||
},
|
||||
},
|
||||
metrics: [
|
||||
{
|
||||
id: 'gid://gitlab/Ml::CandidateMetric/1',
|
||||
name: 'metric1',
|
||||
value: 0.3,
|
||||
step: 0,
|
||||
},
|
||||
],
|
||||
params: [
|
||||
{
|
||||
id: 'gid://gitlab/Ml::CandidateParam/1',
|
||||
name: 'param1',
|
||||
value: 'value1',
|
||||
},
|
||||
],
|
||||
metadata: [
|
||||
{
|
||||
id: 'gid://gitlab/Ml::CandidateMetadata/1',
|
||||
name: 'metadata1',
|
||||
value: 'metadataValue1',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(converted).toEqual(expectedResponse);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -25,10 +25,10 @@ describe('container Protection Rule Form', () => {
|
|||
const findForm = () => wrapper.findComponent(GlForm);
|
||||
const findRepositoryPathPatternInput = () =>
|
||||
wrapper.findByRole('textbox', { name: /repository path pattern/i });
|
||||
const findPushProtectedUpToAccessLevelSelect = () =>
|
||||
wrapper.findByRole('combobox', { name: /maximum access level prevented from pushing/i });
|
||||
const findDeleteProtectedUpToAccessLevelSelect = () =>
|
||||
wrapper.findByRole('combobox', { name: /maximum access level prevented from deleting/i });
|
||||
const findMinimumAccessLevelForPushSelect = () =>
|
||||
wrapper.findByRole('combobox', { name: /minimum access level for push/i });
|
||||
const findMinimumAccessLevelForDeleteSelect = () =>
|
||||
wrapper.findByRole('combobox', { name: /minimum access level for delete/i });
|
||||
const findSubmitButton = () => wrapper.findByRole('button', { name: /add rule/i });
|
||||
|
||||
const mountComponent = ({ config, provide = defaultProvidedValues } = {}) => {
|
||||
|
|
@ -52,21 +52,18 @@ describe('container Protection Rule Form', () => {
|
|||
};
|
||||
|
||||
describe('form fields', () => {
|
||||
describe('form field "pushProtectedUpToAccessLevelSelect"', () => {
|
||||
const pushProtectedUpToAccessLevelSelectOptions = () =>
|
||||
findPushProtectedUpToAccessLevelSelect()
|
||||
describe('form field "minimumAccessLevelForPush"', () => {
|
||||
const minimumAccessLevelForPushOptions = () =>
|
||||
findMinimumAccessLevelForPushSelect()
|
||||
.findAll('option')
|
||||
.wrappers.map((option) => option.element.value);
|
||||
|
||||
it.each(['DEVELOPER', 'MAINTAINER', 'OWNER'])(
|
||||
'includes the %s access level',
|
||||
(accessLevel) => {
|
||||
mountComponent();
|
||||
it.each(['MAINTAINER', 'OWNER', 'ADMIN'])('includes the %s access level', (accessLevel) => {
|
||||
mountComponent();
|
||||
|
||||
expect(findPushProtectedUpToAccessLevelSelect().exists()).toBe(true);
|
||||
expect(pushProtectedUpToAccessLevelSelectOptions()).toContain(accessLevel);
|
||||
},
|
||||
);
|
||||
expect(findMinimumAccessLevelForPushSelect().exists()).toBe(true);
|
||||
expect(minimumAccessLevelForPushOptions()).toContain(accessLevel);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when graphql mutation is in progress', () => {
|
||||
|
|
@ -79,8 +76,8 @@ describe('container Protection Rule Form', () => {
|
|||
it('disables all form fields', () => {
|
||||
expect(findSubmitButton().props('disabled')).toBe(true);
|
||||
expect(findRepositoryPathPatternInput().attributes('disabled')).toBe('disabled');
|
||||
expect(findPushProtectedUpToAccessLevelSelect().attributes('disabled')).toBe('disabled');
|
||||
expect(findDeleteProtectedUpToAccessLevelSelect().attributes('disabled')).toBe('disabled');
|
||||
expect(findMinimumAccessLevelForPushSelect().attributes('disabled')).toBe('disabled');
|
||||
expect(findMinimumAccessLevelForDeleteSelect().attributes('disabled')).toBe('disabled');
|
||||
});
|
||||
|
||||
it('displays a loading spinner', () => {
|
||||
|
|
@ -90,7 +87,7 @@ describe('container Protection Rule Form', () => {
|
|||
});
|
||||
|
||||
describe('form actions', () => {
|
||||
describe('button "Protect"', () => {
|
||||
describe('button "Add rule"', () => {
|
||||
it.each`
|
||||
repositoryPathPattern | submitButtonDisabled
|
||||
${''} | ${true}
|
||||
|
|
|
|||
|
|
@ -170,14 +170,14 @@ export const containerProtectionRulesData = [
|
|||
...Array.from(Array(15)).map((_e, i) => ({
|
||||
id: `gid://gitlab/ContainerRegistry::Protection::Rule/${i}`,
|
||||
repositoryPathPattern: `@flight/flight/maintainer-${i}-*`,
|
||||
pushProtectedUpToAccessLevel: 'MAINTAINER',
|
||||
deleteProtectedUpToAccessLevel: 'MAINTAINER',
|
||||
minimumAccessLevelForPush: 'MAINTAINER',
|
||||
minimumAccessLevelForDelete: 'MAINTAINER',
|
||||
})),
|
||||
{
|
||||
id: 'gid://gitlab/ContainerRegistry::Protection::Rule/16',
|
||||
repositoryPathPattern: '@flight/flight/owner-16-*',
|
||||
pushProtectedUpToAccessLevel: 'OWNER',
|
||||
deleteProtectedUpToAccessLevel: 'OWNER',
|
||||
minimumAccessLevelForPush: 'OWNER',
|
||||
minimumAccessLevelForDelete: 'OWNER',
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -216,9 +216,9 @@ export const createContainerProtectionRuleMutationPayload = ({ override, errors
|
|||
});
|
||||
|
||||
export const createContainerProtectionRuleMutationInput = {
|
||||
repositoryPathPattern: `@flight/flight-developer-14-*`,
|
||||
pushProtectedUpToAccessLevel: 'DEVELOPER',
|
||||
deleteProtectedUpToAccessLevel: 'DEVELOPER',
|
||||
repositoryPathPattern: `@flight/flight-maintainer-14-*`,
|
||||
minimumAccessLevelForPush: 'MAINTAINER',
|
||||
minimumAccessLevelForDelete: 'MAINTAINER',
|
||||
};
|
||||
|
||||
export const createContainerProtectionRuleMutationPayloadErrors = [
|
||||
|
|
|
|||
|
|
@ -16,9 +16,6 @@ describe('Todo Button', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
dispatchEventSpy = jest.spyOn(document, 'dispatchEvent');
|
||||
jest.spyOn(document, 'querySelector').mockReturnValue({
|
||||
innerText: 2,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
|
@ -44,7 +41,7 @@ describe('Todo Button', () => {
|
|||
const dispatchedEvent = dispatchEventSpy.mock.calls[0][0];
|
||||
|
||||
expect(dispatchEventSpy).toHaveBeenCalledTimes(1);
|
||||
expect(dispatchedEvent.detail).toEqual({ count: 1 });
|
||||
expect(dispatchedEvent.detail).toEqual({ delta: -1 });
|
||||
expect(dispatchedEvent.type).toBe('todo:toggle');
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -134,13 +134,21 @@ describe('UserBar component', () => {
|
|||
expect(todosCounter.attributes('class')).toContain('shortcuts-todos');
|
||||
});
|
||||
|
||||
it('should update todo counter when event is emitted', async () => {
|
||||
it('should update todo counter when event with count is emitted', async () => {
|
||||
createWrapper();
|
||||
const count = 100;
|
||||
document.dispatchEvent(new CustomEvent('todo:toggle', { detail: { count } }));
|
||||
await nextTick();
|
||||
expect(findTodosCounter().props('count')).toBe(count);
|
||||
});
|
||||
|
||||
it('should update todo counter when event with diff is emitted', async () => {
|
||||
createWrapper();
|
||||
expect(findTodosCounter().props('count')).toBe(3);
|
||||
document.dispatchEvent(new CustomEvent('todo:toggle', { detail: { delta: -2 } }));
|
||||
await nextTick();
|
||||
expect(findTodosCounter().props('count')).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('renders branding logo', () => {
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ const userCountUpdate = {
|
|||
review_requested_merge_requests: 101112,
|
||||
};
|
||||
|
||||
describe('User Merge Requests', () => {
|
||||
describe('User Count Manager', () => {
|
||||
let channelMock;
|
||||
let newBroadcastChannelMock;
|
||||
|
||||
|
|
@ -108,6 +108,11 @@ describe('User Merge Requests', () => {
|
|||
'userCounts:fetch',
|
||||
expect.any(Function),
|
||||
);
|
||||
expect(document.removeEventListener).toHaveBeenCalledWith(
|
||||
'todo:toggle',
|
||||
expect.any(Function),
|
||||
);
|
||||
expect(document.addEventListener).toHaveBeenCalledWith('todo:toggle', expect.any(Function));
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -146,6 +151,59 @@ describe('User Merge Requests', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('Event listener todo:toggle', () => {
|
||||
beforeEach(() => {
|
||||
createUserCountsManager();
|
||||
userCounts.todos = 10;
|
||||
});
|
||||
|
||||
describe('with total count', () => {
|
||||
it.each([
|
||||
{ count: 123, expected: 123 },
|
||||
{ count: -500, expected: 0 },
|
||||
{ count: 0, expected: 0 },
|
||||
{ count: NaN, expected: 10 },
|
||||
{ count: '99+', expected: 10 },
|
||||
])(`with count: $count results in $expected`, ({ count, expected }) => {
|
||||
expect(userCounts.todos).toBe(10);
|
||||
|
||||
document.dispatchEvent(new CustomEvent('todo:toggle', { detail: { count } }));
|
||||
|
||||
expect(userCounts.todos).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with diff on count', () => {
|
||||
it.each([
|
||||
{ delta: 5, expected: 15 },
|
||||
{ delta: -5, expected: 5 },
|
||||
{ delta: 0, expected: 10 },
|
||||
{ delta: -100, expected: 0 },
|
||||
{ delta: NaN, expected: 10 },
|
||||
{ delta: '99+', expected: 10 },
|
||||
])(`with count: $diff results in $expected`, ({ delta, expected }) => {
|
||||
expect(userCounts.todos).toBe(10);
|
||||
|
||||
document.dispatchEvent(new CustomEvent('todo:toggle', { detail: { delta } }));
|
||||
|
||||
expect(userCounts.todos).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
it('updates count over delta if both are defined', () => {
|
||||
expect(userCounts.todos).toBe(10);
|
||||
|
||||
const detail = {
|
||||
count: 20,
|
||||
delta: -5,
|
||||
};
|
||||
|
||||
document.dispatchEvent(new CustomEvent('todo:toggle', { detail }));
|
||||
|
||||
expect(userCounts.todos).toBe(detail.count);
|
||||
});
|
||||
});
|
||||
|
||||
describe('destroyUserCountsManager', () => {
|
||||
it('unregisters event handler', () => {
|
||||
expect(document.removeEventListener).not.toHaveBeenCalledWith();
|
||||
|
|
|
|||
|
|
@ -5,17 +5,23 @@ import createMockApollo from 'helpers/mock_apollo_helper';
|
|||
import todoMarkDoneMutation from '~/graphql_shared/mutations/todo_mark_done.mutation.graphql';
|
||||
import SidebarTodo from '~/vue_shared/alert_details/components/sidebar/sidebar_todo.vue';
|
||||
import createAlertTodoMutation from '~/vue_shared/alert_details/graphql/mutations/alert_todo_create.mutation.graphql';
|
||||
import alertQuery from '~/vue_shared/alert_details/graphql/queries/alert_sidebar_details.query.graphql';
|
||||
import waitForPromises from 'jest/__helpers__/wait_for_promises';
|
||||
import mockAlerts from './mocks/alerts.json';
|
||||
|
||||
const mockAlert = mockAlerts[0];
|
||||
const mockAlert = mockAlerts[1];
|
||||
|
||||
describe('Alert Details Sidebar To Do', () => {
|
||||
let wrapper;
|
||||
let requestHandler;
|
||||
|
||||
const defaultHandler = {
|
||||
createAlertTodo: jest.fn().mockResolvedValue({}),
|
||||
markAsDone: jest.fn().mockResolvedValue({}),
|
||||
createAlertTodo: jest
|
||||
.fn()
|
||||
.mockResolvedValue({ data: { alertTodoCreate: { errors: [], alert: mockAlert } } }),
|
||||
markAsDone: jest
|
||||
.fn()
|
||||
.mockResolvedValue({ data: { todoMarkDone: { errors: [], todo: { id: 1234 } } } }),
|
||||
};
|
||||
|
||||
const createMockApolloProvider = (handler) => {
|
||||
|
|
@ -30,14 +36,33 @@ describe('Alert Details Sidebar To Do', () => {
|
|||
};
|
||||
|
||||
function mountComponent({ data, sidebarCollapsed = true, handler = defaultHandler } = {}) {
|
||||
wrapper = mount(SidebarTodo, {
|
||||
apolloProvider: createMockApolloProvider(handler),
|
||||
propsData: {
|
||||
alert: { ...mockAlert },
|
||||
...data,
|
||||
sidebarCollapsed,
|
||||
projectPath: 'projectPath',
|
||||
const propsData = {
|
||||
alert: { ...mockAlert },
|
||||
...data,
|
||||
sidebarCollapsed,
|
||||
projectPath: 'projectPath',
|
||||
};
|
||||
const fakeApollo = createMockApolloProvider(handler);
|
||||
|
||||
fakeApollo.clients.defaultClient.cache.writeQuery({
|
||||
query: alertQuery,
|
||||
variables: {
|
||||
fullPath: propsData.projectPath,
|
||||
alertId: propsData.alert.iid,
|
||||
},
|
||||
data: {
|
||||
project: {
|
||||
id: '1',
|
||||
alertManagementAlerts: {
|
||||
nodes: [propsData.alert],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
wrapper = mount(SidebarTodo, {
|
||||
apolloProvider: fakeApollo,
|
||||
propsData,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -47,9 +72,7 @@ describe('Alert Details Sidebar To Do', () => {
|
|||
describe('adding a todo', () => {
|
||||
beforeEach(() => {
|
||||
mountComponent({
|
||||
data: { alert: mockAlert },
|
||||
sidebarCollapsed: false,
|
||||
loading: false,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -65,11 +88,23 @@ describe('Alert Details Sidebar To Do', () => {
|
|||
|
||||
expect(requestHandler.createAlertTodo).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
iid: '1527542',
|
||||
iid: '1527543',
|
||||
projectPath: 'projectPath',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('triggers an update of the todo count', async () => {
|
||||
const dispatchEventSpy = jest.spyOn(document, 'dispatchEvent');
|
||||
|
||||
findToDoButton().trigger('click');
|
||||
await waitForPromises();
|
||||
|
||||
expect(dispatchEventSpy).toHaveBeenCalledTimes(1);
|
||||
const dispatchedEvent = dispatchEventSpy.mock.calls[0][0];
|
||||
expect(dispatchedEvent.detail).toEqual({ delta: 1 });
|
||||
expect(dispatchedEvent.type).toBe('todo:toggle');
|
||||
});
|
||||
});
|
||||
|
||||
describe('removing a todo', () => {
|
||||
|
|
@ -95,6 +130,18 @@ describe('Alert Details Sidebar To Do', () => {
|
|||
id: '1234',
|
||||
});
|
||||
});
|
||||
|
||||
it('triggers an update of the todo count', async () => {
|
||||
const dispatchEventSpy = jest.spyOn(document, 'dispatchEvent');
|
||||
|
||||
findToDoButton().trigger('click');
|
||||
await waitForPromises();
|
||||
|
||||
expect(dispatchEventSpy).toHaveBeenCalledTimes(1);
|
||||
const dispatchedEvent = dispatchEventSpy.mock.calls[0][0];
|
||||
expect(dispatchedEvent.detail).toEqual({ delta: -1 });
|
||||
expect(dispatchedEvent.type).toBe('todo:toggle');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -8,20 +8,65 @@
|
|||
"startedAt": "2020-04-17T23:18:14.996Z",
|
||||
"endedAt": "2020-04-17T23:18:14.996Z",
|
||||
"status": "TRIGGERED",
|
||||
"assignees": { "nodes": [] },
|
||||
"notes": { "nodes": [] },
|
||||
"todos": { "nodes": [] }
|
||||
"assignees": {
|
||||
"nodes": [
|
||||
|
||||
]
|
||||
},
|
||||
"notes": {
|
||||
"nodes": [
|
||||
|
||||
]
|
||||
},
|
||||
"todos": {
|
||||
"nodes": [
|
||||
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"__typename": "AlertManagementAlert",
|
||||
"iid": "1527543",
|
||||
"title": "Some other alert Some other alert Some other alert Some other alert Some other alert Some other alert",
|
||||
"description": "So descriptive",
|
||||
"severity": "MEDIUM",
|
||||
"eventCount": 1,
|
||||
"startedAt": "2020-04-17T23:18:14.996Z",
|
||||
"endedAt": "2020-04-17T23:18:14.996Z",
|
||||
"details": 5,
|
||||
"monitoringTool": "Eyes",
|
||||
"service": "1984",
|
||||
"runbook": "I am running",
|
||||
"createdAt": "2020-04-17T23:20:14.996Z",
|
||||
"updatedAt": "2020-04-17T23:23:14.996Z",
|
||||
"endedAt": "2020-04-18T23:18:14.996Z",
|
||||
"hosts": [
|
||||
"127.0.0.1"
|
||||
],
|
||||
"environment": {
|
||||
"id": "gid://gitlab/Environment/29",
|
||||
"name": "Alerta",
|
||||
"path": "/path/to/alerta"
|
||||
},
|
||||
"status": "ACKNOWLEDGED",
|
||||
"assignees": { "nodes": [{ "username": "root", "avatarUrl": "/url", "name": "root" }] },
|
||||
"issue": { "state" : "closed", "iid": "1", "title": "My test issue" },
|
||||
"assignees": {
|
||||
"nodes": [
|
||||
{
|
||||
"username": "root",
|
||||
"avatarUrl": "/url",
|
||||
"name": "root"
|
||||
}
|
||||
]
|
||||
},
|
||||
"issue": {
|
||||
"__typename": "Issue",
|
||||
"id": "gid://gitlab/Issue/1",
|
||||
"state": "closed",
|
||||
"iid": "1",
|
||||
"title": "My test issue",
|
||||
"createdAt": "2020-04-17T23:18:14.996Z",
|
||||
"webUrl": "http://192.168.1.4:3000/projectPath/-/issues/1"
|
||||
},
|
||||
"notes": {
|
||||
"nodes": [
|
||||
{
|
||||
|
|
@ -30,7 +75,7 @@
|
|||
"id": "gid://gitlab/User/1",
|
||||
"state": "active",
|
||||
"__typename": "User",
|
||||
"avatarUrl": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon",
|
||||
"avatarUrl": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
|
||||
"name": "Administrator",
|
||||
"username": "root",
|
||||
"webUrl": "http://192.168.1.4:3000/root"
|
||||
|
|
@ -39,7 +84,11 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"todos": { "nodes": [] }
|
||||
"todos": {
|
||||
"nodes": [
|
||||
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"iid": "1527544",
|
||||
|
|
@ -49,7 +98,15 @@
|
|||
"startedAt": "2020-04-17T23:18:14.996Z",
|
||||
"endedAt": "2020-04-17T23:18:14.996Z",
|
||||
"status": "RESOLVED",
|
||||
"assignees": { "nodes": [{ "username": "root", "avatarUrl": "/url", "name": "root" }] },
|
||||
"assignees": {
|
||||
"nodes": [
|
||||
{
|
||||
"username": "root",
|
||||
"avatarUrl": "/url",
|
||||
"name": "root"
|
||||
}
|
||||
]
|
||||
},
|
||||
"notes": {
|
||||
"nodes": [
|
||||
{
|
||||
|
|
@ -58,7 +115,7 @@
|
|||
"id": "gid://gitlab/User/2",
|
||||
"state": "active",
|
||||
"__typename": "User",
|
||||
"avatarUrl": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon",
|
||||
"avatarUrl": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
|
||||
"name": "Administrator",
|
||||
"username": "root",
|
||||
"webUrl": "http://192.168.1.4:3000/root"
|
||||
|
|
@ -66,6 +123,10 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"todos": { "nodes": [] }
|
||||
"todos": {
|
||||
"nodes": [
|
||||
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ RSpec.describe Resolvers::Ci::RunnerProjectsResolver, feature_category: :fleet_v
|
|||
|
||||
describe '#resolve' do
|
||||
context 'with authorized user', :enable_admin_mode do
|
||||
let(:current_user) { create(:user, :admin) }
|
||||
let_it_be(:current_user) { create(:user, :admin) }
|
||||
|
||||
context 'with search argument' do
|
||||
let(:args) { { search: 'Project1.' } }
|
||||
|
|
@ -69,15 +69,15 @@ RSpec.describe Resolvers::Ci::RunnerProjectsResolver, feature_category: :fleet_v
|
|||
end
|
||||
|
||||
context 'without arguments' do
|
||||
it 'returns a lazy value with all projects sorted by :id_asc' do
|
||||
it 'returns a lazy value with all projects sorted by :id_desc' do
|
||||
expect(subject).to be_a(GraphQL::Execution::Lazy)
|
||||
expect(subject.value.items).to eq([project1, project2, project3])
|
||||
expect(subject.value.items).to eq([project3, project2, project1])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with unauthorized user' do
|
||||
let(:current_user) { create(:user) }
|
||||
let_it_be(:current_user) { create(:user) }
|
||||
|
||||
it { is_expected.to be_nil }
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,6 +4,6 @@ require 'spec_helper'
|
|||
|
||||
RSpec.describe GitlabSchema.types['ContainerRegistryProtectionRuleAccessLevel'], feature_category: :container_registry do
|
||||
it 'exposes all options' do
|
||||
expect(described_class.values.keys).to match_array(%w[DEVELOPER MAINTAINER OWNER])
|
||||
expect(described_class.values.keys).to match_array(%w[MAINTAINER OWNER ADMIN])
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -21,14 +21,14 @@ RSpec.describe GitlabSchema.types['ContainerRegistryProtectionRule'], feature_ca
|
|||
it { is_expected.to have_non_null_graphql_type(GraphQL::Types::String) }
|
||||
end
|
||||
|
||||
describe 'push_protected_up_to_access_level' do
|
||||
subject { described_class.fields['pushProtectedUpToAccessLevel'] }
|
||||
describe 'minimum_access_level_for_push' do
|
||||
subject { described_class.fields['minimumAccessLevelForPush'] }
|
||||
|
||||
it { is_expected.to have_non_null_graphql_type(Types::ContainerRegistry::Protection::RuleAccessLevelEnum) }
|
||||
end
|
||||
|
||||
describe 'delete_protected_up_to_access_level' do
|
||||
subject { described_class.fields['deleteProtectedUpToAccessLevel'] }
|
||||
describe 'minimum_access_level_for_delete' do
|
||||
subject { described_class.fields['minimumAccessLevelForDelete'] }
|
||||
|
||||
it { is_expected.to have_non_null_graphql_type(Types::ContainerRegistry::Protection::RuleAccessLevelEnum) }
|
||||
end
|
||||
|
|
|
|||
|
|
@ -102,4 +102,40 @@ RSpec.describe Projects::Ml::ModelRegistryHelper, feature_category: :mlops do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#show_ml_model_version_data' do
|
||||
let_it_be(:model) do
|
||||
build_stubbed(:ml_models, :with_latest_version_and_package, project: project, id: 1)
|
||||
end
|
||||
|
||||
let_it_be(:model_version) do
|
||||
model.latest_version
|
||||
end
|
||||
|
||||
subject(:parsed) { Gitlab::Json.parse(helper.show_ml_model_version_data(model_version, user)) }
|
||||
|
||||
it 'generates the correct data' do
|
||||
is_expected.to eq({
|
||||
"projectPath" => project.full_path,
|
||||
"modelId" => model.id,
|
||||
"modelVersionId" => model_version.id,
|
||||
"modelName" => model_version.name,
|
||||
"versionName" => model_version.version,
|
||||
"canWriteModelRegistry" => true
|
||||
})
|
||||
end
|
||||
|
||||
context 'when user does not have write access to model registry' do
|
||||
before do
|
||||
allow(Ability).to receive(:allowed?).and_call_original
|
||||
allow(Ability).to receive(:allowed?)
|
||||
.with(user, :write_model_registry, project)
|
||||
.and_return(false)
|
||||
end
|
||||
|
||||
it 'canWriteModelRegistry is false' do
|
||||
expect(parsed['canWriteModelRegistry']).to eq(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -51,20 +51,16 @@ RSpec.describe Gitlab::Checks::ChangedBlobs, feature_category: :source_code_mana
|
|||
end
|
||||
end
|
||||
|
||||
context 'with quarantine directory' do
|
||||
let_it_be(:project) { create(:project, :small_repo) }
|
||||
context 'with quarantine directory', :request_store do
|
||||
let_it_be_with_refind(:project) { create(:project, :small_repo) }
|
||||
|
||||
let(:revisions) { [repository.commit.id] }
|
||||
|
||||
let(:git_env) do
|
||||
{
|
||||
'GIT_OBJECT_DIRECTORY_RELATIVE' => "objects",
|
||||
'GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE' => ['/dir/one', '/dir/two']
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
allow(Gitlab::Git::HookEnv).to receive(:all).with(repository.gl_repository).and_return(git_env)
|
||||
::Gitlab::Git::HookEnv.set(project.repository.gl_repository,
|
||||
project.repository.raw_repository.relative_path,
|
||||
'GIT_OBJECT_DIRECTORY_RELATIVE' => 'objects',
|
||||
'GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE' => ['/dir/one', '/dir/two'])
|
||||
end
|
||||
|
||||
context 'when the blob does not exist in the repo' do
|
||||
|
|
@ -78,7 +74,7 @@ RSpec.describe Gitlab::Checks::ChangedBlobs, feature_category: :source_code_mana
|
|||
end
|
||||
|
||||
context 'when the same file with different paths is committed' do
|
||||
let_it_be(:commits) do
|
||||
before_all do
|
||||
project.repository.commit_files(
|
||||
user,
|
||||
branch_name: project.repository.root_ref,
|
||||
|
|
|
|||
|
|
@ -29,16 +29,12 @@ RSpec.describe Gitlab::Checks::FileSizeCheck::HookEnvironmentAwareAnyOversizedBl
|
|||
end
|
||||
|
||||
context 'with hook env' do
|
||||
context 'with hook environment' do
|
||||
let(:git_env) do
|
||||
{
|
||||
'GIT_OBJECT_DIRECTORY_RELATIVE' => "objects",
|
||||
'GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE' => ['/dir/one', '/dir/two']
|
||||
}
|
||||
end
|
||||
|
||||
context 'with hook environment', :request_store do
|
||||
before do
|
||||
allow(Gitlab::Git::HookEnv).to receive(:all).with(repository.gl_repository).and_return(git_env)
|
||||
::Gitlab::Git::HookEnv.set(project.repository.gl_repository,
|
||||
project.repository.raw_repository.relative_path,
|
||||
'GIT_OBJECT_DIRECTORY_RELATIVE' => 'objects',
|
||||
'GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE' => ['/dir/one', '/dir/two'])
|
||||
end
|
||||
|
||||
it 'returns an emtpy array' do
|
||||
|
|
|
|||
|
|
@ -3,14 +3,16 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::Git::HookEnv do
|
||||
let(:relative_path) { 'snapshot/relative-path.git' }
|
||||
let(:gl_repository) { 'project-123' }
|
||||
|
||||
describe ".set" do
|
||||
context 'with RequestStore disabled' do
|
||||
it 'does not store anything' do
|
||||
described_class.set(gl_repository, GIT_OBJECT_DIRECTORY_RELATIVE: 'foo')
|
||||
described_class.set(gl_repository, relative_path, GIT_OBJECT_DIRECTORY_RELATIVE: 'foo')
|
||||
|
||||
expect(described_class.all(gl_repository)).to be_empty
|
||||
expect(described_class.get_relative_path).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -18,6 +20,7 @@ RSpec.describe Gitlab::Git::HookEnv do
|
|||
it 'whitelist some `GIT_*` variables and stores them using RequestStore' do
|
||||
described_class.set(
|
||||
gl_repository,
|
||||
relative_path,
|
||||
GIT_OBJECT_DIRECTORY_RELATIVE: 'foo',
|
||||
GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE: 'bar',
|
||||
GIT_EXEC_PATH: 'baz',
|
||||
|
|
@ -34,15 +37,16 @@ RSpec.describe Gitlab::Git::HookEnv do
|
|||
end
|
||||
end
|
||||
|
||||
describe ".all" do
|
||||
context 'with RequestStore enabled', :request_store do
|
||||
before do
|
||||
described_class.set(
|
||||
gl_repository,
|
||||
GIT_OBJECT_DIRECTORY_RELATIVE: 'foo',
|
||||
GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE: ['bar'])
|
||||
end
|
||||
context 'with RequestStore enabled', :request_store do
|
||||
before do
|
||||
described_class.set(
|
||||
gl_repository,
|
||||
relative_path,
|
||||
GIT_OBJECT_DIRECTORY_RELATIVE: 'foo',
|
||||
GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE: ['bar'])
|
||||
end
|
||||
|
||||
describe ".all" do
|
||||
it 'returns an env hash' do
|
||||
expect(described_class.all(gl_repository)).to eq({
|
||||
'GIT_OBJECT_DIRECTORY_RELATIVE' => 'foo',
|
||||
|
|
@ -50,6 +54,12 @@ RSpec.describe Gitlab::Git::HookEnv do
|
|||
})
|
||||
end
|
||||
end
|
||||
|
||||
describe ".get_relative_path" do
|
||||
it 'returns the relative path' do
|
||||
expect(described_class.get_relative_path).to eq(relative_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe ".to_env_hash" do
|
||||
|
|
@ -70,7 +80,7 @@ RSpec.describe Gitlab::Git::HookEnv do
|
|||
|
||||
with_them do
|
||||
before do
|
||||
described_class.set(gl_repository, key.to_sym => input)
|
||||
described_class.set(gl_repository, relative_path, key.to_sym => input)
|
||||
end
|
||||
|
||||
it 'puts the right value in the hash' do
|
||||
|
|
@ -86,26 +96,36 @@ RSpec.describe Gitlab::Git::HookEnv do
|
|||
|
||||
describe 'thread-safety' do
|
||||
context 'with RequestStore enabled', :request_store do
|
||||
let(:other_relative_path) { 'other_relative_path' }
|
||||
|
||||
before do
|
||||
allow(RequestStore).to receive(:active?).and_return(true)
|
||||
described_class.set(gl_repository, GIT_OBJECT_DIRECTORY_RELATIVE: 'foo')
|
||||
described_class.set(gl_repository, relative_path, GIT_OBJECT_DIRECTORY_RELATIVE: 'foo')
|
||||
end
|
||||
|
||||
it 'is thread-safe' do
|
||||
another_thread = Thread.new do
|
||||
described_class.set(gl_repository, GIT_OBJECT_DIRECTORY_RELATIVE: 'bar')
|
||||
described_class.set(gl_repository, other_relative_path, GIT_OBJECT_DIRECTORY_RELATIVE: 'bar')
|
||||
|
||||
Thread.stop
|
||||
described_class.all(gl_repository)[:GIT_OBJECT_DIRECTORY_RELATIVE]
|
||||
|
||||
{
|
||||
relative_path: described_class.get_relative_path,
|
||||
GIT_OBJECT_DIRECTORY_RELATIVE: described_class.all(gl_repository)[:GIT_OBJECT_DIRECTORY_RELATIVE]
|
||||
}
|
||||
end
|
||||
|
||||
# Ensure another_thread runs first
|
||||
sleep 0.1 until another_thread.stop?
|
||||
|
||||
expect(described_class.get_relative_path).to eq(relative_path)
|
||||
expect(described_class.all(gl_repository)[:GIT_OBJECT_DIRECTORY_RELATIVE]).to eq('foo')
|
||||
|
||||
another_thread.run
|
||||
expect(another_thread.value).to eq('bar')
|
||||
expect(another_thread.value).to eq({
|
||||
relative_path: other_relative_path,
|
||||
GIT_OBJECT_DIRECTORY_RELATIVE: 'bar'
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -371,14 +371,13 @@ RSpec.describe Gitlab::GitAccessSnippet do
|
|||
it_behaves_like 'migration bot does not err'
|
||||
end
|
||||
|
||||
context 'when GIT_OBJECT_DIRECTORY_RELATIVE env var is set' do
|
||||
context 'when GIT_OBJECT_DIRECTORY_RELATIVE env var is set', :request_store do
|
||||
let(:change_size) { 100 }
|
||||
|
||||
before do
|
||||
allow(Gitlab::Git::HookEnv)
|
||||
.to receive(:all)
|
||||
.with(repository.gl_repository)
|
||||
.and_return({ 'GIT_OBJECT_DIRECTORY_RELATIVE' => 'objects' })
|
||||
::Gitlab::Git::HookEnv.set(repository.gl_repository,
|
||||
repository.raw_repository.relative_path,
|
||||
'GIT_OBJECT_DIRECTORY_RELATIVE' => 'objects')
|
||||
|
||||
# Stub the object directory size to "simulate" quarantine size
|
||||
allow(repository).to receive(:object_directory_size).and_return(change_size)
|
||||
|
|
|
|||
|
|
@ -397,6 +397,9 @@ RSpec.describe Gitlab::InternalEvents, :snowplow, feature_category: :product_ana
|
|||
|
||||
stub_env('GITLAB_ANALYTICS_ID', app_id)
|
||||
stub_env('GITLAB_ANALYTICS_URL', url)
|
||||
|
||||
stub_feature_flags(internal_events_batching: true)
|
||||
|
||||
allow(GitlabSDK::Client)
|
||||
.to receive(:new)
|
||||
.with(app_id: app_id, host: url, buffer_size: described_class::SNOWPLOW_EMITTER_BUFFER_SIZE)
|
||||
|
|
@ -465,5 +468,19 @@ RSpec.describe Gitlab::InternalEvents, :snowplow, feature_category: :product_ana
|
|||
|
||||
it_behaves_like 'does not send a Product Analytics event'
|
||||
end
|
||||
|
||||
context 'with internal_events_batching FF off' do
|
||||
before do
|
||||
stub_feature_flags(internal_events_batching: false)
|
||||
end
|
||||
|
||||
it 'passes buffer_size 1 to SDK client' do
|
||||
expect(GitlabSDK::Client)
|
||||
.to receive(:new)
|
||||
.with(app_id: app_id, host: url, buffer_size: described_class::DEFAULT_BUFFER_SIZE)
|
||||
|
||||
track_event
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -52,7 +52,26 @@ RSpec.describe Gitlab::SidekiqSharding::ScheduledEnq, feature_category: :scalabi
|
|||
it_behaves_like 'uses sharding router'
|
||||
end
|
||||
|
||||
context 'with routable classes' do
|
||||
context 'with ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper classes' do
|
||||
let(:job_hash) { { class: ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper, args: [] } }
|
||||
let(:store_name) { 'queues_shard_test' }
|
||||
|
||||
before do
|
||||
# simulate routing rules in config/gitlab.yml
|
||||
allow(ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper)
|
||||
.to receive(:get_sidekiq_options).and_return({ 'store' => store_name })
|
||||
|
||||
# skip label creation to avoid calling .get_shard_instance
|
||||
allow_next_instance_of(Gitlab::SidekiqMiddleware::ClientMetrics) do |mh|
|
||||
allow(mh).to receive(:create_labels).and_return({ boundary: "", destination_shard_redis: "",
|
||||
external_dependencies: "", feature_category: "", queue: "", scheduling: "", urgency: "", worker: "" })
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'uses sharding router'
|
||||
end
|
||||
|
||||
context 'with ApplicationWorker classes' do
|
||||
let(:job_hash) { { class: Chaos::CpuSpinWorker, args: [] } }
|
||||
let(:store_name) { 'queues_shard_test' }
|
||||
|
||||
|
|
|
|||
|
|
@ -14,25 +14,25 @@ RSpec.describe ContainerRegistry::Protection::Rule, type: :model, feature_catego
|
|||
describe 'enums' do
|
||||
it {
|
||||
is_expected.to(
|
||||
define_enum_for(:push_protected_up_to_access_level)
|
||||
define_enum_for(:minimum_access_level_for_push)
|
||||
.with_values(
|
||||
developer: Gitlab::Access::DEVELOPER,
|
||||
maintainer: Gitlab::Access::MAINTAINER,
|
||||
owner: Gitlab::Access::OWNER
|
||||
owner: Gitlab::Access::OWNER,
|
||||
admin: Gitlab::Access::ADMIN
|
||||
)
|
||||
.with_prefix(:push_protected_up_to)
|
||||
.with_prefix(:minimum_access_level_for_push)
|
||||
)
|
||||
}
|
||||
|
||||
it {
|
||||
is_expected.to(
|
||||
define_enum_for(:delete_protected_up_to_access_level)
|
||||
.with_values(
|
||||
developer: Gitlab::Access::DEVELOPER,
|
||||
maintainer: Gitlab::Access::MAINTAINER,
|
||||
owner: Gitlab::Access::OWNER
|
||||
)
|
||||
.with_prefix(:delete_protected_up_to)
|
||||
define_enum_for(:minimum_access_level_for_delete)
|
||||
.with_values(
|
||||
maintainer: Gitlab::Access::MAINTAINER,
|
||||
owner: Gitlab::Access::OWNER,
|
||||
admin: Gitlab::Access::ADMIN
|
||||
)
|
||||
.with_prefix(:minimum_access_level_for_delete)
|
||||
)
|
||||
}
|
||||
end
|
||||
|
|
@ -90,12 +90,12 @@ RSpec.describe ContainerRegistry::Protection::Rule, type: :model, feature_catego
|
|||
end
|
||||
end
|
||||
|
||||
describe '#delete_protected_up_to_access_level' do
|
||||
it { is_expected.to validate_presence_of(:delete_protected_up_to_access_level) }
|
||||
describe '#minimum_access_level_for_delete' do
|
||||
it { is_expected.to validate_presence_of(:minimum_access_level_for_delete) }
|
||||
end
|
||||
|
||||
describe '#push_protected_up_to_access_level' do
|
||||
it { is_expected.to validate_presence_of(:push_protected_up_to_access_level) }
|
||||
describe '#minimum_access_level_for_push' do
|
||||
it { is_expected.to validate_presence_of(:minimum_access_level_for_push) }
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -240,7 +240,7 @@ RSpec.describe ContainerRegistry::Protection::Rule, type: :model, feature_catego
|
|||
create(:container_registry_protection_rule,
|
||||
project: project_with_crpr,
|
||||
repository_path_pattern: "#{project_with_crpr.full_path}/my-container-stage*",
|
||||
push_protected_up_to_access_level: :developer
|
||||
minimum_access_level_for_push: :maintainer
|
||||
)
|
||||
end
|
||||
|
||||
|
|
@ -248,7 +248,7 @@ RSpec.describe ContainerRegistry::Protection::Rule, type: :model, feature_catego
|
|||
create(:container_registry_protection_rule,
|
||||
project: project_with_crpr,
|
||||
repository_path_pattern: "#{project_with_crpr.full_path}/my-container-prod*",
|
||||
push_protected_up_to_access_level: :maintainer
|
||||
minimum_access_level_for_push: :owner
|
||||
)
|
||||
end
|
||||
|
||||
|
|
@ -256,7 +256,7 @@ RSpec.describe ContainerRegistry::Protection::Rule, type: :model, feature_catego
|
|||
create(:container_registry_protection_rule,
|
||||
project: project_with_crpr,
|
||||
repository_path_pattern: "#{project_with_crpr.full_path}/my-container-release*",
|
||||
push_protected_up_to_access_level: :owner
|
||||
minimum_access_level_for_push: :admin
|
||||
)
|
||||
end
|
||||
|
||||
|
|
@ -264,7 +264,7 @@ RSpec.describe ContainerRegistry::Protection::Rule, type: :model, feature_catego
|
|||
create(:container_registry_protection_rule,
|
||||
project: project_with_crpr,
|
||||
repository_path_pattern: "#{project_with_crpr.full_path}/my-container-*",
|
||||
push_protected_up_to_access_level: :developer
|
||||
minimum_access_level_for_push: :maintainer
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -2090,15 +2090,12 @@ RSpec.describe Namespace, feature_category: :groups_and_projects do
|
|||
|
||||
where(:shared_runners_enabled, :allow_descendants_override_disabled_shared_runners, :other_setting, :result) do
|
||||
true | true | Namespace::SR_ENABLED | false
|
||||
true | true | Namespace::SR_DISABLED_WITH_OVERRIDE | true
|
||||
true | true | Namespace::SR_DISABLED_AND_OVERRIDABLE | true
|
||||
true | true | Namespace::SR_DISABLED_AND_UNOVERRIDABLE | true
|
||||
false | true | Namespace::SR_ENABLED | false
|
||||
false | true | Namespace::SR_DISABLED_WITH_OVERRIDE | false
|
||||
false | true | Namespace::SR_DISABLED_AND_OVERRIDABLE | false
|
||||
false | true | Namespace::SR_DISABLED_AND_UNOVERRIDABLE | true
|
||||
false | false | Namespace::SR_ENABLED | false
|
||||
false | false | Namespace::SR_DISABLED_WITH_OVERRIDE | false
|
||||
false | false | Namespace::SR_DISABLED_AND_OVERRIDABLE | false
|
||||
false | false | Namespace::SR_DISABLED_AND_UNOVERRIDABLE | false
|
||||
end
|
||||
|
|
|
|||
|
|
@ -818,8 +818,8 @@ RSpec.describe 'Query.runner(id)', :freeze_time, feature_category: :fleet_visibi
|
|||
'projectCount' => 2,
|
||||
'projects' => {
|
||||
'nodes' => [
|
||||
a_graphql_entity_for(project1),
|
||||
a_graphql_entity_for(project2)
|
||||
a_graphql_entity_for(project2),
|
||||
a_graphql_entity_for(project1)
|
||||
]
|
||||
})
|
||||
expect(runner2_data).to match a_hash_including(
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@ RSpec.describe 'Creating the container registry protection rule', :aggregate_fai
|
|||
{
|
||||
project_path: project.full_path,
|
||||
repository_path_pattern: container_registry_protection_rule_attributes.repository_path_pattern,
|
||||
push_protected_up_to_access_level: 'MAINTAINER',
|
||||
delete_protected_up_to_access_level: 'MAINTAINER'
|
||||
minimum_access_level_for_push: 'MAINTAINER',
|
||||
minimum_access_level_for_delete: 'MAINTAINER'
|
||||
}
|
||||
end
|
||||
|
||||
|
|
@ -67,11 +67,11 @@ RSpec.describe 'Creating the container registry protection rule', :aggregate_fai
|
|||
|
||||
it_behaves_like 'a successful response'
|
||||
|
||||
context 'with invalid input fields `pushProtectedUpToAccessLevel` and `deleteProtectedUpToAccessLevel`' do
|
||||
context 'with invalid input fields `minimumAccessLevelForPush` and `minimumAccessLevelForDelete`' do
|
||||
let(:kwargs) do
|
||||
super().merge(
|
||||
push_protected_up_to_access_level: 'UNKNOWN_ACCESS_LEVEL',
|
||||
delete_protected_up_to_access_level: 'UNKNOWN_ACCESS_LEVEL'
|
||||
minimum_access_level_for_push: 'UNKNOWN_ACCESS_LEVEL',
|
||||
minimum_access_level_for_delete: 'UNKNOWN_ACCESS_LEVEL'
|
||||
)
|
||||
end
|
||||
|
||||
|
|
@ -80,7 +80,7 @@ RSpec.describe 'Creating the container registry protection rule', :aggregate_fai
|
|||
it {
|
||||
subject
|
||||
|
||||
expect_graphql_errors_to_include([/pushProtectedUpToAccessLevel/, /deleteProtectedUpToAccessLevel/])
|
||||
expect_graphql_errors_to_include([/minimumAccessLevelForPush/, /minimumAccessLevelForDelete/])
|
||||
}
|
||||
end
|
||||
|
||||
|
|
@ -107,7 +107,7 @@ RSpec.describe 'Creating the container registry protection rule', :aggregate_fai
|
|||
context 'with existing containers protection rule' do
|
||||
let_it_be(:existing_container_registry_protection_rule) do
|
||||
create(:container_registry_protection_rule, project: project,
|
||||
push_protected_up_to_access_level: Gitlab::Access::DEVELOPER)
|
||||
minimum_access_level_for_push: Gitlab::Access::MAINTAINER)
|
||||
end
|
||||
|
||||
context 'when container name pattern is slightly different' do
|
||||
|
|
@ -128,7 +128,7 @@ RSpec.describe 'Creating the container registry protection rule', :aggregate_fai
|
|||
context 'when field `repository_path_pattern` is taken' do
|
||||
let(:kwargs) do
|
||||
super().merge(repository_path_pattern: existing_container_registry_protection_rule.repository_path_pattern,
|
||||
push_protected_up_to_access_level: 'MAINTAINER')
|
||||
minimum_access_level_for_push: 'OWNER')
|
||||
end
|
||||
|
||||
it_behaves_like 'an erroneous response'
|
||||
|
|
@ -144,7 +144,7 @@ RSpec.describe 'Creating the container registry protection rule', :aggregate_fai
|
|||
it 'does not create new container protection rules' do
|
||||
expect(::ContainerRegistry::Protection::Rule.where(project: project,
|
||||
repository_path_pattern: kwargs[:repository_path_pattern],
|
||||
push_protected_up_to_access_level: Gitlab::Access::MAINTAINER)).not_to exist
|
||||
minimum_access_level_for_push: Gitlab::Access::OWNER)).not_to exist
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -42,8 +42,8 @@ RSpec.describe 'Deleting a container registry protection rule', :aggregate_failu
|
|||
'containerRegistryProtectionRule' => {
|
||||
'id' => container_protection_rule.to_global_id.to_s,
|
||||
'repositoryPathPattern' => container_protection_rule.repository_path_pattern,
|
||||
'deleteProtectedUpToAccessLevel' => container_protection_rule.delete_protected_up_to_access_level.upcase,
|
||||
'pushProtectedUpToAccessLevel' => container_protection_rule.push_protected_up_to_access_level.upcase
|
||||
'minimumAccessLevelForDelete' => container_protection_rule.minimum_access_level_for_delete.upcase,
|
||||
'minimumAccessLevelForPush' => container_protection_rule.minimum_access_level_for_push.upcase
|
||||
}
|
||||
)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ RSpec.describe 'Updating the container registry protection rule', :aggregate_fai
|
|||
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be_with_reload(:container_registry_protection_rule) do
|
||||
create(:container_registry_protection_rule, project: project, push_protected_up_to_access_level: :developer)
|
||||
create(:container_registry_protection_rule, project: project, minimum_access_level_for_push: :maintainer)
|
||||
end
|
||||
|
||||
let_it_be(:current_user) { create(:user, maintainer_of: project) }
|
||||
|
|
@ -21,8 +21,8 @@ RSpec.describe 'Updating the container registry protection rule', :aggregate_fai
|
|||
<<~QUERY
|
||||
containerRegistryProtectionRule {
|
||||
repositoryPathPattern
|
||||
deleteProtectedUpToAccessLevel
|
||||
pushProtectedUpToAccessLevel
|
||||
minimumAccessLevelForDelete
|
||||
minimumAccessLevelForPush
|
||||
}
|
||||
clientMutationId
|
||||
errors
|
||||
|
|
@ -34,8 +34,8 @@ RSpec.describe 'Updating the container registry protection rule', :aggregate_fai
|
|||
{
|
||||
id: container_registry_protection_rule.to_global_id,
|
||||
repository_path_pattern: "#{container_registry_protection_rule.repository_path_pattern}-updated",
|
||||
delete_protected_up_to_access_level: 'OWNER',
|
||||
push_protected_up_to_access_level: 'MAINTAINER'
|
||||
minimum_access_level_for_delete: 'OWNER',
|
||||
minimum_access_level_for_push: 'MAINTAINER'
|
||||
}
|
||||
end
|
||||
|
||||
|
|
@ -52,8 +52,8 @@ RSpec.describe 'Updating the container registry protection rule', :aggregate_fai
|
|||
expect(mutation_response).to include(
|
||||
'containerRegistryProtectionRule' => {
|
||||
'repositoryPathPattern' => input[:repository_path_pattern],
|
||||
'deleteProtectedUpToAccessLevel' => input[:delete_protected_up_to_access_level],
|
||||
'pushProtectedUpToAccessLevel' => input[:push_protected_up_to_access_level]
|
||||
'minimumAccessLevelForDelete' => input[:minimum_access_level_for_delete],
|
||||
'minimumAccessLevelForPush' => input[:minimum_access_level_for_push]
|
||||
}
|
||||
)
|
||||
end
|
||||
|
|
@ -62,7 +62,7 @@ RSpec.describe 'Updating the container registry protection rule', :aggregate_fai
|
|||
subject.tap do
|
||||
expect(container_registry_protection_rule.reload).to have_attributes(
|
||||
repository_path_pattern: input[:repository_path_pattern],
|
||||
push_protected_up_to_access_level: input[:push_protected_up_to_access_level].downcase
|
||||
minimum_access_level_for_push: input[:minimum_access_level_for_push].downcase
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
@ -96,12 +96,12 @@ RSpec.describe 'Updating the container registry protection rule', :aggregate_fai
|
|||
end
|
||||
end
|
||||
|
||||
context 'with invalid input param `pushProtectedUpToAccessLevel`' do
|
||||
let(:input) { super().merge(push_protected_up_to_access_level: nil) }
|
||||
context 'with invalid input param `minimumAccessLevelForPush`' do
|
||||
let(:input) { super().merge(minimum_access_level_for_push: nil) }
|
||||
|
||||
it_behaves_like 'an erroneous response'
|
||||
|
||||
it { is_expected.tap { expect_graphql_errors_to_include(/pushProtectedUpToAccessLevel can't be blank/) } }
|
||||
it { is_expected.tap { expect_graphql_errors_to_include(/minimumAccessLevelForPush can't be blank/) } }
|
||||
end
|
||||
|
||||
context 'with invalid input param `repositoryPathPattern`' do
|
||||
|
|
|
|||
|
|
@ -65,23 +65,6 @@ RSpec.describe 'GroupUpdate', feature_category: :groups_and_projects do
|
|||
expect(group.reload.shared_runners_setting).to eq(variables[:shared_runners_setting].downcase)
|
||||
end
|
||||
|
||||
context 'when using DISABLED_WITH_OVERRIDE (deprecated)' do
|
||||
let(:variables) do
|
||||
{
|
||||
full_path: group.full_path,
|
||||
shared_runners_setting: 'DISABLED_WITH_OVERRIDE'
|
||||
}
|
||||
end
|
||||
|
||||
it 'updates shared runners settings with disabled_and_overridable' do
|
||||
post_graphql_mutation(mutation, current_user: user)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:success)
|
||||
expect(graphql_errors).to be_nil
|
||||
expect(group.reload.shared_runners_setting).to eq('disabled_and_overridable')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when bad arguments are provided' do
|
||||
let(:variables) { { full_path: '', shared_runners_setting: 'INVALID' } }
|
||||
|
||||
|
|
|
|||
|
|
@ -38,8 +38,8 @@ RSpec.describe 'getting the containers protection rules linked to a project', :a
|
|||
expect(protection_rules).to include(
|
||||
hash_including(
|
||||
'repositoryPathPattern' => container_protection_rule.repository_path_pattern,
|
||||
'pushProtectedUpToAccessLevel' => 'DEVELOPER',
|
||||
'deleteProtectedUpToAccessLevel' => 'DEVELOPER'
|
||||
'minimumAccessLevelForDelete' => 'MAINTAINER',
|
||||
'minimumAccessLevelForPush' => 'MAINTAINER'
|
||||
)
|
||||
)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -490,6 +490,7 @@ RSpec.describe API::Internal::Base, feature_category: :system_access do
|
|||
end
|
||||
|
||||
context "access granted" do
|
||||
let(:relative_path) { nil }
|
||||
let(:env) { {} }
|
||||
|
||||
around do |example|
|
||||
|
|
@ -515,7 +516,7 @@ RSpec.describe API::Internal::Base, feature_category: :system_access do
|
|||
include_context 'with env passed as a JSON'
|
||||
|
||||
it 'sets env in RequestStore' do
|
||||
expect(Gitlab::Git::HookEnv).to receive(:set).with(gl_repository, env.stringify_keys)
|
||||
expect(Gitlab::Git::HookEnv).to receive(:set).with(gl_repository, relative_path, env.stringify_keys)
|
||||
|
||||
subject
|
||||
|
||||
|
|
@ -551,7 +552,7 @@ RSpec.describe API::Internal::Base, feature_category: :system_access do
|
|||
end
|
||||
|
||||
it 'sets env in RequestStore and routes gRPC messages to primary', :request_store do
|
||||
expect(Gitlab::Git::HookEnv).to receive(:set).with(gl_repository, env.stringify_keys).and_call_original
|
||||
expect(Gitlab::Git::HookEnv).to receive(:set).with(gl_repository, relative_path, env.stringify_keys).and_call_original
|
||||
|
||||
subject
|
||||
|
||||
|
|
@ -560,21 +561,9 @@ RSpec.describe API::Internal::Base, feature_category: :system_access do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when Gitaly provides a relative_path argument', :request_store do
|
||||
subject { push(key, project, relative_path: relative_path) }
|
||||
|
||||
let(:relative_path) { 'relative_path' }
|
||||
|
||||
it 'stores relative_path value in RequestStore' do
|
||||
allow(Gitlab::SafeRequestStore).to receive(:[]=).and_call_original
|
||||
expect(Gitlab::SafeRequestStore).to receive(:[]=).with(:gitlab_git_relative_path, relative_path)
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
end
|
||||
|
||||
context "git push with project.wiki" do
|
||||
let(:relative_path) { project.wiki.repository.relative_path }
|
||||
|
||||
subject { push(key, project.wiki, env: env.to_json) }
|
||||
|
||||
it 'responds with success' do
|
||||
|
|
@ -625,7 +614,9 @@ RSpec.describe API::Internal::Base, feature_category: :system_access do
|
|||
# relative_path is sent from Gitaly to Rails when invoking internal API. In production it points to the
|
||||
# transaction's snapshot repository. As Gitaly is stubbed out from the invocation loop, there is no transaction
|
||||
# and thus no snapshot repository. Pass the original relative path.
|
||||
subject { push(key, personal_snippet, env: env.to_json, changes: snippet_changes, relative_path: "#{personal_snippet.repository.disk_path}.git") }
|
||||
let(:relative_path) { personal_snippet.repository.relative_path }
|
||||
|
||||
subject { push(key, personal_snippet, env: env.to_json, changes: snippet_changes) }
|
||||
|
||||
it 'responds with success' do
|
||||
subject
|
||||
|
|
@ -664,7 +655,9 @@ RSpec.describe API::Internal::Base, feature_category: :system_access do
|
|||
# relative_path is sent from Gitaly to Rails when invoking internal API. In production it points to the
|
||||
# transaction's snapshot repository. As Gitaly is stubbed out from the invocation loop, there is no transaction
|
||||
# and thus no snapshot repository. Pass the original relative path.
|
||||
subject { push(key, project_snippet, env: env.to_json, changes: snippet_changes, relative_path: "#{project_snippet.repository.disk_path}.git") }
|
||||
let(:relative_path) { project_snippet.repository.relative_path }
|
||||
|
||||
subject { push(key, project_snippet, env: env.to_json, changes: snippet_changes) }
|
||||
|
||||
it 'responds with success' do
|
||||
subject
|
||||
|
|
|
|||
|
|
@ -23,8 +23,8 @@ RSpec.describe ContainerRegistry::Protection::CreateRuleService, '#execute', fea
|
|||
be_a(ContainerRegistry::Protection::Rule)
|
||||
.and(have_attributes(
|
||||
repository_path_pattern: params[:repository_path_pattern],
|
||||
push_protected_up_to_access_level: params[:push_protected_up_to_access_level].to_s,
|
||||
delete_protected_up_to_access_level: params[:delete_protected_up_to_access_level].to_s
|
||||
minimum_access_level_for_push: params[:minimum_access_level_for_push].to_s,
|
||||
minimum_access_level_for_delete: params[:minimum_access_level_for_delete].to_s
|
||||
))
|
||||
}
|
||||
)
|
||||
|
|
@ -37,7 +37,7 @@ RSpec.describe ContainerRegistry::Protection::CreateRuleService, '#execute', fea
|
|||
ContainerRegistry::Protection::Rule.where(
|
||||
project: project,
|
||||
repository_path_pattern: params[:repository_path_pattern],
|
||||
push_protected_up_to_access_level: params[:push_protected_up_to_access_level]
|
||||
minimum_access_level_for_push: params[:minimum_access_level_for_push]
|
||||
)
|
||||
).to exist
|
||||
end
|
||||
|
|
@ -58,7 +58,7 @@ RSpec.describe ContainerRegistry::Protection::CreateRuleService, '#execute', fea
|
|||
ContainerRegistry::Protection::Rule.where(
|
||||
project: project,
|
||||
repository_path_pattern: params[:repository_path_pattern],
|
||||
push_protected_up_to_access_level: params[:push_protected_up_to_access_level]
|
||||
minimum_access_level_for_push: params[:minimum_access_level_for_push]
|
||||
)
|
||||
).not_to exist
|
||||
end
|
||||
|
|
@ -75,20 +75,20 @@ RSpec.describe ContainerRegistry::Protection::CreateRuleService, '#execute', fea
|
|||
it { is_expected.to have_attributes(message: match(/Repository path pattern can't be blank/)) }
|
||||
end
|
||||
|
||||
context 'when delete_protected_up_to_access_level is invalid' do
|
||||
let(:params) { super().merge(delete_protected_up_to_access_level: 1000) }
|
||||
context 'when minimum_access_level_for_delete is invalid' do
|
||||
let(:params) { super().merge(minimum_access_level_for_delete: 1000) }
|
||||
|
||||
it_behaves_like 'an erroneous service response'
|
||||
|
||||
it { is_expected.to have_attributes(message: match(/is not a valid delete_protected_up_to_access_level/)) }
|
||||
it { is_expected.to have_attributes(message: match(/is not a valid minimum_access_level_for_delete/)) }
|
||||
end
|
||||
|
||||
context 'when push_protected_up_to_access_level is invalid' do
|
||||
let(:params) { super().merge(push_protected_up_to_access_level: 1000) }
|
||||
context 'when minimum_access_level_for_push is invalid' do
|
||||
let(:params) { super().merge(minimum_access_level_for_push: 1000) }
|
||||
|
||||
it_behaves_like 'an erroneous service response'
|
||||
|
||||
it { is_expected.to have_attributes(message: match(/is not a valid push_protected_up_to_access_level/)) }
|
||||
it { is_expected.to have_attributes(message: match(/is not a valid minimum_access_level_for_push/)) }
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -102,8 +102,8 @@ RSpec.describe ContainerRegistry::Protection::CreateRuleService, '#execute', fea
|
|||
super().merge(
|
||||
# The field `repository_path_pattern` is unique; this is why we change the value in a minimum way
|
||||
repository_path_pattern: "#{existing_container_registry_protection_rule.repository_path_pattern}-unique",
|
||||
push_protected_up_to_access_level:
|
||||
existing_container_registry_protection_rule.push_protected_up_to_access_level
|
||||
minimum_access_level_for_push:
|
||||
existing_container_registry_protection_rule.minimum_access_level_for_push
|
||||
)
|
||||
end
|
||||
|
||||
|
|
@ -114,7 +114,7 @@ RSpec.describe ContainerRegistry::Protection::CreateRuleService, '#execute', fea
|
|||
let(:params) do
|
||||
super().merge(
|
||||
repository_path_pattern: existing_container_registry_protection_rule.repository_path_pattern,
|
||||
push_protected_up_to_access_level: :maintainer
|
||||
minimum_access_level_for_push: :owner
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@ RSpec.describe ContainerRegistry::Protection::UpdateRuleService, '#execute', fea
|
|||
attributes_for(
|
||||
:container_registry_protection_rule,
|
||||
repository_path_pattern: "#{container_registry_protection_rule.repository_path_pattern}-updated",
|
||||
delete_protected_up_to_access_level: 'owner',
|
||||
push_protected_up_to_access_level: 'owner'
|
||||
minimum_access_level_for_delete: 'owner',
|
||||
minimum_access_level_for_push: 'owner'
|
||||
)
|
||||
end
|
||||
|
||||
|
|
@ -77,8 +77,8 @@ RSpec.describe ContainerRegistry::Protection::UpdateRuleService, '#execute', fea
|
|||
{ repository_path_pattern: '' } | include("Repository path pattern can't be blank")
|
||||
{ repository_path_pattern: 'wrong-project-scope/repository-path' } | include("Repository path pattern should start with the project's full path")
|
||||
lazy { { repository_path_pattern: "#{project.full_path}/path-invalid-chars-#@" } } | include("Repository path pattern should be a valid container repository path with optional wildcard characters.")
|
||||
{ delete_protected_up_to_access_level: 1000 } | /not a valid delete_protected_up_to_access_level/
|
||||
{ push_protected_up_to_access_level: 1000 } | /not a valid push_protected_up_to_access_level/
|
||||
{ minimum_access_level_for_delete: 1000 } | /not a valid minimum_access_level_for_delete/
|
||||
{ minimum_access_level_for_push: 1000 } | /not a valid minimum_access_level_for_push/
|
||||
end
|
||||
# rubocop:enable Layout/LineLength
|
||||
|
||||
|
|
|
|||
|
|
@ -246,12 +246,6 @@ RSpec.describe Groups::UpdateSharedRunnersService, feature_category: :groups_and
|
|||
|
||||
include_examples 'allow descendants to override'
|
||||
end
|
||||
|
||||
context "when using SR_DISABLED_WITH_OVERRIDE" do
|
||||
let(:params) { { shared_runners_setting: Namespace::SR_DISABLED_WITH_OVERRIDE } }
|
||||
|
||||
include_examples 'allow descendants to override'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ module APIInternalBaseHelpers
|
|||
)
|
||||
end
|
||||
|
||||
def push(key, container, protocol = 'ssh', env: nil, changes: nil, relative_path: nil)
|
||||
def push(key, container, protocol = 'ssh', env: nil, changes: nil)
|
||||
push_with_path(
|
||||
key,
|
||||
full_path: full_path_for(container),
|
||||
|
|
@ -49,7 +49,7 @@ module APIInternalBaseHelpers
|
|||
protocol: protocol,
|
||||
env: env,
|
||||
changes: changes,
|
||||
relative_path: relative_path
|
||||
relative_path: container.repository.relative_path
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -1454,13 +1454,13 @@ RSpec.shared_examples 'a container registry auth service' do
|
|||
|
||||
context 'for different repository_path_patterns and current user roles' do
|
||||
# rubocop:disable Layout/LineLength -- Avoid formatting to keep one-line table layout
|
||||
where(:repository_path_pattern, :push_protected_up_to_access_level, :current_user, :shared_examples_name) do
|
||||
ref(:container_repository_path) | :developer | ref(:project_developer) | 'a protected container repository'
|
||||
ref(:container_repository_path) | :developer | ref(:project_owner) | 'a pushable'
|
||||
ref(:container_repository_path) | :maintainer | ref(:project_maintainer) | 'a protected container repository'
|
||||
ref(:container_repository_path) | :owner | ref(:project_owner) | 'a protected container repository'
|
||||
ref(:container_repository_path_pattern_no_match) | :developer | ref(:project_developer) | 'a pushable'
|
||||
ref(:container_repository_path_pattern_no_match) | :owner | ref(:project_owner) | 'a pushable'
|
||||
where(:repository_path_pattern, :minimum_access_level_for_push, :current_user, :shared_examples_name) do
|
||||
ref(:container_repository_path) | :maintainer | ref(:project_developer) | 'a protected container repository'
|
||||
ref(:container_repository_path) | :maintainer | ref(:project_owner) | 'a pushable'
|
||||
ref(:container_repository_path) | :owner | ref(:project_maintainer) | 'a protected container repository'
|
||||
ref(:container_repository_path) | :admin | ref(:project_owner) | 'a protected container repository'
|
||||
ref(:container_repository_path_pattern_no_match) | :maintainer | ref(:project_developer) | 'a pushable'
|
||||
ref(:container_repository_path_pattern_no_match) | :admin | ref(:project_owner) | 'a pushable'
|
||||
end
|
||||
# rubocop:enable Layout/LineLength
|
||||
|
||||
|
|
@ -1468,7 +1468,7 @@ RSpec.shared_examples 'a container registry auth service' do
|
|||
before do
|
||||
container_registry_protection_rule.update!(
|
||||
repository_path_pattern: repository_path_pattern,
|
||||
push_protected_up_to_access_level: push_protected_up_to_access_level
|
||||
minimum_access_level_for_push: minimum_access_level_for_push
|
||||
)
|
||||
end
|
||||
|
||||
|
|
@ -1480,7 +1480,7 @@ RSpec.shared_examples 'a container registry auth service' do
|
|||
let_it_be(:current_user) { project_maintainer }
|
||||
|
||||
before do
|
||||
container_registry_protection_rule.update!(push_protected_up_to_access_level: :maintainer)
|
||||
container_registry_protection_rule.update!(minimum_access_level_for_push: :owner)
|
||||
end
|
||||
|
||||
where(:current_params_scopes, :shared_examples_name) do
|
||||
|
|
@ -1505,18 +1505,18 @@ RSpec.shared_examples 'a container registry auth service' do
|
|||
end
|
||||
|
||||
context 'with matching package protection rule for all roles' do
|
||||
where(:repository_path_pattern, :push_protected_up_to_access_level, :shared_examples_name) do
|
||||
ref(:container_repository_path) | :developer | 'a pushable'
|
||||
ref(:container_repository_path) | :owner | 'a pushable'
|
||||
ref(:container_repository_path_pattern_no_match) | :developer | 'a pushable'
|
||||
ref(:container_repository_path_pattern_no_match) | :owner | 'a pushable'
|
||||
where(:repository_path_pattern, :minimum_access_level_for_push, :shared_examples_name) do
|
||||
ref(:container_repository_path) | :maintainer | 'a pushable'
|
||||
ref(:container_repository_path) | :admin | 'a pushable'
|
||||
ref(:container_repository_path_pattern_no_match) | :maintainer | 'a pushable'
|
||||
ref(:container_repository_path_pattern_no_match) | :admin | 'a pushable'
|
||||
end
|
||||
|
||||
with_them do
|
||||
before do
|
||||
container_registry_protection_rule.update!(
|
||||
repository_path_pattern: repository_path_pattern,
|
||||
push_protected_up_to_access_level: push_protected_up_to_access_level
|
||||
minimum_access_level_for_push: minimum_access_level_for_push
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue