Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
0ed5eb7634
commit
a913a36c8a
1
Gemfile
1
Gemfile
|
|
@ -74,6 +74,7 @@ gem 'devise-pbkdf2-encryptable', '~> 0.0.0', path: 'vendor/gems/devise-pbkdf2-en
|
|||
gem 'bcrypt', '~> 3.1', '>= 3.1.14' # rubocop:todo Gemfile/MissingFeatureCategory
|
||||
gem 'doorkeeper', '~> 5.6', '>= 5.6.6' # rubocop:todo Gemfile/MissingFeatureCategory
|
||||
gem 'doorkeeper-openid_connect', '~> 1.8', '>= 1.8.7' # rubocop:todo Gemfile/MissingFeatureCategory
|
||||
gem 'doorkeeper-device_authorization_grant', '~> 1.0.0', feature_category: :system_access
|
||||
gem 'rexml', '~> 3.2.6' # rubocop:todo Gemfile/MissingFeatureCategory
|
||||
gem 'ruby-saml', '~> 1.15.0' # rubocop:todo Gemfile/MissingFeatureCategory
|
||||
gem 'omniauth', '~> 2.1.0' # rubocop:todo Gemfile/MissingFeatureCategory
|
||||
|
|
|
|||
|
|
@ -128,6 +128,7 @@
|
|||
{"name":"docile","version":"1.4.0","platform":"ruby","checksum":"5f1734bde23721245c20c3d723e76c104208e1aa01277a69901ce770f0ebb8d3"},
|
||||
{"name":"domain_name","version":"0.5.20190701","platform":"ruby","checksum":"000a600454cb4a344769b2f10b531765ea7bd3a304fe47ed12e5ca1eab969851"},
|
||||
{"name":"doorkeeper","version":"5.6.6","platform":"ruby","checksum":"2344e86c77770526efcda893b5217aa13d1c7eb1b40de840b58b19eb1ff757e0"},
|
||||
{"name":"doorkeeper-device_authorization_grant","version":"1.0.3","platform":"ruby","checksum":"94c3ac12a0d50942850ecd58ed64298b397a5e903e8880cb68d4085600932679"},
|
||||
{"name":"doorkeeper-openid_connect","version":"1.8.7","platform":"ruby","checksum":"71edaf33118deefe25674ba3f8280c32835f057351f70e9beb222c0fd6b8e786"},
|
||||
{"name":"dotenv","version":"2.7.6","platform":"ruby","checksum":"2451ed5e8e43776d7a787e51d6f8903b98e446146c7ad143d5678cc2c409d547"},
|
||||
{"name":"dry-cli","version":"1.0.0","platform":"ruby","checksum":"28ead169f872954dd08910eb8ead59cf86cd18b4aab321e8eeefe945749569f0"},
|
||||
|
|
@ -621,7 +622,7 @@
|
|||
{"name":"sd_notify","version":"0.1.1","platform":"ruby","checksum":"cbc7ac6caa7cedd26b30a72b5eeb6f36050dc0752df263452ea24fb5a4ad3131"},
|
||||
{"name":"seed-fu","version":"2.3.7","platform":"ruby","checksum":"f19673443e9af799b730e3d4eca6a89b39e5a36825015dffd00d02ea3365cf74"},
|
||||
{"name":"selenium-webdriver","version":"4.21.1","platform":"ruby","checksum":"c30b64014532fc5156c60797985f839f36adbe60ff4653e7112b008dc1c83263"},
|
||||
{"name":"semver_dialects","version":"3.2.0","platform":"ruby","checksum":"11559c8bd77db40be1e9312598c94c1b1b1e2129785d030a19f0db4b11f5555f"},
|
||||
{"name":"semver_dialects","version":"3.2.1","platform":"ruby","checksum":"4547361a8f6589b9cbc4c3f6f52bef19cebb7d3beca719aa9ddb1f63788d4b23"},
|
||||
{"name":"sentry-rails","version":"5.17.3","platform":"ruby","checksum":"017771c42d739c0ad2213a581ca9d005cf543227bc13662cd1ca9909f2429459"},
|
||||
{"name":"sentry-ruby","version":"5.17.3","platform":"ruby","checksum":"61791a4b0bb0f95cd87aceeaa1efa6d4ab34d64236c9d5df820478adfe2fbbfc"},
|
||||
{"name":"sentry-sidekiq","version":"5.17.3","platform":"ruby","checksum":"d0714a218999e41e38127d0c174e0ee62a32b069f92e85b544e0c2125eca2c58"},
|
||||
|
|
|
|||
|
|
@ -503,6 +503,8 @@ GEM
|
|||
unf (>= 0.0.5, < 1.0.0)
|
||||
doorkeeper (5.6.6)
|
||||
railties (>= 5)
|
||||
doorkeeper-device_authorization_grant (1.0.3)
|
||||
doorkeeper (~> 5.5)
|
||||
doorkeeper-openid_connect (1.8.7)
|
||||
doorkeeper (>= 5.5, < 5.7)
|
||||
jwt (>= 2.5)
|
||||
|
|
@ -1652,7 +1654,7 @@ GEM
|
|||
rexml (~> 3.2, >= 3.2.5)
|
||||
rubyzip (>= 1.2.2, < 3.0)
|
||||
websocket (~> 1.0)
|
||||
semver_dialects (3.2.0)
|
||||
semver_dialects (3.2.1)
|
||||
deb_version (~> 1.0.1)
|
||||
pastel (~> 0.8.0)
|
||||
thor (~> 1.3)
|
||||
|
|
@ -1973,6 +1975,7 @@ DEPENDENCIES
|
|||
diffy (~> 3.4)
|
||||
discordrb-webhooks (~> 3.5)
|
||||
doorkeeper (~> 5.6, >= 5.6.6)
|
||||
doorkeeper-device_authorization_grant (~> 1.0.0)
|
||||
doorkeeper-openid_connect (~> 1.8, >= 1.8.7)
|
||||
duo_api (~> 1.3)
|
||||
ed25519 (~> 1.3.0)
|
||||
|
|
|
|||
|
|
@ -28,11 +28,7 @@ export default {
|
|||
containerClasses: ['dag-graph-container', 'gl-display-flex', 'gl-flex-direction-column'].join(
|
||||
' ',
|
||||
),
|
||||
hoverFadeClasses: [
|
||||
'gl-cursor-pointer',
|
||||
'gl-duration-slow',
|
||||
'gl-transition-timing-function-ease',
|
||||
].join(' '),
|
||||
hoverFadeClasses: ['gl-cursor-pointer', 'gl-duration-slow', 'gl-ease-ease'].join(' '),
|
||||
},
|
||||
gitLabColorRotation: [
|
||||
'#e17223',
|
||||
|
|
|
|||
|
|
@ -152,7 +152,7 @@ export default {
|
|||
:key="link.path"
|
||||
:ref="link.ref"
|
||||
:d="link.path"
|
||||
class="gl-fill-transparent gl-duration-slow gl-transition-timing-function-ease"
|
||||
class="gl-fill-transparent gl-duration-slow gl-ease-ease"
|
||||
:class="getLinkClasses(link)"
|
||||
:stroke-width="$options.STROKE_WIDTH"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -218,10 +218,7 @@ export default {
|
|||
:pipeline-id="pipelineId"
|
||||
:stage-name="showStageName ? group.stageName : ''"
|
||||
:css-class-job-name="$options.jobClasses"
|
||||
:class="[
|
||||
{ 'gl-opacity-3': isFadedOut(group.name) },
|
||||
'gl-duration-slow gl-transition-timing-function-ease',
|
||||
]"
|
||||
:class="[{ 'gl-opacity-3': isFadedOut(group.name) }, 'gl-duration-slow gl-ease-ease']"
|
||||
@pipelineActionRequestComplete="$emit('refreshPipelineGraph')"
|
||||
@setSkipRetryModal="$emit('setSkipRetryModal')"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ export default {
|
|||
<tooltip-on-truncate :title="jobName" truncate-target="child" placement="top">
|
||||
<div
|
||||
:id="id"
|
||||
class="gl-bg-white gl-shadow-inner-1-gray-100 gl-text-center gl-text-truncate gl-rounded-6 gl-mb-3 gl-px-5 gl-py-3 gl-relative gl-z-1 gl-duration-slow gl-transition-timing-function-ease"
|
||||
class="gl-bg-white gl-shadow-inner-1-gray-100 gl-text-center gl-text-truncate gl-rounded-6 gl-mb-3 gl-px-5 gl-py-3 gl-relative gl-z-1 gl-duration-slow gl-ease-ease"
|
||||
:class="jobPillClasses"
|
||||
@mouseover="onMouseEnter"
|
||||
@mouseleave="onMouseLeave"
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { debounce, isEmpty, isNull } from 'lodash';
|
|||
import { GlAvatarLabeled, GlButton, GlCollapsibleListbox } from '@gitlab/ui';
|
||||
import { __, s__ } from '~/locale';
|
||||
import { createAlert } from '~/alert';
|
||||
import { getFirstPropertyValue } from '~/lib/utils/common_utils';
|
||||
|
||||
import searchUsersQuery from '~/graphql_shared/queries/users_search_all_paginated.query.graphql';
|
||||
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
|
||||
|
|
@ -11,13 +12,15 @@ import {
|
|||
PLACEHOLDER_STATUS_AWAITING_APPROVAL,
|
||||
PLACEHOLDER_STATUS_REASSIGNING,
|
||||
} from '~/import_entities/import_groups/constants';
|
||||
import importSourceUserReassignMutation from '../../placeholders/graphql/mutations/reassign.mutation.graphql';
|
||||
import importSourceUserKeepAsPlaceholderMutation from '../../placeholders/graphql/mutations/keep_as_placeholder.mutation.graphql';
|
||||
import importSourceUserCancelReassignmentMutation from '../../placeholders/graphql/mutations/cancel_reassignment.mutation.graphql';
|
||||
|
||||
const USERS_PER_PAGE = 20;
|
||||
|
||||
const createUserObject = (user) => ({
|
||||
...user,
|
||||
text: user.name,
|
||||
username: `@${user.username}`,
|
||||
value: user.id,
|
||||
});
|
||||
|
||||
|
|
@ -29,7 +32,7 @@ export default {
|
|||
GlCollapsibleListbox,
|
||||
},
|
||||
props: {
|
||||
placeholder: {
|
||||
sourceUser: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => ({}),
|
||||
|
|
@ -38,6 +41,9 @@ export default {
|
|||
|
||||
data() {
|
||||
return {
|
||||
isConfirmLoading: false,
|
||||
isCancelLoading: false,
|
||||
isNotifyLoading: false,
|
||||
isLoadingInitial: true,
|
||||
isLoadingMore: false,
|
||||
isValidated: false,
|
||||
|
|
@ -97,7 +103,7 @@ export default {
|
|||
}
|
||||
|
||||
if (this.selectedUser) {
|
||||
return this.selectedUser.username;
|
||||
return `@${this.selectedUser.username}`;
|
||||
}
|
||||
|
||||
return s__('UserMapping|Select user');
|
||||
|
|
@ -112,16 +118,16 @@ export default {
|
|||
},
|
||||
|
||||
statusIsAwaitingApproval() {
|
||||
return this.placeholder.status === PLACEHOLDER_STATUS_AWAITING_APPROVAL;
|
||||
return this.sourceUser.status === PLACEHOLDER_STATUS_AWAITING_APPROVAL;
|
||||
},
|
||||
statusIsReassigning() {
|
||||
return this.placeholder.status === PLACEHOLDER_STATUS_REASSIGNING;
|
||||
return this.sourceUser.status === PLACEHOLDER_STATUS_REASSIGNING;
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
if (this.statusIsAwaitingApproval || this.statusIsReassigning) {
|
||||
this.selectedUser = this.placeholder.reassignToUser;
|
||||
this.selectedUser = this.sourceUser.reassignToUser;
|
||||
}
|
||||
|
||||
this.debouncedSetSearch = debounce(this.setSearch, DEFAULT_DEBOUNCE_AND_THROTTLE_MS);
|
||||
|
|
@ -176,15 +182,64 @@ export default {
|
|||
},
|
||||
|
||||
onNotify() {
|
||||
this.isNotifyLoading = true;
|
||||
this.$toast.show(s__('UserMapping|Notification email sent.'));
|
||||
this.isNotifyLoading = false;
|
||||
},
|
||||
onCancel() {
|
||||
this.$emit('cancel');
|
||||
this.isCancelLoading = true;
|
||||
this.$apollo
|
||||
.mutate({
|
||||
mutation: importSourceUserCancelReassignmentMutation,
|
||||
variables: {
|
||||
id: this.sourceUser.id,
|
||||
},
|
||||
})
|
||||
.then(({ data }) => {
|
||||
const { errors } = getFirstPropertyValue(data);
|
||||
if (errors?.length) {
|
||||
createAlert({ message: errors.join() });
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
createAlert({
|
||||
message: s__(
|
||||
'UserMapping|There was a problem cancelling placeholder user reassignment.',
|
||||
),
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
this.isCancelLoading = false;
|
||||
});
|
||||
},
|
||||
onConfirm() {
|
||||
this.isValidated = true;
|
||||
if (!this.userSelectInvalid) {
|
||||
this.$emit('confirm', this.selectedUserValue);
|
||||
this.isConfirmLoading = true;
|
||||
this.$apollo
|
||||
.mutate({
|
||||
mutation: this.selectedUser.id
|
||||
? importSourceUserReassignMutation
|
||||
: importSourceUserKeepAsPlaceholderMutation,
|
||||
variables: {
|
||||
id: this.sourceUser.id,
|
||||
...(this.selectedUser.id ? { userId: this.selectedUser.id } : {}),
|
||||
},
|
||||
})
|
||||
.then(({ data }) => {
|
||||
const { errors } = getFirstPropertyValue(data);
|
||||
if (errors?.length) {
|
||||
createAlert({ message: errors.join() });
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
createAlert({
|
||||
message: s__('UserMapping|There was a problem reassigning placeholder user.'),
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
this.isConfirmLoading = false;
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
@ -220,7 +275,7 @@ export default {
|
|||
:size="32"
|
||||
:src="item.avatarUrl"
|
||||
:label="item.text"
|
||||
:sub-label="item.username"
|
||||
:sub-label="`@${item.username}`"
|
||||
/>
|
||||
</template>
|
||||
|
||||
|
|
@ -246,15 +301,28 @@ export default {
|
|||
</div>
|
||||
|
||||
<template v-if="statusIsAwaitingApproval || statusIsReassigning">
|
||||
<gl-button :disabled="statusIsReassigning" data-testid="notify-button" @click="onNotify">{{
|
||||
s__('UserMapping|Notify again')
|
||||
}}</gl-button>
|
||||
<gl-button :disabled="statusIsReassigning" data-testid="cancel-button" @click="onCancel">{{
|
||||
__('Cancel')
|
||||
}}</gl-button>
|
||||
<gl-button
|
||||
:disabled="statusIsReassigning"
|
||||
:loading="isNotifyLoading"
|
||||
data-testid="notify-button"
|
||||
@click="onNotify"
|
||||
>{{ s__('UserMapping|Notify again') }}</gl-button
|
||||
>
|
||||
<gl-button
|
||||
:disabled="statusIsReassigning"
|
||||
:loading="isCancelLoading"
|
||||
data-testid="cancel-button"
|
||||
@click="onCancel"
|
||||
>{{ __('Cancel') }}</gl-button
|
||||
>
|
||||
</template>
|
||||
<gl-button v-else variant="confirm" data-testid="confirm-button" @click="onConfirm">{{
|
||||
confirmText
|
||||
}}</gl-button>
|
||||
<gl-button
|
||||
v-else
|
||||
variant="confirm"
|
||||
:loading="isConfirmLoading"
|
||||
data-testid="confirm-button"
|
||||
@click="onConfirm"
|
||||
>{{ confirmText }}</gl-button
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -84,17 +84,19 @@ export default {
|
|||
|
||||
isReassignedItem(item) {
|
||||
return (
|
||||
(item.status === PLACEHOLDER_STATUS_KEPT_AS_PLACEHOLDER ||
|
||||
item.status === PLACEHOLDER_STATUS_COMPLETED) &&
|
||||
item.reassignToUser
|
||||
item.status === PLACEHOLDER_STATUS_KEPT_AS_PLACEHOLDER ||
|
||||
item.status === PLACEHOLDER_STATUS_COMPLETED
|
||||
);
|
||||
},
|
||||
reassginedUser(item) {
|
||||
if (item.status === PLACEHOLDER_STATUS_KEPT_AS_PLACEHOLDER) {
|
||||
return item.placeholderUser;
|
||||
}
|
||||
if (item.status === PLACEHOLDER_STATUS_COMPLETED) {
|
||||
return item.reassignToUser;
|
||||
}
|
||||
|
||||
onCancel(item) {
|
||||
this.$emit('cancel', item);
|
||||
},
|
||||
onConfirm(item, selectedUserId) {
|
||||
this.$emit('confirm', item, selectedUserId);
|
||||
return {};
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
@ -134,18 +136,13 @@ export default {
|
|||
|
||||
<template #cell(actions)="{ item }">
|
||||
<gl-avatar-labeled
|
||||
v-if="isReassignedItem(item) && item.reassignToUser"
|
||||
v-if="isReassignedItem(item)"
|
||||
:size="32"
|
||||
:src="item.reassignToUser.avatarUrl"
|
||||
:label="item.reassignToUser.name"
|
||||
:sub-label="`@${item.reassignToUser.username}`"
|
||||
/>
|
||||
<placeholder-actions
|
||||
v-else
|
||||
:placeholder="item"
|
||||
@confirm="onConfirm(item, $event)"
|
||||
@cancel="onCancel(item)"
|
||||
:src="reassginedUser(item).avatarUrl"
|
||||
:label="reassginedUser(item).name"
|
||||
:sub-label="`@${reassginedUser(item).username}`"
|
||||
/>
|
||||
<placeholder-actions v-else :source-user="item" />
|
||||
</template>
|
||||
</gl-table>
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
#import "../fragments/import_source_user.fragment.graphql"
|
||||
|
||||
mutation cancelReassignment($id: ImportSourceUserID!) {
|
||||
importSourceUserCancelReassignment(input: { id: $id }) {
|
||||
errors
|
||||
importSourceUser {
|
||||
...ImportSourceUser
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
#import "../fragments/import_source_user.fragment.graphql"
|
||||
|
||||
mutation keepAsPlaceholder($id: ImportSourceUserID!) {
|
||||
importSourceUserKeepAsPlaceholder(input: { id: $id }) {
|
||||
errors
|
||||
importSourceUser {
|
||||
...ImportSourceUser
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
#import "../fragments/import_source_user.fragment.graphql"
|
||||
|
||||
mutation reassignPlaceholder($id: ImportSourceUserID!, $userId: UserID!) {
|
||||
importSourceUserReassign(input: { id: $id, assigneeUserId: $userId }) {
|
||||
errors
|
||||
importSourceUser {
|
||||
...ImportSourceUser
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +1,22 @@
|
|||
<script>
|
||||
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
import TokenPermissions from './token_permissions.vue';
|
||||
import OutboundTokenAccess from './outbound_token_access.vue';
|
||||
import InboundTokenAccess from './inbound_token_access.vue';
|
||||
|
||||
export default {
|
||||
name: 'TokenAccessApp',
|
||||
components: {
|
||||
OutboundTokenAccess,
|
||||
InboundTokenAccess,
|
||||
OutboundTokenAccess,
|
||||
TokenPermissions,
|
||||
},
|
||||
mixins: [glFeatureFlagsMixin()],
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<token-permissions v-if="glFeatures.allowPushRepositoryForJobToken" />
|
||||
<inbound-token-access />
|
||||
<outbound-token-access />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,131 @@
|
|||
<script>
|
||||
import { GlButton, GlCard, GlFormCheckbox, GlIcon, GlLink, GlLoadingIcon } from '@gitlab/ui';
|
||||
import { createAlert } from '~/alert';
|
||||
import { __, sprintf } from '~/locale';
|
||||
import { helpPagePath } from '~/helpers/help_page_helper';
|
||||
import updateCiJobTokenPermissionsMutation from '../graphql/mutations/update_ci_job_token_permissions.mutation.graphql';
|
||||
import getCiJobTokenPermissionsQuery from '../graphql/queries/get_ci_job_token_permissions.query.graphql';
|
||||
|
||||
export default {
|
||||
name: 'TokenPermissions',
|
||||
components: {
|
||||
GlButton,
|
||||
GlCard,
|
||||
GlFormCheckbox,
|
||||
GlIcon,
|
||||
GlLink,
|
||||
GlLoadingIcon,
|
||||
},
|
||||
inject: ['fullPath'],
|
||||
apollo: {
|
||||
ciCdSettings: {
|
||||
query: getCiJobTokenPermissionsQuery,
|
||||
variables() {
|
||||
return {
|
||||
fullPath: this.fullPath,
|
||||
};
|
||||
},
|
||||
update({ data }) {
|
||||
return data?.project?.ciCdSettings;
|
||||
},
|
||||
result({ data }) {
|
||||
this.projectName = data?.project?.name;
|
||||
this.allowPushToRepo = data?.project?.ciCdSettings?.pushRepositoryForJobTokenAllowed;
|
||||
},
|
||||
error() {
|
||||
createAlert({
|
||||
message: __('There was a problem fetching the CI/CD job token permissions.'),
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
allowPushToRepo: false,
|
||||
isUpdating: false,
|
||||
projectName: '',
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isPermissionsQueryLoading() {
|
||||
return this.$apollo.queries.ciCdSettings.loading;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
updateAllowPushToRepo(value) {
|
||||
this.allowPushToRepo = value;
|
||||
},
|
||||
async updateCiJobTokenPermissions() {
|
||||
this.isUpdating = true;
|
||||
|
||||
try {
|
||||
const {
|
||||
data: {
|
||||
projectCiCdSettingsUpdate: { errors },
|
||||
},
|
||||
} = await this.$apollo.mutate({
|
||||
mutation: updateCiJobTokenPermissionsMutation,
|
||||
variables: {
|
||||
input: {
|
||||
fullPath: this.fullPath,
|
||||
pushRepositoryForJobTokenAllowed: this.allowPushToRepo,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (errors.length > 0) {
|
||||
throw new Error(errors[0]);
|
||||
} else {
|
||||
const toastMessage = sprintf(
|
||||
__("CI/CD job token permissions for '%{projectName}' were successfully updated."),
|
||||
{ projectName: this.projectName },
|
||||
);
|
||||
this.$toast.show(toastMessage);
|
||||
}
|
||||
} catch (error) {
|
||||
createAlert({ message: error.message });
|
||||
} finally {
|
||||
this.isUpdating = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
docsLink: helpPagePath('ci/jobs/ci_job_token', {
|
||||
anchor: 'push-to-a-project-repository-using-a-job-token',
|
||||
}),
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<gl-loading-icon v-if="isPermissionsQueryLoading" size="lg" class="gl-mt-5" />
|
||||
<gl-card v-else class="gl-new-card" header-class="gl-new-card-header">
|
||||
<template #header>
|
||||
<div class="gl-new-card-title-wrapper gl-flex-col gl-flex-wrap">
|
||||
<div class="gl-new-card-title">
|
||||
<h5 class="gl-mt-0 gl-mb-2">{{ s__('CICD|Additional permissions') }}</h5>
|
||||
</div>
|
||||
<p class="gl-text-secondary gl-my-0">
|
||||
{{
|
||||
s__("CICD|Grant additional access permissions to this project's CI/CD job tokens.")
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
<gl-form-checkbox :checked="allowPushToRepo" @input="updateAllowPushToRepo">
|
||||
{{ s__('CICD|Allow Git push requests to the repository') }}
|
||||
<p class="gl-text-secondary gl-mb-3">
|
||||
{{
|
||||
s__(
|
||||
'CICD|CI/CD job token can be used to authenticate a Git push to this repository, using the permissions of the user that started the job.',
|
||||
)
|
||||
}}<gl-link :href="$options.docsLink" target="_blank">
|
||||
<gl-icon name="question-o" class="gl-ml-2 gl-text-blue-500" />
|
||||
</gl-link>
|
||||
</p>
|
||||
</gl-form-checkbox>
|
||||
|
||||
<gl-button variant="confirm" :loading="isUpdating" @click="updateCiJobTokenPermissions"
|
||||
>{{ __('Save Changes') }}
|
||||
</gl-button>
|
||||
</gl-card>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
mutation updateCiJobTokenPermissions($input: ProjectCiCdSettingsUpdateInput!) {
|
||||
projectCiCdSettingsUpdate(input: $input) {
|
||||
ciCdSettings {
|
||||
pushRepositoryForJobTokenAllowed
|
||||
}
|
||||
errors
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
query getCiJobTokenPermissions($fullPath: ID!) {
|
||||
project(fullPath: $fullPath) {
|
||||
id
|
||||
name
|
||||
ciCdSettings {
|
||||
pushRepositoryForJobTokenAllowed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
import { GlToast } from '@gitlab/ui';
|
||||
import Vue from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import createDefaultClient from '~/lib/graphql';
|
||||
|
|
@ -5,6 +6,7 @@ import TokenAccessApp from './components/token_access_app.vue';
|
|||
import cacheConfig from './graphql/cache_config';
|
||||
|
||||
Vue.use(VueApollo);
|
||||
Vue.use(GlToast);
|
||||
|
||||
const apolloProvider = new VueApollo({
|
||||
defaultClient: createDefaultClient({}, { cacheConfig }),
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { InternalEvents } from '~/tracking';
|
|||
import {
|
||||
VIEW_MERGE_REQUEST_WIDGET,
|
||||
TELEMETRY_WIDGET_EXPANDED,
|
||||
TELEMETRY_WIDGET_FULL_REPORT_CLICKED,
|
||||
CLICK_FULL_REPORT_ON_MERGE_REQUEST_WIDGET,
|
||||
} from '../../constants';
|
||||
|
||||
function simplifyWidgetName(componentName) {
|
||||
|
|
@ -43,7 +43,6 @@ function whenable(bus) {
|
|||
function defaultBehaviorEvents({ bus, config }) {
|
||||
const when = whenable(bus);
|
||||
const isExpanded = when(TELEMETRY_WIDGET_EXPANDED);
|
||||
const fullReportIsClicked = when(TELEMETRY_WIDGET_FULL_REPORT_CLICKED);
|
||||
const toHll = config?.uniqueUser || {};
|
||||
const toCounts = config?.counter || {};
|
||||
const user = api.trackRedisHllUserEvent.bind(api);
|
||||
|
|
@ -75,13 +74,6 @@ function defaultBehaviorEvents({ bus, config }) {
|
|||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (toHll.clickFullReport) {
|
||||
fullReportIsClicked({ execute: user, track: toHll.clickFullReport });
|
||||
}
|
||||
if (toCounts.clickFullReport) {
|
||||
fullReportIsClicked({ execute: count, track: toCounts.clickFullReport });
|
||||
}
|
||||
}
|
||||
|
||||
function baseTelemetry(componentName) {
|
||||
|
|
@ -101,11 +93,9 @@ function baseTelemetry(componentName) {
|
|||
return {
|
||||
uniqueUser: {
|
||||
expand: [`${baseRedisEventName(simpleExtensionName)}_expand`],
|
||||
clickFullReport: [`${baseRedisEventName(simpleExtensionName)}_click_full_report`],
|
||||
},
|
||||
counter: {
|
||||
expand: [`${baseRedisEventName(simpleExtensionName)}_count_expand`],
|
||||
clickFullReport: [`${baseRedisEventName(simpleExtensionName)}_count_click_full_report`],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
@ -126,7 +116,9 @@ export function createTelemetryHub(componentName) {
|
|||
bus.$emit(TELEMETRY_WIDGET_EXPANDED, { type });
|
||||
},
|
||||
fullReportClicked() {
|
||||
bus.$emit(TELEMETRY_WIDGET_FULL_REPORT_CLICKED);
|
||||
InternalEvents.trackEvent(CLICK_FULL_REPORT_ON_MERGE_REQUEST_WIDGET, {
|
||||
label: baseWidgetName(simplifyWidgetName(componentName)),
|
||||
});
|
||||
},
|
||||
/* I want a Record here: #{ ...config } // and then the comment would be: This is for debugging only, changing your reference to it does nothing. 😘 */
|
||||
config: Object.freeze({ ...config }), // This is *intended* to be for debugging only, but it's pretty mutable, and it has references to live data in child props
|
||||
|
|
|
|||
|
|
@ -184,7 +184,8 @@ export const EXTENSION_ICON_CLASS = {
|
|||
|
||||
export const VIEW_MERGE_REQUEST_WIDGET = 'view_merge_request_widget';
|
||||
export const TELEMETRY_WIDGET_EXPANDED = 'WIDGET_EXPANDED';
|
||||
export const TELEMETRY_WIDGET_FULL_REPORT_CLICKED = 'WIDGET_FULL_REPORT_CLICKED';
|
||||
export const CLICK_FULL_REPORT_ON_MERGE_REQUEST_WIDGET =
|
||||
'click_full_report_on_merge_request_widget';
|
||||
|
||||
export { STATE_MACHINE };
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Oauth
|
||||
class DeviceAuthorizationsController < Doorkeeper::DeviceAuthorizationGrant::DeviceAuthorizationsController
|
||||
layout 'minimal'
|
||||
|
||||
def index
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
render "doorkeeper/device_authorization_grant/index"
|
||||
end
|
||||
format.json { head :no_content }
|
||||
end
|
||||
end
|
||||
|
||||
def confirm
|
||||
# rubocop:disable CodeReuse/ActiveRecord -- We are using .find_by here because the models are part of the Doorkeeper gem.
|
||||
device_grant = device_grant_model.find_by(user_code: user_code)
|
||||
# rubocop:enable CodeReuse/ActiveRecord
|
||||
@scopes = device_grant&.scopes || ''
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
render "doorkeeper/device_authorization_grant/authorize"
|
||||
end
|
||||
format.json { head :no_content }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Oauth
|
||||
class DeviceCodesController < Doorkeeper::DeviceAuthorizationGrant::DeviceCodesController
|
||||
def create
|
||||
# rubocop:disable Gitlab/FeatureFlagWithoutActor -- Does not execute in user context
|
||||
return :not_found unless Feature.enabled?(:oauth2_device_grant_flow)
|
||||
|
||||
# rubocop:enable Gitlab/FeatureFlagWithoutActor
|
||||
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -15,6 +15,7 @@ module Projects
|
|||
|
||||
before_action do
|
||||
push_frontend_feature_flag(:ci_variables_pages, current_user)
|
||||
push_frontend_feature_flag(:allow_push_repository_for_job_token, @project)
|
||||
end
|
||||
|
||||
helper_method :highlight_badge
|
||||
|
|
|
|||
|
|
@ -261,7 +261,7 @@ class ContainerRepository < ApplicationRecord
|
|||
end
|
||||
|
||||
def last_published_at
|
||||
return unless migrated_and_can_access_the_gitlab_api?
|
||||
return unless gitlab_api_client.supports_gitlab_api?
|
||||
|
||||
timestamp_string = gitlab_api_client_repository_details['last_published_at']
|
||||
DateTime.iso8601(timestamp_string)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
- add_page_specific_style 'page_bundles/settings'
|
||||
- @force_desktop_expanded_sidebar = true
|
||||
|
||||
%div{ data: { event_tracking_load: 'true', event_tracking: 'view_admin_application_settings_general_pageload' } }
|
||||
|
||||
= render ::Layouts::SettingsBlockComponent.new(_('Visibility and access controls'),
|
||||
id: 'js-visibility-settings',
|
||||
testid: 'admin-visibility-access-settings',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
.gl-mx-auto{ class: 'sm:gl-w-1/2' }
|
||||
.gl-justify-start.gl-items-center
|
||||
.gl-heading-1
|
||||
= s_('DeviceAuth|Authorize device to access to your GitLab account.')
|
||||
.gl-text-secondary
|
||||
= s_('DeviceAuth|Please make sure that you intended to authorize this device.')
|
||||
.gl-flex.gl-items-center.gl-gap-2.gl-py-5
|
||||
= render Pajamas::AvatarComponent.new(current_user, size: 24, avatar_options: { data: { testid: 'user_avatar_content' }, title: current_user.username })
|
||||
.gl-pl-1
|
||||
%strong= current_user.name
|
||||
·
|
||||
.gl-text-secondary
|
||||
%span= current_user.to_reference
|
||||
- if current_user.admin?
|
||||
= render Pajamas::AlertComponent.new(variant: :warning, dismissible: false, alert_options: { class: 'gl-mb-5'}) do |c|
|
||||
- c.with_body do
|
||||
= s_('DeviceAuth|You are an administrator, which means authorizing access will allow it to interact with GitLab as an administrator as well.')
|
||||
.div
|
||||
- if @scopes.present?
|
||||
.gl-mt-3
|
||||
.gl-heading-4
|
||||
= s_('DeviceAuth|Scopes associated with this request:') + " #{@scopes}"
|
||||
.div
|
||||
= form_with url: oauth_device_authorizations_authorize_url, method: :post, class: 'inline gl-pr-3' do |f|
|
||||
.form-group.row
|
||||
.col-lg-8.col-sm-10
|
||||
= f.hidden_field :user_code, value: params[:user_code]
|
||||
.div
|
||||
= render Pajamas::ButtonComponent.new(type: :submit,
|
||||
variant: :confirm,
|
||||
button_options: { id: 'commit-changes', testid: 'authorization-button'}) do
|
||||
= s_('DeviceAuth|Confirm')
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
.gl-mx-auto{ class: 'sm:gl-w-1/2' }
|
||||
.gl-justify-start.gl-items-center
|
||||
.gl-heading-1
|
||||
= s_('DeviceAuth|Authorize device to access to your GitLab account.')
|
||||
.div
|
||||
= form_with url: confirm_oauth_device_path, method: :post, class: 'inline gl-pr-3' do |f|
|
||||
.form-group.row
|
||||
.col-lg-8.col-sm-10
|
||||
= f.label :user_code
|
||||
= text_field_tag :user_code, params[:user_code]
|
||||
.div
|
||||
= render Pajamas::ButtonComponent.new(type: :submit,
|
||||
variant: :confirm,
|
||||
button_options: { id: 'commit-changes', testid: 'authorization-button'}) do
|
||||
= s_('DeviceAuth|Authorize')
|
||||
|
|
@ -109,7 +109,7 @@
|
|||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||
= expanded ? _('Collapse') : _('Expand')
|
||||
%p.gl-text-secondary
|
||||
= _("Control whether CI/CD job tokens can be used to authenticate with this project.")
|
||||
= _("Control which CI/CD job tokens can be used to authenticate with this project.")
|
||||
.settings-content
|
||||
= render 'ci/token_access/index'
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
description: Tracks that "full report" was clicked on a merge request widget
|
||||
internal_events: true
|
||||
action: click_full_report_on_merge_request_widget
|
||||
identifiers:
|
||||
- project
|
||||
- namespace
|
||||
- user
|
||||
additional_properties:
|
||||
label:
|
||||
description: The type of the widget. Can be `accessibility`, `code_quality`, etc.
|
||||
product_group: code_review
|
||||
milestone: '17.2'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/158454
|
||||
distributions:
|
||||
- ce
|
||||
- ee
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
description: Tracks pageviews for the admin application settings general page
|
||||
internal_events: true
|
||||
action: view_admin_application_settings_general_pageload
|
||||
identifiers:
|
||||
- user
|
||||
product_group: personal_productivity
|
||||
milestone: '17.2'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/158118
|
||||
distributions:
|
||||
- ce
|
||||
- ee
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
name: oauth2_device_grant_flow
|
||||
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/332682
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/155622
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/468479
|
||||
milestone: '17.1'
|
||||
group: group::authentication
|
||||
type: beta
|
||||
default_enabled: false
|
||||
|
|
@ -102,7 +102,7 @@ Doorkeeper.configure do
|
|||
# "password" => Resource Owner Password Credentials Grant Flow
|
||||
# "client_credentials" => Client Credentials Grant Flow
|
||||
#
|
||||
grant_flows %w[authorization_code password client_credentials]
|
||||
grant_flows %w[authorization_code password client_credentials device_code]
|
||||
|
||||
# Under some circumstances you might want to have applications auto-approved,
|
||||
# so that the user skips the authorization step.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
Doorkeeper::DeviceAuthorizationGrant.configure do
|
||||
# For future configuration
|
||||
# Minimum device code polling interval expected from the client, expressed in seconds.
|
||||
# device_code_polling_interval 5
|
||||
|
||||
# Device code expiration time, in seconds.
|
||||
# device_code_expires_in 300
|
||||
|
||||
# Customizable reference to the DeviceGrant model.
|
||||
# device_grant_class 'Doorkeeper::DeviceAuthorizationGrant::DeviceGrant'
|
||||
|
||||
# Reference to a model (or class) for user code generation.
|
||||
#
|
||||
# It must implement a `.generate` method, which can be invoked without
|
||||
# arguments, to obtain a String user code value.
|
||||
#
|
||||
# user_code_generator 'Doorkeeper::DeviceAuthorizationGrant::OAuth::Helpers::UserCode'
|
||||
|
||||
# A Proc returning the end-user verification URI on the authorization server.
|
||||
# verification_uri ->(host_name) do
|
||||
# "#{host_name}/oauth/device"
|
||||
# end
|
||||
|
||||
# A Proc returning the verification URI that includes the "user_code"
|
||||
# (or other information with the same function as the "user_code"), which is
|
||||
# designed for non-textual transmission. This is optional, so the Proc can
|
||||
# also return `nil`.
|
||||
#
|
||||
# verification_uri_complete ->(verification_uri, host_name, device_grant) do
|
||||
# "#{verification_uri}?user_code=#{CGI.escape(device_grant.user_code)}"
|
||||
# end
|
||||
end
|
||||
|
|
@ -7,12 +7,13 @@ status: active
|
|||
milestone: "15.2"
|
||||
introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/91831"
|
||||
time_frame: 28d
|
||||
data_source: redis_hll
|
||||
data_category: optional
|
||||
instrumentation_class: RedisHLLMetric
|
||||
options:
|
||||
events:
|
||||
- i_code_review_merge_request_widget_test_summary_full_report_clicked
|
||||
data_source: internal_events
|
||||
events:
|
||||
- name: click_full_report_on_merge_request_widget
|
||||
unique: user.id
|
||||
filter:
|
||||
label: test_summary
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
|
|
|
|||
|
|
@ -7,12 +7,13 @@ status: active
|
|||
milestone: "15.3"
|
||||
introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93232"
|
||||
time_frame: 28d
|
||||
data_source: redis_hll
|
||||
data_category: optional
|
||||
instrumentation_class: RedisHLLMetric
|
||||
options:
|
||||
events:
|
||||
- i_code_review_merge_request_widget_accessibility_full_report_clicked
|
||||
data_source: internal_events
|
||||
events:
|
||||
- name: click_full_report_on_merge_request_widget
|
||||
unique: user.id
|
||||
filter:
|
||||
label: accessibility
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
|
|
|
|||
|
|
@ -7,12 +7,13 @@ status: active
|
|||
milestone: "15.3"
|
||||
introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93333"
|
||||
time_frame: 28d
|
||||
data_source: redis_hll
|
||||
data_category: optional
|
||||
instrumentation_class: RedisHLLMetric
|
||||
options:
|
||||
events:
|
||||
- i_code_review_merge_request_widget_code_quality_full_report_clicked
|
||||
data_source: internal_events
|
||||
events:
|
||||
- name: click_full_report_on_merge_request_widget
|
||||
unique: user.id
|
||||
filter:
|
||||
label: code_quality
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
|
|
|
|||
|
|
@ -7,12 +7,13 @@ status: active
|
|||
milestone: "15.3"
|
||||
introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93340"
|
||||
time_frame: 28d
|
||||
data_source: redis_hll
|
||||
data_category: optional
|
||||
instrumentation_class: RedisHLLMetric
|
||||
options:
|
||||
events:
|
||||
- i_code_review_merge_request_widget_terraform_full_report_clicked
|
||||
data_source: internal_events
|
||||
events:
|
||||
- name: click_full_report_on_merge_request_widget
|
||||
unique: user.id
|
||||
filter:
|
||||
label: terraform
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
|
|
|
|||
|
|
@ -9,12 +9,13 @@ milestone_removed: '17.1'
|
|||
milestone: "15.4"
|
||||
introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/96538"
|
||||
time_frame: 28d
|
||||
data_source: redis_hll
|
||||
data_category: optional
|
||||
instrumentation_class: RedisHLLMetric
|
||||
options:
|
||||
events:
|
||||
- i_code_review_merge_request_widget_license_compliance_full_report_clicked
|
||||
data_source: internal_events
|
||||
events:
|
||||
- name: click_full_report_on_merge_request_widget
|
||||
unique: user.id
|
||||
filter:
|
||||
label: license_compliance
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
|
|
|
|||
|
|
@ -7,13 +7,14 @@ status: active
|
|||
milestone: "15.7"
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/104578
|
||||
time_frame: 28d
|
||||
data_source: redis_hll
|
||||
data_category: optional
|
||||
instrumentation_class: RedisHLLMetric
|
||||
performance_indicator_type: []
|
||||
options:
|
||||
events:
|
||||
- i_code_review_merge_request_widget_security_reports_full_report_clicked
|
||||
data_source: internal_events
|
||||
events:
|
||||
- name: click_full_report_on_merge_request_widget
|
||||
unique: user.id
|
||||
filter:
|
||||
label: security_reports
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
key_path: redis_hll_counters.count_distinct_user_id_from_view_admin_application_settings_general_pageload_monthly
|
||||
description: Monthly count of unique users who visisted the admin application settings general page
|
||||
product_group: personal_productivity
|
||||
performance_indicator_type: []
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: '17.2'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/158118
|
||||
time_frame: 28d
|
||||
data_source: internal_events
|
||||
data_category: optional
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
events:
|
||||
- name: view_admin_application_settings_general_pageload
|
||||
unique: user.id
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
key_path: counts.count_total_view_admin_application_settings_general_pageload_monthly
|
||||
description: Monthly count of total users who visisted the admin application settings general page
|
||||
product_group: personal_productivity
|
||||
performance_indicator_type: []
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: '17.2'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/158118
|
||||
time_frame: 28d
|
||||
data_source: internal_events
|
||||
data_category: optional
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
events:
|
||||
- name: view_admin_application_settings_general_pageload
|
||||
|
|
@ -7,12 +7,13 @@ status: active
|
|||
milestone: "15.2"
|
||||
introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/91831"
|
||||
time_frame: 7d
|
||||
data_source: redis_hll
|
||||
data_category: optional
|
||||
instrumentation_class: RedisHLLMetric
|
||||
options:
|
||||
events:
|
||||
- i_code_review_merge_request_widget_test_summary_full_report_clicked
|
||||
data_source: internal_events
|
||||
events:
|
||||
- name: click_full_report_on_merge_request_widget
|
||||
unique: user.id
|
||||
filter:
|
||||
label: test_summary
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
|
|
|
|||
|
|
@ -7,12 +7,13 @@ status: active
|
|||
milestone: "15.3"
|
||||
introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93232"
|
||||
time_frame: 7d
|
||||
data_source: redis_hll
|
||||
data_category: optional
|
||||
instrumentation_class: RedisHLLMetric
|
||||
options:
|
||||
events:
|
||||
- i_code_review_merge_request_widget_accessibility_full_report_clicked
|
||||
data_source: internal_events
|
||||
events:
|
||||
- name: click_full_report_on_merge_request_widget
|
||||
unique: user.id
|
||||
filter:
|
||||
label: accessibility
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
|
|
|
|||
|
|
@ -7,12 +7,13 @@ status: active
|
|||
milestone: "15.3"
|
||||
introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93333"
|
||||
time_frame: 7d
|
||||
data_source: redis_hll
|
||||
data_category: optional
|
||||
instrumentation_class: RedisHLLMetric
|
||||
options:
|
||||
events:
|
||||
- i_code_review_merge_request_widget_code_quality_full_report_clicked
|
||||
data_source: internal_events
|
||||
events:
|
||||
- name: click_full_report_on_merge_request_widget
|
||||
unique: user.id
|
||||
filter:
|
||||
label: code_quality
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
|
|
|
|||
|
|
@ -7,12 +7,13 @@ status: active
|
|||
milestone: "15.3"
|
||||
introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93340"
|
||||
time_frame: 7d
|
||||
data_source: redis_hll
|
||||
data_category: optional
|
||||
instrumentation_class: RedisHLLMetric
|
||||
options:
|
||||
events:
|
||||
- i_code_review_merge_request_widget_terraform_full_report_clicked
|
||||
data_source: internal_events
|
||||
events:
|
||||
- name: click_full_report_on_merge_request_widget
|
||||
unique: user.id
|
||||
filter:
|
||||
label: terraform
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
|
|
|
|||
|
|
@ -9,12 +9,13 @@ milestone_removed: '17.1'
|
|||
milestone: "15.4"
|
||||
introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/96538"
|
||||
time_frame: 7d
|
||||
data_source: redis_hll
|
||||
data_category: optional
|
||||
instrumentation_class: RedisHLLMetric
|
||||
options:
|
||||
events:
|
||||
- i_code_review_merge_request_widget_license_compliance_full_report_clicked
|
||||
data_source: internal_events
|
||||
events:
|
||||
- name: click_full_report_on_merge_request_widget
|
||||
unique: user.id
|
||||
filter:
|
||||
label: license_compliance
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
|
|
|
|||
|
|
@ -7,13 +7,14 @@ status: active
|
|||
milestone: "15.7"
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/104578
|
||||
time_frame: 7d
|
||||
data_source: redis_hll
|
||||
data_category: optional
|
||||
instrumentation_class: RedisHLLMetric
|
||||
performance_indicator_type: []
|
||||
options:
|
||||
events:
|
||||
- i_code_review_merge_request_widget_security_reports_full_report_clicked
|
||||
data_source: internal_events
|
||||
events:
|
||||
- name: click_full_report_on_merge_request_widget
|
||||
unique: user.id
|
||||
filter:
|
||||
label: security_reports
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
key_path: redis_hll_counters.count_distinct_user_id_from_view_admin_application_settings_general_pageload_weekly
|
||||
description: Weekly count of unique users who visisted the admin application settings general page
|
||||
product_group: personal_productivity
|
||||
performance_indicator_type: []
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: '17.2'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/158118
|
||||
time_frame: 7d
|
||||
data_source: internal_events
|
||||
data_category: optional
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
events:
|
||||
- name: view_admin_application_settings_general_pageload
|
||||
unique: user.id
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
key_path: counts.count_total_view_admin_application_settings_general_pageload_weekly
|
||||
description: Weekly count of total users who visisted the admin application settings general page
|
||||
product_group: personal_productivity
|
||||
performance_indicator_type: []
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: '17.2'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/158118
|
||||
time_frame: 7d
|
||||
data_source: internal_events
|
||||
data_category: optional
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
events:
|
||||
- name: view_admin_application_settings_general_pageload
|
||||
|
|
@ -7,12 +7,12 @@ status: active
|
|||
milestone: "15.2"
|
||||
introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/91831"
|
||||
time_frame: all
|
||||
data_source: redis
|
||||
data_source: internal_events
|
||||
data_category: optional
|
||||
instrumentation_class: MergeRequestWidgetExtensionMetric
|
||||
options:
|
||||
event: full_report_clicked
|
||||
widget: test_summary
|
||||
events:
|
||||
- name: click_full_report_on_merge_request_widget
|
||||
filter:
|
||||
label: test_summary
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@ status: active
|
|||
milestone: "15.3"
|
||||
introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93232"
|
||||
time_frame: all
|
||||
data_source: redis
|
||||
data_source: internal_events
|
||||
data_category: optional
|
||||
instrumentation_class: MergeRequestWidgetExtensionMetric
|
||||
options:
|
||||
event: full_report_clicked
|
||||
widget: accessibility
|
||||
events:
|
||||
- name: click_full_report_on_merge_request_widget
|
||||
filter:
|
||||
label: accessibility
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@ status: active
|
|||
milestone: "15.3"
|
||||
introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93333"
|
||||
time_frame: all
|
||||
data_source: redis
|
||||
data_source: internal_events
|
||||
data_category: optional
|
||||
instrumentation_class: MergeRequestWidgetExtensionMetric
|
||||
options:
|
||||
event: full_report_clicked
|
||||
widget: code_quality
|
||||
events:
|
||||
- name: click_full_report_on_merge_request_widget
|
||||
filter:
|
||||
label: code_quality
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@ status: active
|
|||
milestone: "15.3"
|
||||
introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93340"
|
||||
time_frame: all
|
||||
data_source: redis
|
||||
data_source: internal_events
|
||||
data_category: optional
|
||||
instrumentation_class: MergeRequestWidgetExtensionMetric
|
||||
options:
|
||||
event: full_report_clicked
|
||||
widget: terraform
|
||||
events:
|
||||
- name: click_full_report_on_merge_request_widget
|
||||
filter:
|
||||
label: terraform
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
|
|
|
|||
|
|
@ -9,12 +9,12 @@ milestone_removed: '17.1'
|
|||
milestone: "15.4"
|
||||
introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/96538"
|
||||
time_frame: all
|
||||
data_source: redis
|
||||
data_source: internal_events
|
||||
data_category: optional
|
||||
instrumentation_class: MergeRequestWidgetExtensionMetric
|
||||
options:
|
||||
event: full_report_clicked
|
||||
widget: license_compliance
|
||||
events:
|
||||
- name: click_full_report_on_merge_request_widget
|
||||
filter:
|
||||
label: license_compliance
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
|
|
|
|||
|
|
@ -7,13 +7,13 @@ status: active
|
|||
milestone: "15.7"
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/104578
|
||||
time_frame: all
|
||||
data_source: redis
|
||||
data_source: internal_events
|
||||
data_category: optional
|
||||
instrumentation_class: MergeRequestWidgetExtensionMetric
|
||||
performance_indicator_type: []
|
||||
options:
|
||||
event: full_report_clicked
|
||||
widget: security_reports
|
||||
events:
|
||||
- name: click_full_report_on_merge_request_widget
|
||||
filter:
|
||||
label: security_reports
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
|
|
|
|||
|
|
@ -38,6 +38,11 @@ InitializerConnections.raise_if_new_database_connection do
|
|||
controllers discovery: 'jwks'
|
||||
end
|
||||
|
||||
use_doorkeeper_device_authorization_grant do
|
||||
controller device_authorizations: 'oauth/device_authorizations',
|
||||
device_codes: 'oauth/device_codes'
|
||||
end
|
||||
|
||||
# Add OPTIONS method for CORS preflight requests
|
||||
match '/oauth/userinfo' => 'doorkeeper/openid_connect/userinfo#show', via: :options
|
||||
match '/oauth/discovery/keys' => 'jwks#keys', via: :options
|
||||
|
|
@ -286,6 +291,7 @@ InitializerConnections.raise_if_new_database_connection do
|
|||
draw :api
|
||||
draw :activity_pub
|
||||
draw :customers_dot
|
||||
draw :device_auth
|
||||
draw :sidekiq
|
||||
draw :help
|
||||
draw :google_api
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
namespace :oauth do
|
||||
resource :device, only: [] do
|
||||
post :confirm, to: 'device_authorizations#confirm'
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
---
|
||||
table_name: oauth_device_grants
|
||||
classes:
|
||||
- Doorkeeper::DeviceAuthorizationGrant::DeviceGrant
|
||||
feature_categories:
|
||||
- system_access
|
||||
description: Used for OAuth device grant flow
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/155494
|
||||
milestone: '17.2'
|
||||
gitlab_schema: gitlab_main
|
||||
sharding_key_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/463785
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CreateDoorkeeperDeviceGrants < Gitlab::Database::Migration[2.2]
|
||||
disable_ddl_transaction!
|
||||
milestone '17.2'
|
||||
|
||||
def up
|
||||
create_table :oauth_device_grants do |t| # rubocop:disable Migration/EnsureFactoryForTable -- No factory needed
|
||||
t.bigint :resource_owner_id, null: true
|
||||
t.bigint :application_id, null: false
|
||||
t.datetime_with_timezone :created_at, null: false
|
||||
t.datetime_with_timezone :last_polling_at, null: true
|
||||
t.integer :expires_in, null: false
|
||||
t.text :scopes, null: false, default: '', limit: 255
|
||||
t.text :device_code, null: false, limit: 255
|
||||
t.text :user_code, null: true, limit: 255
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
drop_table :oauth_device_grants
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddIndexUserCodeToOAuthDeviceGrants < Gitlab::Database::Migration[2.2]
|
||||
disable_ddl_transaction!
|
||||
|
||||
milestone '17.2'
|
||||
|
||||
INDEX_NAME = 'index_oauth_device_grants_on_user_code'
|
||||
|
||||
def up
|
||||
add_concurrent_index :oauth_device_grants, :user_code, unique: true
|
||||
end
|
||||
|
||||
def down
|
||||
remove_concurrent_index_by_name :oauth_device_grants, :user_code
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddIndexDeviceCodeToOAuthDeviceGrants < Gitlab::Database::Migration[2.2]
|
||||
disable_ddl_transaction!
|
||||
|
||||
milestone '17.2'
|
||||
|
||||
INDEX_NAME = 'index_oauth_device_grants_on_device_code'
|
||||
|
||||
def up
|
||||
add_concurrent_index :oauth_device_grants, :device_code, unique: true, name: INDEX_NAME
|
||||
end
|
||||
|
||||
def down
|
||||
remove_concurrent_index_by_name :oauth_device_grants, INDEX_NAME
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddIndexApplicationIdToOAuthDeviceGrants < Gitlab::Database::Migration[2.2]
|
||||
disable_ddl_transaction!
|
||||
|
||||
milestone '17.2'
|
||||
|
||||
INDEX_NAME = 'index_oauth_device_grants_on_application_id'
|
||||
|
||||
def up
|
||||
add_concurrent_index :oauth_device_grants, :application_id, name: INDEX_NAME
|
||||
end
|
||||
|
||||
def down
|
||||
remove_concurrent_index_by_name :oauth_device_grants, INDEX_NAME
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddForeignKeyForApplicationIdOnOAuthDeviceGrants < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.2'
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
add_concurrent_foreign_key(
|
||||
:oauth_device_grants,
|
||||
:oauth_applications,
|
||||
column: :application_id,
|
||||
on_delete: :cascade
|
||||
)
|
||||
end
|
||||
|
||||
def down
|
||||
with_lock_retries do
|
||||
remove_foreign_key :oauth_device_grants, column: :application_id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class DropIndexVulnerabilityOccurrencesForIssueLinksMigration < Gitlab::Database::Migration[2.2]
|
||||
TABLE_NAME = :vulnerability_occurrences
|
||||
INDEX = 'index_vulnerability_occurrences_for_issue_links_migration'
|
||||
|
||||
milestone '17.2'
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
remove_concurrent_index_by_name TABLE_NAME, name: INDEX
|
||||
end
|
||||
|
||||
def down
|
||||
add_concurrent_index(
|
||||
TABLE_NAME,
|
||||
"project_id, report_type, encode(project_fingerprint, 'hex'::text)",
|
||||
name: INDEX
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
d5b3e558e4caaf6fe978bca81b45ae724e240d515fbb8139f8badad0af26fce8
|
||||
|
|
@ -0,0 +1 @@
|
|||
3a34d455d4b6b53c41be04329df35a7526916e89ba208a0780ff711b64880d58
|
||||
|
|
@ -0,0 +1 @@
|
|||
c36004d9071d231fdbca714907ee74a84bf3f498a271c30e85e6c0fd0b41083b
|
||||
|
|
@ -0,0 +1 @@
|
|||
d3dcd5649dd44865c01e7b566c92ba4fe9d9e8aa1def35558513524834321e81
|
||||
|
|
@ -0,0 +1 @@
|
|||
515a7c196321bf241ebedc8eb355f34df3a68f9e9bdf82095a166847c4eec3be
|
||||
|
|
@ -0,0 +1 @@
|
|||
df28091c9283eb5aa0e0ce4a6a34095a7bd033f87ca00a2267b0925b0a084b42
|
||||
|
|
@ -13616,6 +13616,30 @@ CREATE SEQUENCE oauth_applications_id_seq
|
|||
|
||||
ALTER SEQUENCE oauth_applications_id_seq OWNED BY oauth_applications.id;
|
||||
|
||||
CREATE TABLE oauth_device_grants (
|
||||
id bigint NOT NULL,
|
||||
resource_owner_id bigint,
|
||||
application_id bigint NOT NULL,
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
last_polling_at timestamp with time zone,
|
||||
expires_in integer NOT NULL,
|
||||
scopes text DEFAULT ''::text NOT NULL,
|
||||
device_code text NOT NULL,
|
||||
user_code text,
|
||||
CONSTRAINT check_a6271f2c07 CHECK ((char_length(device_code) <= 255)),
|
||||
CONSTRAINT check_b0459113c7 CHECK ((char_length(scopes) <= 255)),
|
||||
CONSTRAINT check_b36370c6df CHECK ((char_length(user_code) <= 255))
|
||||
);
|
||||
|
||||
CREATE SEQUENCE oauth_device_grants_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
ALTER SEQUENCE oauth_device_grants_id_seq OWNED BY oauth_device_grants.id;
|
||||
|
||||
CREATE TABLE oauth_openid_requests (
|
||||
id integer NOT NULL,
|
||||
access_grant_id integer NOT NULL,
|
||||
|
|
@ -21086,6 +21110,8 @@ ALTER TABLE ONLY oauth_access_tokens ALTER COLUMN id SET DEFAULT nextval('oauth_
|
|||
|
||||
ALTER TABLE ONLY oauth_applications ALTER COLUMN id SET DEFAULT nextval('oauth_applications_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY oauth_device_grants ALTER COLUMN id SET DEFAULT nextval('oauth_device_grants_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY oauth_openid_requests ALTER COLUMN id SET DEFAULT nextval('oauth_openid_requests_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY onboarding_progresses ALTER COLUMN id SET DEFAULT nextval('onboarding_progresses_id_seq'::regclass);
|
||||
|
|
@ -23436,6 +23462,9 @@ ALTER TABLE ONLY oauth_access_tokens
|
|||
ALTER TABLE ONLY oauth_applications
|
||||
ADD CONSTRAINT oauth_applications_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY oauth_device_grants
|
||||
ADD CONSTRAINT oauth_device_grants_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY oauth_openid_requests
|
||||
ADD CONSTRAINT oauth_openid_requests_pkey PRIMARY KEY (id);
|
||||
|
||||
|
|
@ -28118,6 +28147,12 @@ CREATE INDEX index_oauth_applications_on_owner_id_and_owner_type ON oauth_applic
|
|||
|
||||
CREATE UNIQUE INDEX index_oauth_applications_on_uid ON oauth_applications USING btree (uid);
|
||||
|
||||
CREATE INDEX index_oauth_device_grants_on_application_id ON oauth_device_grants USING btree (application_id);
|
||||
|
||||
CREATE UNIQUE INDEX index_oauth_device_grants_on_device_code ON oauth_device_grants USING btree (device_code);
|
||||
|
||||
CREATE UNIQUE INDEX index_oauth_device_grants_on_user_code ON oauth_device_grants USING btree (user_code);
|
||||
|
||||
CREATE INDEX index_oauth_openid_requests_on_access_grant_id ON oauth_openid_requests USING btree (access_grant_id);
|
||||
|
||||
CREATE UNIQUE INDEX index_on_deploy_keys_id_and_type_and_public ON keys USING btree (id, type) WHERE (public = true);
|
||||
|
|
@ -29592,8 +29627,6 @@ CREATE INDEX index_vulnerability_occurrence_pipelines_on_pipeline_id ON vulnerab
|
|||
|
||||
CREATE INDEX index_vulnerability_occurrences_deduplication ON vulnerability_occurrences USING btree (project_id, report_type, project_fingerprint);
|
||||
|
||||
CREATE INDEX index_vulnerability_occurrences_for_issue_links_migration ON vulnerability_occurrences USING btree (project_id, report_type, encode(project_fingerprint, 'hex'::text));
|
||||
|
||||
CREATE INDEX index_vulnerability_occurrences_for_override_uuids_logic ON vulnerability_occurrences USING btree (project_id, report_type, location_fingerprint);
|
||||
|
||||
CREATE INDEX index_vulnerability_occurrences_on_initial_pipeline_id ON vulnerability_occurrences USING btree (initial_pipeline_id);
|
||||
|
|
@ -32098,6 +32131,9 @@ ALTER TABLE ONLY zoekt_replicas
|
|||
ALTER TABLE ONLY analytics_cycle_analytics_group_stages
|
||||
ADD CONSTRAINT fk_3078345d6d FOREIGN KEY (stage_event_hash_id) REFERENCES analytics_cycle_analytics_stage_event_hashes(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY oauth_device_grants
|
||||
ADD CONSTRAINT fk_308d5b76fe FOREIGN KEY (application_id) REFERENCES oauth_applications(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY lists
|
||||
ADD CONSTRAINT fk_30f2a831f4 FOREIGN KEY (iteration_id) REFERENCES sprints(id) ON DELETE CASCADE;
|
||||
|
||||
|
|
|
|||
|
|
@ -159,6 +159,28 @@ This field returns a [connection](#connections). It accepts the
|
|||
four standard [pagination arguments](#pagination-arguments):
|
||||
`before: String`, `after: String`, `first: Int`, and `last: Int`.
|
||||
|
||||
### `Query.blobSearch`
|
||||
|
||||
Find code visible to the current user.
|
||||
|
||||
DETAILS:
|
||||
**Introduced** in GitLab 17.2.
|
||||
**Status**: Experiment.
|
||||
|
||||
Returns [`BlobSearch`](#blobsearch).
|
||||
|
||||
#### Arguments
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="queryblobsearchchunkcount"></a>`chunkCount` **{warning-solid}** | [`Int`](#int) | **Introduced** in GitLab 17.2. **Status**: Experiment. Maximum chunks per file. |
|
||||
| <a id="queryblobsearchgroupid"></a>`groupId` **{warning-solid}** | [`GroupID`](#groupid) | **Introduced** in GitLab 17.2. **Status**: Experiment. Group to search in. |
|
||||
| <a id="queryblobsearchpage"></a>`page` **{warning-solid}** | [`Int`](#int) | **Introduced** in GitLab 17.2. **Status**: Experiment. Page number to fetch the results. |
|
||||
| <a id="queryblobsearchperpage"></a>`perPage` **{warning-solid}** | [`Int`](#int) | **Introduced** in GitLab 17.2. **Status**: Experiment. Number of results per page. |
|
||||
| <a id="queryblobsearchprojectid"></a>`projectId` **{warning-solid}** | [`ProjectID`](#projectid) | **Introduced** in GitLab 17.2. **Status**: Experiment. Project to search in. |
|
||||
| <a id="queryblobsearchrepositoryref"></a>`repositoryRef` **{warning-solid}** | [`String`](#string) | **Introduced** in GitLab 17.2. **Status**: Experiment. Repository reference to search in. |
|
||||
| <a id="queryblobsearchsearch"></a>`search` | [`String!`](#string) | Searched term. |
|
||||
|
||||
### `Query.boardList`
|
||||
|
||||
Find an issue board list.
|
||||
|
|
@ -17429,6 +17451,21 @@ An emoji awarded by a user.
|
|||
| <a id="blobwebpath"></a>`webPath` | [`String`](#string) | Web path of the blob. |
|
||||
| <a id="blobweburl"></a>`webUrl` | [`String`](#string) | Web URL of the blob. |
|
||||
|
||||
### `BlobSearch`
|
||||
|
||||
Full JSON structure of multi-match results in a single file.
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="blobsearchfilecount"></a>`fileCount` **{warning-solid}** | [`Int`](#int) | **Introduced** in GitLab 17.2. **Status**: Experiment. Total number of files with matches. |
|
||||
| <a id="blobsearchfiles"></a>`files` **{warning-solid}** | [`[SearchBlobFileType!]`](#searchblobfiletype) | **Introduced** in GitLab 17.2. **Status**: Experiment. List of files with matches. |
|
||||
| <a id="blobsearchmatchcount"></a>`matchCount` **{warning-solid}** | [`Int`](#int) | **Introduced** in GitLab 17.2. **Status**: Experiment. Total number of matches. |
|
||||
| <a id="blobsearchperpage"></a>`perPage` **{warning-solid}** | [`Int`](#int) | **Introduced** in GitLab 17.2. **Status**: Experiment. Total number of files per page. |
|
||||
| <a id="blobsearchsearchlevel"></a>`searchLevel` **{warning-solid}** | [`SearchLevel`](#searchlevel) | **Introduced** in GitLab 17.2. **Status**: Experiment. Level of search performed. |
|
||||
| <a id="blobsearchsearchtype"></a>`searchType` **{warning-solid}** | [`SearchType`](#searchtype) | **Introduced** in GitLab 17.2. **Status**: Experiment. Type of search performed. |
|
||||
|
||||
### `BlobViewer`
|
||||
|
||||
Represents how the blob content should be displayed.
|
||||
|
|
@ -30718,6 +30755,45 @@ Represents a resource scanned by a security scan.
|
|||
| <a id="scannedresourcerequestmethod"></a>`requestMethod` | [`String`](#string) | HTTP request method used to access the URL. |
|
||||
| <a id="scannedresourceurl"></a>`url` | [`String`](#string) | URL scanned by the scanner. |
|
||||
|
||||
### `SearchBlobChunk`
|
||||
|
||||
JSON structure of a matched chunk.
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="searchblobchunklines"></a>`lines` **{warning-solid}** | [`[SearchBlobLine!]`](#searchblobline) | **Introduced** in GitLab 17.2. **Status**: Experiment. Path of the file. |
|
||||
| <a id="searchblobchunkmatchcountinchunk"></a>`matchCountInChunk` **{warning-solid}** | [`Int`](#int) | **Introduced** in GitLab 17.2. **Status**: Experiment. Number of matches in the chunk. |
|
||||
|
||||
### `SearchBlobFileType`
|
||||
|
||||
JSON structure of a file with matches.
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="searchblobfiletypeblameurl"></a>`blameUrl` **{warning-solid}** | [`String`](#string) | **Introduced** in GitLab 17.2. **Status**: Experiment. Blame URL of the file. |
|
||||
| <a id="searchblobfiletypechunks"></a>`chunks` **{warning-solid}** | [`[SearchBlobChunk!]`](#searchblobchunk) | **Introduced** in GitLab 17.2. **Status**: Experiment. Maximum matches per file. |
|
||||
| <a id="searchblobfiletypefileurl"></a>`fileUrl` **{warning-solid}** | [`String`](#string) | **Introduced** in GitLab 17.2. **Status**: Experiment. URL of the file. |
|
||||
| <a id="searchblobfiletypematchcount"></a>`matchCount` **{warning-solid}** | [`Int`](#int) | **Introduced** in GitLab 17.2. **Status**: Experiment. Matches per file in maximum 50 chunks. |
|
||||
| <a id="searchblobfiletypematchcounttotal"></a>`matchCountTotal` **{warning-solid}** | [`Int`](#int) | **Introduced** in GitLab 17.2. **Status**: Experiment. Total number of matches per file. |
|
||||
| <a id="searchblobfiletypepath"></a>`path` **{warning-solid}** | [`String`](#string) | **Introduced** in GitLab 17.2. **Status**: Experiment. Path of the file. |
|
||||
| <a id="searchblobfiletypeprojectpath"></a>`projectPath` **{warning-solid}** | [`String`](#string) | **Introduced** in GitLab 17.2. **Status**: Experiment. Full path of the project. |
|
||||
|
||||
### `SearchBlobLine`
|
||||
|
||||
JSON structure of each line in a matched chunk.
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="searchbloblinelinenumber"></a>`lineNumber` **{warning-solid}** | [`Int`](#int) | **Introduced** in GitLab 17.2. **Status**: Experiment. Line number of the blob. |
|
||||
| <a id="searchbloblinerichtext"></a>`richText` **{warning-solid}** | [`String`](#string) | **Introduced** in GitLab 17.2. **Status**: Experiment. Rich text of the blob. |
|
||||
| <a id="searchbloblinetext"></a>`text` **{warning-solid}** | [`String`](#string) | **Introduced** in GitLab 17.2. **Status**: Experiment. Text content of the blob. |
|
||||
|
||||
### `SecurityPolicyValidationError`
|
||||
|
||||
Security policy validation error.
|
||||
|
|
@ -35838,6 +35914,26 @@ The status of the security scan.
|
|||
| <a id="scanstatusreport_error"></a>`REPORT_ERROR` | The report artifact provided by the CI build couldn't be parsed. |
|
||||
| <a id="scanstatussucceeded"></a>`SUCCEEDED` | The report has been successfully prepared. |
|
||||
|
||||
### `SearchLevel`
|
||||
|
||||
Level of search.
|
||||
|
||||
| Value | Description |
|
||||
| ----- | ----------- |
|
||||
| <a id="searchlevelglobal"></a>`GLOBAL` | Global search including all groups and projects. |
|
||||
| <a id="searchlevelgroup"></a>`GROUP` | Group search. |
|
||||
| <a id="searchlevelproject"></a>`PROJECT` | Project search. |
|
||||
|
||||
### `SearchType`
|
||||
|
||||
Type of search.
|
||||
|
||||
| Value | Description |
|
||||
| ----- | ----------- |
|
||||
| <a id="searchtypeadvanced"></a>`ADVANCED` | Advanced search. |
|
||||
| <a id="searchtypebasic"></a>`BASIC` | Basic search. |
|
||||
| <a id="searchtypezoekt"></a>`ZOEKT` | Exact code search. |
|
||||
|
||||
### `SecurityPolicyRelationType`
|
||||
|
||||
| Value | Description |
|
||||
|
|
|
|||
|
|
@ -48,8 +48,7 @@ GitLab supports the following authorization flows:
|
|||
server-side apps.
|
||||
- **Resource owner password credentials:** To be used **only** for securely
|
||||
hosted, first-party services. GitLab recommends against use of this flow.
|
||||
|
||||
Device Authorization Grant is not supported. [Issue 332682](https://gitlab.com/gitlab-org/gitlab/-/issues/332682) proposes to add support.
|
||||
- **Device Authorization Grant** (GitLab 17.1 and later) Secure flow oriented toward devices without browser access. Requires a secondary device to complete the authorization flow.
|
||||
|
||||
The draft specification for [OAuth 2.1](https://oauth.net/2.1/) specifically omits both the
|
||||
Implicit grant and Resource Owner Password Credentials flows.
|
||||
|
|
@ -106,7 +105,7 @@ Before starting the flow, generate the `STATE`, the `CODE_VERIFIER` and the `COD
|
|||
- The `CODE_VERIFIER` is a random string, between 43 and 128 characters in length,
|
||||
which use the characters `A-Z`, `a-z`, `0-9`, `-`, `.`, `_`, and `~`.
|
||||
- The `CODE_CHALLENGE` is an URL-safe base64-encoded string of the SHA256 hash of the
|
||||
`CODE_VERIFIER`.
|
||||
`CODE_VERIFIER`:
|
||||
- The SHA256 hash must be in binary format before encoding.
|
||||
- In Ruby, you can set that up with `Base64.urlsafe_encode64(Digest::SHA256.digest(CODE_VERIFIER), padding: false)`.
|
||||
- For reference, a `CODE_VERIFIER` string of `ks02i3jdikdo2k0dkfodf3m39rjfjsdk0wk349rj3jrhf` when hashed
|
||||
|
|
@ -260,6 +259,129 @@ authorization request.
|
|||
|
||||
You can now make requests to the API with the access token returned.
|
||||
|
||||
### Device authorization grant flow
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/332682) in GitLab 17.2 [with a flag](../administration/feature_flags.md) named `oauth2_device_grant_flow`. Disabled by default.
|
||||
|
||||
FLAG:
|
||||
The availability of this feature is controlled by a feature flag.
|
||||
For more information, see the history.
|
||||
|
||||
NOTE:
|
||||
Check the [RFC spec](https://datatracker.ietf.org/doc/html/rfc8628#section-3.1) for a detailed
|
||||
description of the device authorization grant flow, from device authorization request to token response from the browser login.
|
||||
|
||||
The device authorization grant flow makes it possible to securely authenticate your GitLab identity from input constrained devices where browser interactions are not an option.
|
||||
|
||||
This makes the device authorization grant flow ideal for users attempting to use GitLab services from headless servers or other devices with no, or limited, UI.
|
||||
|
||||
1. To request device authorization, a request is sent from the input-limited
|
||||
device client to `https://gitlab.example.com/oauth/authorize_device`. For example:
|
||||
|
||||
```ruby
|
||||
parameters = 'client_id=UID&scope=read'
|
||||
RestClient.post 'https://gitlab.example.com/oauth/authorize_device', parameters
|
||||
```
|
||||
|
||||
After a successful request, a response containing a `verification_uri` is returned to the user. For example:
|
||||
|
||||
```json
|
||||
{
|
||||
"device_code": "GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS",
|
||||
"user_code": "0A44L90H",
|
||||
"verification_uri": "https://gitlab.example.com/oauth/device",
|
||||
"verification_uri_complete": "https://gitlab.example.com/oauth/device?user_code=0A44L90H",
|
||||
"expires_in": 300,
|
||||
"interval": 5
|
||||
}
|
||||
```
|
||||
|
||||
1. The device client displays the `user_code` and `verification_uri` from the response to the
|
||||
requesting user. That user then, on a secondary device with browser access:
|
||||
1. Goes to the provided URI.
|
||||
1. Enters the user code.
|
||||
1. Completes an authentication as prompted.
|
||||
|
||||
1. Immediately after displaying the `verification_uri` and `user_code`, the device client
|
||||
begins polling the token endpoint with the associated `device_code` returned in the initial response:
|
||||
|
||||
```ruby
|
||||
parameters = 'grant_type=urn:ietf:params:oauth:grant-type:device_code
|
||||
&device_code=GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS
|
||||
&client_id=1406020730'
|
||||
RestClient.post 'https://gitlab.example.com/oauth/token', parameters
|
||||
```
|
||||
|
||||
1. The device client receives a response from the token endpoint. If the authorization was successful,
|
||||
a success response is returned, otherwise, an error response is returned.
|
||||
Potential error responses are categorized by either of the following:
|
||||
- Those defined by the OAuth Authorization Framework access token error responses.
|
||||
- Those specific to the device authorization grant flow described here.
|
||||
Those error responses specific to the device flow are described in the following content.
|
||||
For more information on each potential response, see the relevant [RFC spec for device authorization grant](https://datatracker.ietf.org/doc/html/rfc8628#section-3.5) and the
|
||||
[RFC spec for authorization tokens](https://datatracker.ietf.org/doc/html/rfc6749#section-5.2).
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "authorization_pending",
|
||||
"error_description": "..."
|
||||
}
|
||||
```
|
||||
|
||||
On receipt of this response, the device client continues polling.
|
||||
|
||||
If the polling interval is too short, a slow down error response is returned. For example:
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "slow_down",
|
||||
"error_description": "..."
|
||||
}
|
||||
```
|
||||
|
||||
On receipt of this response, the device client reduces its polling rate and continues polling at the new rate.
|
||||
|
||||
If the device code expires before authentication is complete, an expired token error
|
||||
response is returned. For example:
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "expired_token",
|
||||
"error_description": "..."
|
||||
}
|
||||
```
|
||||
|
||||
At that point, the device-client should stop and initiate a new device authorization request.
|
||||
|
||||
If the authorization request was denied, an access denied error response is returned. For example:
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "access_denied",
|
||||
"error_description": "..."
|
||||
}
|
||||
```
|
||||
|
||||
The authentication request has been rejected. The user should verify their credentials or contact their system administrator
|
||||
|
||||
1. After the user successfully authenticates, a success response is returned:
|
||||
|
||||
```json
|
||||
{
|
||||
"access_token": "TOKEN",
|
||||
"token_type": "Bearer",
|
||||
"expires_in": 7200,
|
||||
"scope": "read",
|
||||
"created_at": 1593096829
|
||||
}
|
||||
```
|
||||
|
||||
At this point, the device authentication flow is complete. The returned `access_token` can be provided to GitLab to authenticate the user identity when accessing GitLab resources, such as when cloning over HTTPS or accessing the API.
|
||||
|
||||
A sample application that implements the client side device flow can be found at: <https://gitlab.com/johnwparent/git-auth-over-https>.
|
||||
|
||||
### Resource owner password credentials flow
|
||||
|
||||
NOTE:
|
||||
|
|
@ -364,10 +486,10 @@ curl --header "Authorization: Bearer OAUTH-TOKEN" "https://gitlab.example.com/ap
|
|||
|
||||
A token with [scope](../integration/oauth_provider.md#view-all-authorized-applications)
|
||||
`read_repository` or `write_repository` can access Git over HTTPS. Use the token as the password.
|
||||
The username should be your GitLab username or `oauth2`:
|
||||
The username must be `oauth2`. The username must not be your username:
|
||||
|
||||
```plaintext
|
||||
https://<your_gitlab_username>:<your_access_token>@gitlab.example.com/project_path/project_name.git
|
||||
https://oauth2:<your_access_token>@gitlab.example.com/project_path/project_name.git
|
||||
```
|
||||
|
||||
Alternatively, you can use a [Git credential helper](../user/profile/account/two_factor_authentication.md#oauth-credential-helpers)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
owning-stage: "~devops::secure"
|
||||
description: 'EPSS Support ADR 002: Use a new bucket for EPSS data'
|
||||
---
|
||||
|
||||
# EPSS Support ADR 002: Use a new bucket for EPSS data
|
||||
|
||||
## Context
|
||||
|
||||
PMDB exports data to GCP buckets. The data is later pulled by GitLab instances. Advisory data and license data are stored in different buckets. This is sensible, because advisory and license data are not directly related, and rather provide additional information about packages. Data is updated based on deltas—changes from the previous state of the data. Only those changes are saved with each addition to the database.
|
||||
|
||||
EPSS data is directly associated with advisories, so it feels natural to add it to the existing advisories bucket. However, the current advisories bucket is structured based on `purl_type`. Adding an `epss` data type would couple `epss` with `purl_type` which is a faulty pairing. Due to the tight coupling between `purl_type` and the existing advisories bucket, it would be difficult and convoluted to add `epss` to it.
|
||||
|
||||
Following [extensive discussions on the EPSS epic](https://gitlab.com/groups/gitlab-org/-/epics/11544#note_1952695268) and [discussion](https://gitlab.com/gitlab-org/gitlab/-/issues/468131#note_1961344123) during the refinement of PMDB issues, it was initially decided to use the existing bucket as this feels most intuitive and at the time felt a healthier approach. [Further discussion](https://gitlab.com/gitlab-org/gitlab/-/issues/467672#note_1980715240) during the refinement of the GitLab backend effort led to the decision to use a new bucket, due to the complexity of the coupling of `purl_type` and other, unrelated areas in the monolith. Adding `epss` to `purl_type` would impact other components and we want to avoid having to work around that. We may want to later simplify these areas and reconsider the bucket structure at a later stage.
|
||||
|
||||
## Decision
|
||||
|
||||
Export EPSS data to a new bucket, rather than exporting it into the existing PMDB advisories bucket.
|
||||
|
||||
## Consequences
|
||||
|
||||
The implementation is simpler than adding a directory to the existing advisories bucket, but may feel less intuitive.
|
||||
This change require the relevant Terraform changes regarding the provisioning of a new bucket.
|
||||
This should also be addressed in the exporter and the GitLab `package_metadata` sync configuration.
|
||||
|
||||
## Alternatives
|
||||
|
||||
The other option is to add EPSS data to the advisories bucket, since they are directly related. This was the [initial decision](https://gitlab.com/gitlab-org/gitlab/-/issues/468131#note_1980366323). This would allow us to utilize existing mechanisms and keep related data close. However, EPSS data doesn't fit into the current structure of the advisories bucket. An ideal solution would reconstruct the buckets in a manner more fitting for this approach, but this would be a big effort and is not critical enough.
|
||||
|
|
@ -169,11 +169,11 @@ Following the discussions in the [EPSS epic](https://gitlab.com/groups/gitlab-or
|
|||
1. PMDB database is extended with a new table to store EPSS scores.
|
||||
1. PMDB infrastructure runs the feeder daily in order to pull and process EPSS data.
|
||||
1. The advisory-processor receives the EPSS data and stores them to the PMDB DB.
|
||||
1. PMDB exports EPSS data to existing PMDB advisories bucket.
|
||||
- Create a new directory in the existing bucket to store EPSS data.
|
||||
1. PMDB exports EPSS data to a new PMDB EPSS bucket.
|
||||
- Create a new bucket to store EPSS data.
|
||||
- Delete former EPSS data once new data is uploaded, as the old data is no longer needed.
|
||||
- Truncate EPSS scores to two digits after the dot.
|
||||
1. GitLab instances pull data from the PMDB bucket.
|
||||
1. GitLab instances pull data from the PMDB EPSS bucket.
|
||||
- Create a new table in rails DB to store EPSS data.
|
||||
1. GitLab instances expose EPSS data through GraphQL API and present data in vulnerability report and details pages.
|
||||
|
||||
|
|
@ -201,6 +201,10 @@ compared with the pros and cons of alternatives.
|
|||
|
||||
## Design and implementation details
|
||||
|
||||
### Decisions
|
||||
|
||||
- [002: Use a new bucket for EPSS data](decisions/002_use_new_bucket.md)
|
||||
|
||||
### Important notes
|
||||
|
||||
- All EPSS scores get updated on a daily basis. This is pivotal to this feature's design.
|
||||
|
|
@ -211,7 +215,7 @@ compared with the pros and cons of alternatives.
|
|||
|
||||
- Create a new EPSS table in [PMDB](https://gitlab.com/gitlab-org/security-products/license-db) with an advisory identifier and the EPSS score. This includes changing the [schema](https://gitlab.com/gitlab-org/security-products/license-db/schema) and any necessary migrations.
|
||||
- Ingest EPSS data into new PMDB table. We want to keep the EPSS data structure as close as possible to the origin so all of the data may be available to the exporter, and the exporter may choose how to process it. Therefore we will save scores and percentiles with their complete values.
|
||||
- Export EPSS scores in separate directory in the advisories bucket.
|
||||
- Export EPSS scores in separate bucket.
|
||||
- Delete the previous day's export as it is no longer needed after the new one is added.
|
||||
- Add new pubsub topics to deployment to be used by PMDB components, using existing terraform modules.
|
||||
|
||||
|
|
|
|||
|
|
@ -300,16 +300,26 @@ While troubleshooting CI/CD job token authentication issues, be aware that:
|
|||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/389060) in GitLab 17.2. [with a flag](../../administration/feature_flags.md) named `allow_push_repository_for_job_token`. Disabled by default.
|
||||
|
||||
FLAG:
|
||||
The availability of this feature is controlled by a feature flag.
|
||||
For more information, see the history.
|
||||
This feature is available for testing, but not ready for production use.
|
||||
|
||||
WARNING:
|
||||
Pushing via job token is still in development and is not yet optimized for performance.
|
||||
If you enable this feature for testing, you must thoroughly test and implement validation measures
|
||||
to prevent infinite loops of "push" pipelines triggering more pipelines.
|
||||
|
||||
By default, pushing to a project repository by authenticating with a job token is disabled.
|
||||
To enable this ability, you can:
|
||||
By default, the ability to Git push to a project repository by authenticating with a job token is disabled.
|
||||
|
||||
- Feature flag named `allow_push_repository_for_job_token` should be enabled.
|
||||
- Enable the [`pushRepositoryForJobTokenAllowed`](../../api/graphql/reference/index.md#mutationprojectcicdsettingsupdate) GraphQL endpoint.
|
||||
- Enable the [`ci_push_repository_for_job_token_allowed`](../../api/projects.md#edit-project) REST API endpoint.
|
||||
To grant permission to job tokens generated in your project to push to the project's repository:
|
||||
|
||||
You are only permitted to push to the repository of the project where the job is running.
|
||||
1. On the left sidebar, select **Search or go to** and find your project.
|
||||
1. Select **Settings > CI/CD**.
|
||||
1. Expand **Token Access**.
|
||||
1. In the **Permissions** section, select **Allow Git push requests to the repository**.
|
||||
|
||||
The job token has the same access permissions as the user that started the job.
|
||||
|
||||
You can also control this setting with the [`ci_push_repository_for_job_token_allowed`](../../api/projects.md#edit-project)
|
||||
parameter in the `projects` REST API endpoint.
|
||||
|
|
|
|||
|
|
@ -61,12 +61,11 @@ To view the vulnerability report:
|
|||
1. On the left sidebar, select **Search or go to** and find your project or group.
|
||||
1. Select **Secure > Vulnerability report**.
|
||||
|
||||
## Vulnerability Report filters
|
||||
## Filtering vulnerabilities
|
||||
|
||||
You can filter the Vulnerability Report to narrow focus on only vulnerabilities matching specific
|
||||
criteria.
|
||||
You can filter vulnerabilities in the vulnerability report to more efficiently triage them.
|
||||
|
||||
The filters available at all levels are:
|
||||
You can filter by:
|
||||
|
||||
<!-- vale gitlab.SubstitutionWarning = NO -->
|
||||
|
||||
|
|
@ -75,30 +74,10 @@ The filters available at all levels are:
|
|||
- **Severity**: Critical, high, medium, low, info, unknown.
|
||||
- **Tool**: For more details, see [Tool filter](#tool-filter).
|
||||
- **Activity**: For more details, see [Activity filter](#activity-filter).
|
||||
|
||||
The [project filter](#project-filter) is available only at the group level.
|
||||
- **Project**: Filter vulnerabilities in specific projects (available only for groups).
|
||||
|
||||
<!-- vale gitlab.SubstitutionWarning = YES -->
|
||||
|
||||
### Filter the list of vulnerabilities
|
||||
|
||||
> - Refactored user interface for GitLab.com and GitLab Dedicated [introduced](https://gitlab.com/groups/gitlab-org/-/epics/13339) in GitLab 17.0.
|
||||
|
||||
Filter the vulnerability report to focus on a subset of vulnerabilities.
|
||||
|
||||
To filter the list of vulnerabilities:
|
||||
|
||||
1. On the left sidebar, select **Search or go to** and find your project.
|
||||
1. Select **Secure > Vulnerability report**.
|
||||
1. Optional. To remove the default filters, select **Clear** (**{clear}**).
|
||||
1. Above the list of vulnerabilities, select the filter bar.
|
||||
1. In the dropdown list that appears, select an attribute you want to filter by, then select the
|
||||
values from the dropdown list.
|
||||
1. Select outside the filter field. The vulnerability severity totals and list of matching
|
||||
vulnerabilities are updated.
|
||||
1. To filter by multiple attributes, repeat the three previous steps. Multiple attributes are joined
|
||||
by a logical AND.
|
||||
|
||||
### Tool filter
|
||||
|
||||
> - Project-level tool filter [introduced](https://gitlab.com/groups/gitlab-org/-/epics/11237) in GitLab 16.6.
|
||||
|
|
@ -164,6 +143,25 @@ Selection behavior when using the activity filter:
|
|||
- **Has a solution**: Vulnerabilities with an available solution.
|
||||
- **Does not have a solution**: Vulnerabilities without an available solution.
|
||||
|
||||
### Filter vulnerabilities
|
||||
|
||||
> - Refactored user interface for GitLab.com and GitLab Dedicated [introduced](https://gitlab.com/groups/gitlab-org/-/epics/13339) in GitLab 17.0.
|
||||
|
||||
Filter vulnerabilities on the vulnerability report to more efficiently triage them.
|
||||
|
||||
To filter the list of vulnerabilities:
|
||||
|
||||
1. On the left sidebar, select **Search or go to** and find your project.
|
||||
1. Select **Secure > Vulnerability report**.
|
||||
1. Optional. To remove the default filters, select **Clear** (**{clear}**).
|
||||
1. Above the list of vulnerabilities, select the filter bar.
|
||||
1. In the dropdown list that appears, select an attribute you want to filter by, then select the
|
||||
values from the dropdown list.
|
||||
1. Select outside the filter field. The vulnerability severity totals and list of matching
|
||||
vulnerabilities are updated.
|
||||
1. To filter by multiple attributes, repeat the three previous steps. Multiple attributes are joined
|
||||
by a logical AND.
|
||||
|
||||
## Grouping vulnerabilities
|
||||
|
||||
> - Project-level grouping of vulnerabilities[introduced](https://gitlab.com/groups/gitlab-org/-/epics/10164) in GitLab 16.4 [with a flag](../../../administration/feature_flags.md) named `vulnerability_report_grouping`. Disabled by default.
|
||||
|
|
|
|||
|
|
@ -145,6 +145,8 @@ To view the last time a token was used:
|
|||
> - Personal access tokens no longer being able to access container or package registries [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/387721) in GitLab 16.0.
|
||||
> - `k8s_proxy` [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/422408) in GitLab 16.4 [with a flag](../../administration/feature_flags.md) named `k8s_proxy_pat`. Enabled by default.
|
||||
> - Feature flag `k8s_proxy_pat` [removed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/131518) in GitLab 16.5.
|
||||
> - `read_service_ping` [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/42692#note_1222832412) in GitLab 17.1.
|
||||
> - `manage_runner` [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/460721) in GitLab 17.1.
|
||||
|
||||
A personal access token can perform actions based on the assigned scopes.
|
||||
|
||||
|
|
@ -160,10 +162,10 @@ A personal access token can perform actions based on the assigned scopes.
|
|||
| `sudo` | Grants permission to perform API actions as any user in the system, when authenticated as an administrator. |
|
||||
| `admin_mode` | Grants permission to perform API actions as an administrator, when Admin Mode is enabled. ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/107875) in GitLab 15.8.) |
|
||||
| `create_runner` | Grants permission to create runners. |
|
||||
| `manage_runner` | Grants permission to manage runners. |
|
||||
| `manage_runner` | Grants permission to manage runners. |
|
||||
| `ai_features` | Grants permission to perform API actions for GitLab Duo. This scope is designed to work with the GitLab Duo Plugin for JetBrains. For all other extensions, see scope requirements. |
|
||||
| `k8s_proxy` | Grants permission to perform Kubernetes API calls using the agent for Kubernetes. |
|
||||
| `read_service_ping`| Grant access to download Service Ping payload through the API when authenticated as an admin use. ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/107875) in GitLab 16.8. |
|
||||
| `read_service_ping`| Grant access to download Service Ping payload through the API when authenticated as an admin use. |
|
||||
|
||||
WARNING:
|
||||
If you enabled [external authorization](../../administration/settings/external_authorization.md), personal access tokens cannot access container or package registries. If you use personal access tokens to access these registries, this measure breaks this use of these tokens. Disable external authorization to use personal access tokens with container or package registries.
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ module Gitlab
|
|||
store.subscribe ::Users::RecordLastActivityWorker,
|
||||
to: ::Users::ActivityEvent,
|
||||
if: ->(event) do
|
||||
actor = ::Namespace.actor_from_id(event.data[:namespace_id])
|
||||
actor = ::Group.actor_from_id(event.data[:namespace_id])
|
||||
Feature.enabled?(:track_member_activity, actor)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5,6 +5,22 @@
|
|||
#
|
||||
# It is not safe to regenerate it using the same script
|
||||
---
|
||||
'click_full_report_on_merge_request_widget-filter:[label:accessibility]-user': i_code_review_merge_request_widget_accessibility_full_report_clicked
|
||||
'click_full_report_on_merge_request_widget-filter:[label:code_quality]-user': i_code_review_merge_request_widget_code_quality_full_report_clicked
|
||||
'click_full_report_on_merge_request_widget-filter:[label:license_compliance]-user': i_code_review_merge_request_widget_license_compliance_full_report_clicked
|
||||
'click_full_report_on_merge_request_widget-filter:[label:metrics]-user': i_code_review_merge_request_widget_metrics_full_report_clicked
|
||||
'click_full_report_on_merge_request_widget-filter:[label:security_reports]-user': i_code_review_merge_request_widget_security_reports_full_report_clicked
|
||||
'click_full_report_on_merge_request_widget-filter:[label:status_checks]-user': i_code_review_merge_request_widget_status_checks_full_report_clicked
|
||||
'click_full_report_on_merge_request_widget-filter:[label:terraform]-user': i_code_review_merge_request_widget_terraform_full_report_clicked
|
||||
'click_full_report_on_merge_request_widget-filter:[label:test_summary]-user': i_code_review_merge_request_widget_test_summary_full_report_clicked
|
||||
'view_merge_request_widget-filter:[label:accessibility]-user': i_code_review_merge_request_widget_accessibility_view
|
||||
'view_merge_request_widget-filter:[label:code_quality]-user': i_code_review_merge_request_widget_code_quality_view
|
||||
'view_merge_request_widget-filter:[label:license_compliance]-user': i_code_review_merge_request_widget_license_compliance_view
|
||||
'view_merge_request_widget-filter:[label:metrics]-user': i_code_review_merge_request_widget_metrics_view
|
||||
'view_merge_request_widget-filter:[label:security_reports]-user': i_code_review_merge_request_widget_security_reports_view
|
||||
'view_merge_request_widget-filter:[label:status_checks]-user': i_code_review_merge_request_widget_status_checks_view
|
||||
'view_merge_request_widget-filter:[label:terraform]-user': i_code_review_merge_request_widget_terraform_view
|
||||
'view_merge_request_widget-filter:[label:test_summary]-user': i_code_review_merge_request_widget_test_summary_view
|
||||
agent_users_using_ci_tunnel-user: agent_users_using_ci_tunnel
|
||||
ci_template_included-project: ci_template_included
|
||||
create_ci_build-user: create_ci_build
|
||||
|
|
@ -129,11 +145,3 @@ value_streams_dashboard_metric_link_clicked-user: value_streams_dashboard_metric
|
|||
value_streams_dashboard_time_to_restore_service_link_clicked-user: value_streams_dashboard_time_to_restore_service_link_clicked
|
||||
value_streams_dashboard_vulnerability_critical_link_clicked-user: value_streams_dashboard_vulnerability_critical_link_clicked
|
||||
value_streams_dashboard_vulnerability_high_link_clicked-user: value_streams_dashboard_vulnerability_high_link_clicked
|
||||
'view_merge_request_widget-filter:[label:accessibility]-user': i_code_review_merge_request_widget_accessibility_view
|
||||
'view_merge_request_widget-filter:[label:code_quality]-user': i_code_review_merge_request_widget_code_quality_view
|
||||
'view_merge_request_widget-filter:[label:license_compliance]-user': i_code_review_merge_request_widget_license_compliance_view
|
||||
'view_merge_request_widget-filter:[label:status_checks]-user': i_code_review_merge_request_widget_status_checks_view
|
||||
'view_merge_request_widget-filter:[label:terraform]-user': i_code_review_merge_request_widget_terraform_view
|
||||
'view_merge_request_widget-filter:[label:test_summary]-user': i_code_review_merge_request_widget_test_summary_view
|
||||
'view_merge_request_widget-filter:[label:metrics]-user': i_code_review_merge_request_widget_metrics_view
|
||||
'view_merge_request_widget-filter:[label:security_reports]-user': i_code_review_merge_request_widget_security_reports_view
|
||||
|
|
|
|||
|
|
@ -10,6 +10,14 @@
|
|||
'{event_counters}_analytics_dashboard_viewed': USAGE_PRODUCT_ANALYTICS_VIEW_DASHBOARD
|
||||
'{event_counters}_click_external_link_license_compliance': USAGE_USERS_CLICKING_LICENSE_TESTING_VISITING_EXTERNAL_WEBSITE
|
||||
'{event_counters}_click_full_report_license_compliance': USAGE_USERS_VISITING_TESTING_LICENSE_COMPLIANCE_FULL_REPORT
|
||||
'{event_counters}_click_full_report_on_merge_request_widget-filter:[label:accessibility]': USAGE_I_CODE_REVIEW_MERGE_REQUEST_WIDGET_ACCESSIBILITY_COUNT_FULL_REPORT_CLICKED
|
||||
'{event_counters}_click_full_report_on_merge_request_widget-filter:[label:code_quality]': USAGE_I_CODE_REVIEW_MERGE_REQUEST_WIDGET_CODE_QUALITY_COUNT_FULL_REPORT_CLICKED
|
||||
'{event_counters}_click_full_report_on_merge_request_widget-filter:[label:license_compliance]': USAGE_I_CODE_REVIEW_MERGE_REQUEST_WIDGET_LICENSE_COMPLIANCE_COUNT_FULL_REPORT_CLICKED
|
||||
'{event_counters}_click_full_report_on_merge_request_widget-filter:[label:metrics]': USAGE_I_CODE_REVIEW_MERGE_REQUEST_WIDGET_METRICS_COUNT_FULL_REPORT_CLICKED
|
||||
'{event_counters}_click_full_report_on_merge_request_widget-filter:[label:security_reports]': USAGE_I_CODE_REVIEW_MERGE_REQUEST_WIDGET_SECURITY_REPORTS_COUNT_FULL_REPORT_CLICKED
|
||||
'{event_counters}_click_full_report_on_merge_request_widget-filter:[label:status_checks]': USAGE_I_CODE_REVIEW_MERGE_REQUEST_WIDGET_STATUS_CHECKS_COUNT_FULL_REPORT_CLICKED
|
||||
'{event_counters}_click_full_report_on_merge_request_widget-filter:[label:terraform]': USAGE_I_CODE_REVIEW_MERGE_REQUEST_WIDGET_TERRAFORM_COUNT_FULL_REPORT_CLICKED
|
||||
'{event_counters}_click_full_report_on_merge_request_widget-filter:[label:test_summary]': USAGE_I_CODE_REVIEW_MERGE_REQUEST_WIDGET_TEST_SUMMARY_COUNT_FULL_REPORT_CLICKED
|
||||
'{event_counters}_create_commit_from_web_ide': WEB_IDE_COMMITS_COUNT
|
||||
'{event_counters}_create_commit_note': USAGE_NOTE_CREATE_COMMIT
|
||||
'{event_counters}_create_design_management_design': USAGE_DESIGN_MANAGEMENT_DESIGNS_CREATE
|
||||
|
|
@ -54,14 +62,14 @@
|
|||
'{event_counters}_usage_data_download_payload_clicked': USAGE_SERVICE_USAGE_DATA_DOWNLOAD_PAYLOAD_CLICK
|
||||
'{event_counters}_value_streams_dashboard_viewed': USAGE_VALUE_STREAMS_DASHBOARD_VIEWS
|
||||
'{event_counters}_view_cycle_analytics': USAGE_CYCLE_ANALYTICS_VIEWS
|
||||
'{event_counters}_view_productivity_analytics': USAGE_PRODUCTIVITY_ANALYTICS_VIEWS
|
||||
'{event_counters}_view_wiki_page': USAGE_WIKI_PAGES_VIEW
|
||||
'{event_counters}_web_ide_viewed': WEB_IDE_VIEWS_COUNT
|
||||
'{event_counters}_view_merge_request_widget-filter:[label:accessibility]': USAGE_I_CODE_REVIEW_MERGE_REQUEST_WIDGET_ACCESSIBILITY_COUNT_VIEW
|
||||
'{event_counters}_view_merge_request_widget-filter:[label:code_quality]': USAGE_I_CODE_REVIEW_MERGE_REQUEST_WIDGET_CODE_QUALITY_COUNT_VIEW
|
||||
'{event_counters}_view_merge_request_widget-filter:[label:license_compliance]': USAGE_I_CODE_REVIEW_MERGE_REQUEST_WIDGET_LICENSE_COMPLIANCE_COUNT_VIEW
|
||||
'{event_counters}_view_merge_request_widget-filter:[label:metrics]': USAGE_I_CODE_REVIEW_MERGE_REQUEST_WIDGET_METRICS_COUNT_VIEW
|
||||
'{event_counters}_view_merge_request_widget-filter:[label:security_reports]': USAGE_I_CODE_REVIEW_MERGE_REQUEST_WIDGET_SECURITY_REPORTS_COUNT_VIEW
|
||||
'{event_counters}_view_merge_request_widget-filter:[label:status_checks]': USAGE_I_CODE_REVIEW_MERGE_REQUEST_WIDGET_STATUS_CHECKS_COUNT_VIEW
|
||||
'{event_counters}_view_merge_request_widget-filter:[label:terraform]': USAGE_I_CODE_REVIEW_MERGE_REQUEST_WIDGET_TERRAFORM_COUNT_VIEW
|
||||
'{event_counters}_view_merge_request_widget-filter:[label:test_summary]': USAGE_I_CODE_REVIEW_MERGE_REQUEST_WIDGET_TEST_SUMMARY_COUNT_VIEW
|
||||
'{event_counters}_view_merge_request_widget-filter:[label:metrics]': USAGE_I_CODE_REVIEW_MERGE_REQUEST_WIDGET_METRICS_COUNT_VIEW
|
||||
'{event_counters}_view_merge_request_widget-filter:[label:security_reports]': USAGE_I_CODE_REVIEW_MERGE_REQUEST_WIDGET_SECURITY_REPORTS_COUNT_VIEW
|
||||
'{event_counters}_view_productivity_analytics': USAGE_PRODUCTIVITY_ANALYTICS_VIEWS
|
||||
'{event_counters}_view_wiki_page': USAGE_WIKI_PAGES_VIEW
|
||||
'{event_counters}_web_ide_viewed': WEB_IDE_VIEWS_COUNT
|
||||
|
|
|
|||
|
|
@ -10019,6 +10019,9 @@ msgstr ""
|
|||
msgid "CI/CD configuration file"
|
||||
msgstr ""
|
||||
|
||||
msgid "CI/CD job token permissions for '%{projectName}' were successfully updated."
|
||||
msgstr ""
|
||||
|
||||
msgid "CI/CD limits"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -10078,6 +10081,12 @@ msgstr ""
|
|||
msgid "CICD|Add an existing project to the scope"
|
||||
msgstr ""
|
||||
|
||||
msgid "CICD|Additional permissions"
|
||||
msgstr ""
|
||||
|
||||
msgid "CICD|Allow Git push requests to the repository"
|
||||
msgstr ""
|
||||
|
||||
msgid "CICD|Authorized groups and projects"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -10087,6 +10096,9 @@ msgstr ""
|
|||
msgid "CICD|Automatic deployment to staging, manual deployment to production"
|
||||
msgstr ""
|
||||
|
||||
msgid "CICD|CI/CD job token can be used to authenticate a Git push to this repository, using the permissions of the user that started the job."
|
||||
msgstr ""
|
||||
|
||||
msgid "CICD|Continuous deployment to production"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -10114,6 +10126,9 @@ msgstr ""
|
|||
msgid "CICD|Ensure only groups and projects with members authorized to access sensitive project data are added to the allowlist."
|
||||
msgstr ""
|
||||
|
||||
msgid "CICD|Grant additional access permissions to this project's CI/CD job tokens."
|
||||
msgstr ""
|
||||
|
||||
msgid "CICD|Jobs"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -14974,10 +14989,10 @@ msgstr ""
|
|||
msgid "Contributor analytics"
|
||||
msgstr ""
|
||||
|
||||
msgid "Control whether CI/CD job tokens can be used to authenticate with this project."
|
||||
msgid "Control whether to display customer experience improvement content and third-party offers in GitLab."
|
||||
msgstr ""
|
||||
|
||||
msgid "Control whether to display customer experience improvement content and third-party offers in GitLab."
|
||||
msgid "Control which CI/CD job tokens can be used to authenticate with this project."
|
||||
msgstr ""
|
||||
|
||||
msgid "Converts item to %{type}. Widgets not supported in new type are removed."
|
||||
|
|
@ -18666,6 +18681,24 @@ msgstr ""
|
|||
msgid "Device name"
|
||||
msgstr ""
|
||||
|
||||
msgid "DeviceAuth|Authorize"
|
||||
msgstr ""
|
||||
|
||||
msgid "DeviceAuth|Authorize device to access to your GitLab account."
|
||||
msgstr ""
|
||||
|
||||
msgid "DeviceAuth|Confirm"
|
||||
msgstr ""
|
||||
|
||||
msgid "DeviceAuth|Please make sure that you intended to authorize this device."
|
||||
msgstr ""
|
||||
|
||||
msgid "DeviceAuth|Scopes associated with this request:"
|
||||
msgstr ""
|
||||
|
||||
msgid "DeviceAuth|You are an administrator, which means authorizing access will allow it to interact with GitLab as an administrator as well."
|
||||
msgstr ""
|
||||
|
||||
msgid "Devices (optional)"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -53709,6 +53742,9 @@ msgstr ""
|
|||
msgid "There was a problem fetching releases."
|
||||
msgstr ""
|
||||
|
||||
msgid "There was a problem fetching the CI/CD job token permissions."
|
||||
msgstr ""
|
||||
|
||||
msgid "There was a problem fetching the job token scope value"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -57667,9 +57703,15 @@ msgstr ""
|
|||
msgid "UserMapping|Select user"
|
||||
msgstr ""
|
||||
|
||||
msgid "UserMapping|There was a problem cancelling placeholder user reassignment."
|
||||
msgstr ""
|
||||
|
||||
msgid "UserMapping|There was a problem fetching placeholder users."
|
||||
msgstr ""
|
||||
|
||||
msgid "UserMapping|There was a problem reassigning placeholder user."
|
||||
msgstr ""
|
||||
|
||||
msgid "UserMapping|To learn more about reassignments, %{link_start}visit the docs%{link_end}. If you don't recognize this request, you can safely ignore this email or %{report_link_start}report abuse%{report_link_end}."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@
|
|||
"@mattiasbuelens/web-streams-adapter": "^0.1.0",
|
||||
"@rails/actioncable": "7.0.8-4",
|
||||
"@rails/ujs": "7.0.8-4",
|
||||
"@sentry/browser": "8.8.0",
|
||||
"@sentry/browser": "8.15.0",
|
||||
"@snowplow/browser-plugin-client-hints": "^3.9.0",
|
||||
"@snowplow/browser-plugin-form-tracking": "^3.9.0",
|
||||
"@snowplow/browser-plugin-ga-cookies": "^3.9.0",
|
||||
|
|
|
|||
|
|
@ -8,6 +8,6 @@ group :test do
|
|||
gem "climate_control", "~> 1.2"
|
||||
gem "gitlab-styles", "~> 11.0"
|
||||
gem "pry", "~> 0.14.2"
|
||||
gem "rspec", "~> 3.0"
|
||||
gem "rspec", "~> 3.13"
|
||||
gem "simplecov", "~> 0.22.0"
|
||||
end
|
||||
|
|
|
|||
|
|
@ -142,8 +142,8 @@ DEPENDENCIES
|
|||
gitlab-cng!
|
||||
gitlab-styles (~> 11.0)
|
||||
pry (~> 0.14.2)
|
||||
rspec (~> 3.0)
|
||||
rspec (~> 3.13)
|
||||
simplecov (~> 0.22.0)
|
||||
|
||||
BUNDLED WITH
|
||||
2.5.9
|
||||
2.5.11
|
||||
|
|
|
|||
|
|
@ -21,9 +21,8 @@ module RuboCop
|
|||
# These namespaces are considered acceptable.
|
||||
# Note: Nested namespace like Foo::Bar are also supported.
|
||||
PERMITTED_NAMESPACES = %w[
|
||||
Search EE::Search API::Search EE::API::Search API::Admin::Search RuboCop::Cop::Search
|
||||
API::Entities::Search::Zoekt
|
||||
API::Internal::Search::Zoekt
|
||||
Search EE::Search API::Search EE::API::Search API::Admin::Search RuboCop::Cop::Search Types Resolvers
|
||||
API::Entities::Search::Zoekt API::Internal::Search::Zoekt
|
||||
Keeps
|
||||
].map { |x| x.split('::') }.freeze
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,89 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Oauth::DeviceAuthorizationsController, feature_category: :system_access do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
describe "GET #index" do
|
||||
render_views
|
||||
|
||||
context "when requested with HTML format" do
|
||||
it "renders the 'doorkeeper/device_authorization_grant/index' template" do
|
||||
get :index, format: :html
|
||||
expect(response).to render_template("doorkeeper/device_authorization_grant/index")
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
|
||||
it "uses the 'minimal' layout" do
|
||||
get :index, format: :html
|
||||
expect(response).to render_template(layout: 'minimal')
|
||||
end
|
||||
end
|
||||
|
||||
context "when requested with JSON format" do
|
||||
it "returns a no content status" do
|
||||
get :index, format: :json
|
||||
expect(response).to have_gitlab_http_status(:no_content)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST #confirm' do
|
||||
let(:user_code) { 'valid_user_code' }
|
||||
let(:device_grant) { instance_double('Doorkeeper::DeviceAuthorizationGrant::DeviceGrant', scopes: 'read write') }
|
||||
let(:invalid_user_code) { 'invalid_user_code' }
|
||||
|
||||
before do
|
||||
allow(controller).to receive(:device_grant_model).and_return(Doorkeeper::DeviceAuthorizationGrant::DeviceGrant)
|
||||
end
|
||||
|
||||
context 'with valid user_code' do
|
||||
before do
|
||||
allow(Doorkeeper::DeviceAuthorizationGrant::DeviceGrant).to receive(:find_by)
|
||||
.with(user_code: user_code).and_return(device_grant)
|
||||
end
|
||||
|
||||
it 'assigns @scopes' do
|
||||
post :confirm, params: { user_code: user_code }, format: :html
|
||||
expect(assigns(:scopes)).to eq('read write')
|
||||
end
|
||||
|
||||
it 'renders the authorize template' do
|
||||
post :confirm, params: { user_code: user_code }, format: :html
|
||||
expect(response).to render_template('doorkeeper/device_authorization_grant/authorize')
|
||||
end
|
||||
|
||||
it 'responds with no content for JSON format' do
|
||||
post :confirm, params: { user_code: user_code }, format: :json
|
||||
expect(response).to have_gitlab_http_status(:no_content)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid user_code' do
|
||||
before do
|
||||
allow(Doorkeeper::DeviceAuthorizationGrant::DeviceGrant).to receive(:find_by)
|
||||
.with(user_code: invalid_user_code).and_return(nil)
|
||||
end
|
||||
|
||||
it 'assigns @scopes as an empty string' do
|
||||
post :confirm, params: { user_code: invalid_user_code }, format: :html
|
||||
expect(assigns(:scopes)).to eq('')
|
||||
end
|
||||
|
||||
it 'renders the authorize template' do
|
||||
post :confirm, params: { user_code: invalid_user_code }, format: :html
|
||||
expect(response).to render_template('doorkeeper/device_authorization_grant/authorize')
|
||||
end
|
||||
|
||||
it 'responds with no content for JSON format' do
|
||||
post :confirm, params: { user_code: invalid_user_code }, format: :json
|
||||
expect(response).to have_gitlab_http_status(:no_content)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Oauth::DeviceCodesController, feature_category: :system_access do
|
||||
describe 'POST #create' do
|
||||
context 'when the feature is enabled' do
|
||||
before do
|
||||
stub_feature_flags(oauth2_device_grant_flow: true)
|
||||
end
|
||||
|
||||
it 'calls the superclass create method' do
|
||||
expect_any_instance_of(Doorkeeper::DeviceAuthorizationGrant::DeviceCodesController) do |instance|
|
||||
expect(instance).to receive(:create)
|
||||
end
|
||||
post :create
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the feature is disabled' do
|
||||
before do
|
||||
stub_feature_flags(oauth2_device_grant_flow: false)
|
||||
end
|
||||
|
||||
it 'returns :not_found' do
|
||||
post :create
|
||||
expect(response).to have_gitlab_http_status(:no_content)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -128,6 +128,7 @@ RSpec.describe 'Database schema', feature_category: :database do
|
|||
oauth_access_grants: %w[resource_owner_id application_id],
|
||||
oauth_access_tokens: %w[resource_owner_id application_id],
|
||||
oauth_applications: %w[owner_id],
|
||||
oauth_device_grants: %w[resource_owner_id application_id],
|
||||
p_ci_builds: %w[erased_by_id trigger_request_id partition_id auto_canceled_by_partition_id execution_config_id],
|
||||
p_batched_git_ref_updates_deletions: %w[project_id partition_id],
|
||||
p_catalog_resource_sync_events: %w[catalog_resource_id project_id partition_id],
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ exports[`The DAG graph in the basic case renders the graph svg 1`] = `
|
|||
stroke-opacity="0.8"
|
||||
>
|
||||
<g
|
||||
class="dag-link gl-cursor-pointer gl-duration-slow gl-transition-stroke-opacity gl-transition-timing-function-ease"
|
||||
class="dag-link gl-cursor-pointer gl-duration-slow gl-ease-ease gl-transition-stroke-opacity"
|
||||
id="reference-0"
|
||||
>
|
||||
<lineargradient
|
||||
|
|
@ -52,7 +52,7 @@ exports[`The DAG graph in the basic case renders the graph svg 1`] = `
|
|||
/>
|
||||
</g>
|
||||
<g
|
||||
class="dag-link gl-cursor-pointer gl-duration-slow gl-transition-stroke-opacity gl-transition-timing-function-ease"
|
||||
class="dag-link gl-cursor-pointer gl-duration-slow gl-ease-ease gl-transition-stroke-opacity"
|
||||
id="reference-3"
|
||||
>
|
||||
<lineargradient
|
||||
|
|
@ -93,7 +93,7 @@ exports[`The DAG graph in the basic case renders the graph svg 1`] = `
|
|||
/>
|
||||
</g>
|
||||
<g
|
||||
class="dag-link gl-cursor-pointer gl-duration-slow gl-transition-stroke-opacity gl-transition-timing-function-ease"
|
||||
class="dag-link gl-cursor-pointer gl-duration-slow gl-ease-ease gl-transition-stroke-opacity"
|
||||
id="reference-6"
|
||||
>
|
||||
<lineargradient
|
||||
|
|
@ -134,7 +134,7 @@ exports[`The DAG graph in the basic case renders the graph svg 1`] = `
|
|||
/>
|
||||
</g>
|
||||
<g
|
||||
class="dag-link gl-cursor-pointer gl-duration-slow gl-transition-stroke-opacity gl-transition-timing-function-ease"
|
||||
class="dag-link gl-cursor-pointer gl-duration-slow gl-ease-ease gl-transition-stroke-opacity"
|
||||
id="reference-9"
|
||||
>
|
||||
<lineargradient
|
||||
|
|
@ -175,7 +175,7 @@ exports[`The DAG graph in the basic case renders the graph svg 1`] = `
|
|||
/>
|
||||
</g>
|
||||
<g
|
||||
class="dag-link gl-cursor-pointer gl-duration-slow gl-transition-stroke-opacity gl-transition-timing-function-ease"
|
||||
class="dag-link gl-cursor-pointer gl-duration-slow gl-ease-ease gl-transition-stroke-opacity"
|
||||
id="reference-12"
|
||||
>
|
||||
<lineargradient
|
||||
|
|
@ -216,7 +216,7 @@ exports[`The DAG graph in the basic case renders the graph svg 1`] = `
|
|||
/>
|
||||
</g>
|
||||
<g
|
||||
class="dag-link gl-cursor-pointer gl-duration-slow gl-transition-stroke-opacity gl-transition-timing-function-ease"
|
||||
class="dag-link gl-cursor-pointer gl-duration-slow gl-ease-ease gl-transition-stroke-opacity"
|
||||
id="reference-15"
|
||||
>
|
||||
<lineargradient
|
||||
|
|
@ -257,7 +257,7 @@ exports[`The DAG graph in the basic case renders the graph svg 1`] = `
|
|||
/>
|
||||
</g>
|
||||
<g
|
||||
class="dag-link gl-cursor-pointer gl-duration-slow gl-transition-stroke-opacity gl-transition-timing-function-ease"
|
||||
class="dag-link gl-cursor-pointer gl-duration-slow gl-ease-ease gl-transition-stroke-opacity"
|
||||
id="reference-18"
|
||||
>
|
||||
<lineargradient
|
||||
|
|
@ -298,7 +298,7 @@ exports[`The DAG graph in the basic case renders the graph svg 1`] = `
|
|||
/>
|
||||
</g>
|
||||
<g
|
||||
class="dag-link gl-cursor-pointer gl-duration-slow gl-transition-stroke-opacity gl-transition-timing-function-ease"
|
||||
class="dag-link gl-cursor-pointer gl-duration-slow gl-ease-ease gl-transition-stroke-opacity"
|
||||
id="reference-21"
|
||||
>
|
||||
<lineargradient
|
||||
|
|
@ -339,7 +339,7 @@ exports[`The DAG graph in the basic case renders the graph svg 1`] = `
|
|||
/>
|
||||
</g>
|
||||
<g
|
||||
class="dag-link gl-cursor-pointer gl-duration-slow gl-transition-stroke-opacity gl-transition-timing-function-ease"
|
||||
class="dag-link gl-cursor-pointer gl-duration-slow gl-ease-ease gl-transition-stroke-opacity"
|
||||
id="reference-24"
|
||||
>
|
||||
<lineargradient
|
||||
|
|
@ -380,7 +380,7 @@ exports[`The DAG graph in the basic case renders the graph svg 1`] = `
|
|||
/>
|
||||
</g>
|
||||
<g
|
||||
class="dag-link gl-cursor-pointer gl-duration-slow gl-transition-stroke-opacity gl-transition-timing-function-ease"
|
||||
class="dag-link gl-cursor-pointer gl-duration-slow gl-ease-ease gl-transition-stroke-opacity"
|
||||
id="reference-27"
|
||||
>
|
||||
<lineargradient
|
||||
|
|
@ -423,7 +423,7 @@ exports[`The DAG graph in the basic case renders the graph svg 1`] = `
|
|||
</g>
|
||||
<g>
|
||||
<line
|
||||
class="dag-node gl-cursor-pointer gl-duration-slow gl-transition-stroke gl-transition-timing-function-ease"
|
||||
class="dag-node gl-cursor-pointer gl-duration-slow gl-ease-ease gl-transition-stroke"
|
||||
id="reference-30"
|
||||
stroke="#e17223"
|
||||
stroke-linecap="round"
|
||||
|
|
@ -434,7 +434,7 @@ exports[`The DAG graph in the basic case renders the graph svg 1`] = `
|
|||
y2="154.00000000000003"
|
||||
/>
|
||||
<line
|
||||
class="dag-node gl-cursor-pointer gl-duration-slow gl-transition-stroke gl-transition-timing-function-ease"
|
||||
class="dag-node gl-cursor-pointer gl-duration-slow gl-ease-ease gl-transition-stroke"
|
||||
id="reference-31"
|
||||
stroke="#83ab4a"
|
||||
stroke-linecap="round"
|
||||
|
|
@ -445,7 +445,7 @@ exports[`The DAG graph in the basic case renders the graph svg 1`] = `
|
|||
y2="154"
|
||||
/>
|
||||
<line
|
||||
class="dag-node gl-cursor-pointer gl-duration-slow gl-transition-stroke gl-transition-timing-function-ease"
|
||||
class="dag-node gl-cursor-pointer gl-duration-slow gl-ease-ease gl-transition-stroke"
|
||||
id="reference-32"
|
||||
stroke="#5772ff"
|
||||
stroke-linecap="round"
|
||||
|
|
@ -456,7 +456,7 @@ exports[`The DAG graph in the basic case renders the graph svg 1`] = `
|
|||
y2="237.00000000000003"
|
||||
/>
|
||||
<line
|
||||
class="dag-node gl-cursor-pointer gl-duration-slow gl-transition-stroke gl-transition-timing-function-ease"
|
||||
class="dag-node gl-cursor-pointer gl-duration-slow gl-ease-ease gl-transition-stroke"
|
||||
id="reference-33"
|
||||
stroke="#b24800"
|
||||
stroke-linecap="round"
|
||||
|
|
@ -467,7 +467,7 @@ exports[`The DAG graph in the basic case renders the graph svg 1`] = `
|
|||
y2="320.00000000000006"
|
||||
/>
|
||||
<line
|
||||
class="dag-node gl-cursor-pointer gl-duration-slow gl-transition-stroke gl-transition-timing-function-ease"
|
||||
class="dag-node gl-cursor-pointer gl-duration-slow gl-ease-ease gl-transition-stroke"
|
||||
id="reference-34"
|
||||
stroke="#25d2d2"
|
||||
stroke-linecap="round"
|
||||
|
|
@ -478,7 +478,7 @@ exports[`The DAG graph in the basic case renders the graph svg 1`] = `
|
|||
y2="403.0000000000001"
|
||||
/>
|
||||
<line
|
||||
class="dag-node gl-cursor-pointer gl-duration-slow gl-transition-stroke gl-transition-timing-function-ease"
|
||||
class="dag-node gl-cursor-pointer gl-duration-slow gl-ease-ease gl-transition-stroke"
|
||||
id="reference-35"
|
||||
stroke="#6f3500"
|
||||
stroke-linecap="round"
|
||||
|
|
@ -489,7 +489,7 @@ exports[`The DAG graph in the basic case renders the graph svg 1`] = `
|
|||
y2="212.00000000000009"
|
||||
/>
|
||||
<line
|
||||
class="dag-node gl-cursor-pointer gl-duration-slow gl-transition-stroke gl-transition-timing-function-ease"
|
||||
class="dag-node gl-cursor-pointer gl-duration-slow gl-ease-ease gl-transition-stroke"
|
||||
id="reference-36"
|
||||
stroke="#006887"
|
||||
stroke-linecap="round"
|
||||
|
|
@ -500,7 +500,7 @@ exports[`The DAG graph in the basic case renders the graph svg 1`] = `
|
|||
y2="294.99999999999994"
|
||||
/>
|
||||
<line
|
||||
class="dag-node gl-cursor-pointer gl-duration-slow gl-transition-stroke gl-transition-timing-function-ease"
|
||||
class="dag-node gl-cursor-pointer gl-duration-slow gl-ease-ease gl-transition-stroke"
|
||||
id="reference-37"
|
||||
stroke="#487900"
|
||||
stroke-linecap="round"
|
||||
|
|
@ -511,7 +511,7 @@ exports[`The DAG graph in the basic case renders the graph svg 1`] = `
|
|||
y2="436"
|
||||
/>
|
||||
<line
|
||||
class="dag-node gl-cursor-pointer gl-duration-slow gl-transition-stroke gl-transition-timing-function-ease"
|
||||
class="dag-node gl-cursor-pointer gl-duration-slow gl-ease-ease gl-transition-stroke"
|
||||
id="reference-38"
|
||||
stroke="#d84280"
|
||||
stroke-linecap="round"
|
||||
|
|
@ -522,7 +522,7 @@ exports[`The DAG graph in the basic case renders the graph svg 1`] = `
|
|||
y2="353"
|
||||
/>
|
||||
<line
|
||||
class="dag-node gl-cursor-pointer gl-duration-slow gl-transition-stroke gl-transition-timing-function-ease"
|
||||
class="dag-node gl-cursor-pointer gl-duration-slow gl-ease-ease gl-transition-stroke"
|
||||
id="reference-39"
|
||||
stroke="#3547de"
|
||||
stroke-linecap="round"
|
||||
|
|
@ -533,7 +533,7 @@ exports[`The DAG graph in the basic case renders the graph svg 1`] = `
|
|||
y2="436"
|
||||
/>
|
||||
<line
|
||||
class="dag-node gl-cursor-pointer gl-duration-slow gl-transition-stroke gl-transition-timing-function-ease"
|
||||
class="dag-node gl-cursor-pointer gl-duration-slow gl-ease-ease gl-transition-stroke"
|
||||
id="reference-40"
|
||||
stroke="#006887"
|
||||
stroke-linecap="round"
|
||||
|
|
@ -544,7 +544,7 @@ exports[`The DAG graph in the basic case renders the graph svg 1`] = `
|
|||
y2="295.1890725105691"
|
||||
/>
|
||||
<line
|
||||
class="dag-node gl-cursor-pointer gl-duration-slow gl-transition-stroke gl-transition-timing-function-ease"
|
||||
class="dag-node gl-cursor-pointer gl-duration-slow gl-ease-ease gl-transition-stroke"
|
||||
id="reference-41"
|
||||
stroke="#275600"
|
||||
stroke-linecap="round"
|
||||
|
|
|
|||
|
|
@ -13,27 +13,27 @@ exports[`Links Inner component with a large number of needs matches snapshot and
|
|||
width="1019px"
|
||||
>
|
||||
<path
|
||||
class="gl-duration-slow gl-fill-transparent gl-stroke-gray-200 gl-transition-timing-function-ease"
|
||||
class="gl-duration-slow gl-ease-ease gl-fill-transparent gl-stroke-gray-200"
|
||||
d="M202,118C52,118,52,138,102,138"
|
||||
stroke-width="2"
|
||||
/>
|
||||
<path
|
||||
class="gl-duration-slow gl-fill-transparent gl-stroke-gray-200 gl-transition-timing-function-ease"
|
||||
class="gl-duration-slow gl-ease-ease gl-fill-transparent gl-stroke-gray-200"
|
||||
d="M202,118C62,118,62,148,112,148"
|
||||
stroke-width="2"
|
||||
/>
|
||||
<path
|
||||
class="gl-duration-slow gl-fill-transparent gl-stroke-gray-200 gl-transition-timing-function-ease"
|
||||
class="gl-duration-slow gl-ease-ease gl-fill-transparent gl-stroke-gray-200"
|
||||
d="M222,138C72,138,72,158,122,158"
|
||||
stroke-width="2"
|
||||
/>
|
||||
<path
|
||||
class="gl-duration-slow gl-fill-transparent gl-stroke-gray-200 gl-transition-timing-function-ease"
|
||||
class="gl-duration-slow gl-ease-ease gl-fill-transparent gl-stroke-gray-200"
|
||||
d="M212,128C82,128,82,168,132,168"
|
||||
stroke-width="2"
|
||||
/>
|
||||
<path
|
||||
class="gl-duration-slow gl-fill-transparent gl-stroke-gray-200 gl-transition-timing-function-ease"
|
||||
class="gl-duration-slow gl-ease-ease gl-fill-transparent gl-stroke-gray-200"
|
||||
d="M232,148C92,148,92,178,142,178"
|
||||
stroke-width="2"
|
||||
/>
|
||||
|
|
@ -54,7 +54,7 @@ exports[`Links Inner component with a parallel need matches snapshot and has exp
|
|||
width="1019px"
|
||||
>
|
||||
<path
|
||||
class="gl-duration-slow gl-fill-transparent gl-stroke-gray-200 gl-transition-timing-function-ease"
|
||||
class="gl-duration-slow gl-ease-ease gl-fill-transparent gl-stroke-gray-200"
|
||||
d="M192,108C32,108,32,118,82,118"
|
||||
stroke-width="2"
|
||||
/>
|
||||
|
|
@ -75,7 +75,7 @@ exports[`Links Inner component with one need matches snapshot and has expected p
|
|||
width="1019px"
|
||||
>
|
||||
<path
|
||||
class="gl-duration-slow gl-fill-transparent gl-stroke-gray-200 gl-transition-timing-function-ease"
|
||||
class="gl-duration-slow gl-ease-ease gl-fill-transparent gl-stroke-gray-200"
|
||||
d="M202,118C52,118,52,138,102,138"
|
||||
stroke-width="2"
|
||||
/>
|
||||
|
|
@ -96,12 +96,12 @@ exports[`Links Inner component with same stage needs matches snapshot and has ex
|
|||
width="1019px"
|
||||
>
|
||||
<path
|
||||
class="gl-duration-slow gl-fill-transparent gl-stroke-gray-200 gl-transition-timing-function-ease"
|
||||
class="gl-duration-slow gl-ease-ease gl-fill-transparent gl-stroke-gray-200"
|
||||
d="M192,108C32,108,32,118,82,118"
|
||||
stroke-width="2"
|
||||
/>
|
||||
<path
|
||||
class="gl-duration-slow gl-fill-transparent gl-stroke-gray-200 gl-transition-timing-function-ease"
|
||||
class="gl-duration-slow gl-ease-ease gl-fill-transparent gl-stroke-gray-200"
|
||||
d="M202,118C42,118,42,128,92,128"
|
||||
stroke-width="2"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -56,7 +56,6 @@ export const mockSourceUsers = [
|
|||
}),
|
||||
createMockSourceUser(6, {
|
||||
status: 'KEEP_AS_PLACEHOLDER',
|
||||
reassignToUser: true,
|
||||
}),
|
||||
createMockSourceUser(7, {
|
||||
status: 'COMPLETED',
|
||||
|
|
@ -85,6 +84,44 @@ export const mockSourceUsersQueryResponse = ({ pageInfo = {} } = {}) => ({
|
|||
},
|
||||
});
|
||||
|
||||
export const mockReassignMutationResponse = {
|
||||
data: {
|
||||
importSourceUserReassign: {
|
||||
errors: [],
|
||||
importSourceUser: {
|
||||
...mockSourceUsers[0],
|
||||
status: 'AWAITING_APPROVAL',
|
||||
reassignToUser: createMockReassignUser(1),
|
||||
},
|
||||
__typename: 'ImportSourceUserReassignPayload',
|
||||
},
|
||||
},
|
||||
};
|
||||
export const mockKeepAsPlaceholderMutationResponse = {
|
||||
data: {
|
||||
importSourceUserKeepAsPlaceholder: {
|
||||
errors: [],
|
||||
importSourceUser: {
|
||||
...mockSourceUsers[0],
|
||||
status: 'KEEP_AS_PLACEHOLDER',
|
||||
},
|
||||
__typename: 'ImportSourceUserKeepAsPlaceholderPayload',
|
||||
},
|
||||
},
|
||||
};
|
||||
export const mockCancelReassignmentMutationResponse = {
|
||||
data: {
|
||||
importSourceUserCancelReassignment: {
|
||||
errors: [],
|
||||
importSourceUser: {
|
||||
...mockSourceUsers[0],
|
||||
status: 'PENDING_ASSIGNMENT',
|
||||
},
|
||||
__typename: 'ImportSourceUserCancelReassignmentPayload',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const mockUser1 = {
|
||||
__typename: 'UserCore',
|
||||
id: 'gid://gitlab/User/1',
|
||||
|
|
|
|||
|
|
@ -8,8 +8,15 @@ import waitForPromises from 'helpers/wait_for_promises';
|
|||
|
||||
import PlaceholderActions from '~/members/components/placeholders/placeholder_actions.vue';
|
||||
import searchUsersQuery from '~/graphql_shared/queries/users_search_all_paginated.query.graphql';
|
||||
import importSourceUserReassignMutation from '~/members/placeholders/graphql/mutations/reassign.mutation.graphql';
|
||||
import importSourceUserKeepAsPlaceholderMutation from '~/members/placeholders/graphql/mutations/keep_as_placeholder.mutation.graphql';
|
||||
import importSourceUserCancelReassignmentMutation from '~/members/placeholders/graphql/mutations/cancel_reassignment.mutation.graphql';
|
||||
|
||||
import {
|
||||
mockSourceUsers,
|
||||
mockReassignMutationResponse,
|
||||
mockKeepAsPlaceholderMutationResponse,
|
||||
mockCancelReassignmentMutationResponse,
|
||||
mockUser1,
|
||||
mockUser2,
|
||||
mockUsersQueryResponse,
|
||||
|
|
@ -23,17 +30,33 @@ describe('PlaceholderActions', () => {
|
|||
let wrapper;
|
||||
let mockApollo;
|
||||
|
||||
const defaultProps = {
|
||||
sourceUser: mockSourceUsers[0],
|
||||
};
|
||||
const usersQueryHandler = jest.fn().mockResolvedValue(mockUsersQueryResponse);
|
||||
const reassignMutationHandler = jest.fn().mockResolvedValue(mockReassignMutationResponse);
|
||||
const keepAsPlaceholderMutationHandler = jest
|
||||
.fn()
|
||||
.mockResolvedValue(mockKeepAsPlaceholderMutationResponse);
|
||||
const cancelReassignmentMutationHandler = jest
|
||||
.fn()
|
||||
.mockResolvedValue(mockCancelReassignmentMutationResponse);
|
||||
const $toast = {
|
||||
show: jest.fn(),
|
||||
};
|
||||
|
||||
const createComponent = ({ queryHandler = usersQueryHandler, props = {} } = {}) => {
|
||||
mockApollo = createMockApollo([[searchUsersQuery, queryHandler]]);
|
||||
const createComponent = ({ seachUsersQueryHandler = usersQueryHandler, props = {} } = {}) => {
|
||||
mockApollo = createMockApollo([
|
||||
[searchUsersQuery, seachUsersQueryHandler],
|
||||
[importSourceUserReassignMutation, reassignMutationHandler],
|
||||
[importSourceUserKeepAsPlaceholderMutation, keepAsPlaceholderMutationHandler],
|
||||
[importSourceUserCancelReassignmentMutation, cancelReassignmentMutationHandler],
|
||||
]);
|
||||
|
||||
wrapper = shallowMountExtended(PlaceholderActions, {
|
||||
apolloProvider: mockApollo,
|
||||
propsData: {
|
||||
...defaultProps,
|
||||
...props,
|
||||
},
|
||||
mocks: { $toast },
|
||||
|
|
@ -68,7 +91,7 @@ describe('PlaceholderActions', () => {
|
|||
const usersFailedQueryHandler = jest.fn().mockRejectedValue(new Error('GraphQL error'));
|
||||
|
||||
createComponent({
|
||||
queryHandler: usersFailedQueryHandler,
|
||||
seachUsersQueryHandler: usersFailedQueryHandler,
|
||||
});
|
||||
await waitForPromises();
|
||||
});
|
||||
|
|
@ -108,12 +131,27 @@ describe('PlaceholderActions', () => {
|
|||
|
||||
it('renders confirm button as "Confirm"', () => {
|
||||
expect(findConfirmButton().text()).toBe('Confirm');
|
||||
expect(findConfirmButton().props()).toMatchObject({
|
||||
disabled: false,
|
||||
loading: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('emits "confirm" event when Confirm button is clicked', () => {
|
||||
findConfirmButton().vm.$emit('click');
|
||||
describe('when Confirm button is clicked', () => {
|
||||
beforeEach(async () => {
|
||||
findConfirmButton().vm.$emit('click');
|
||||
await nextTick();
|
||||
});
|
||||
|
||||
expect(wrapper.emitted('confirm')[0]).toEqual([undefined]);
|
||||
it('calls keepAsPlaceholder mutation', async () => {
|
||||
expect(findConfirmButton().props('loading')).toBe(true);
|
||||
await waitForPromises();
|
||||
expect(findConfirmButton().props('loading')).toBe(false);
|
||||
|
||||
expect(keepAsPlaceholderMutationHandler).toHaveBeenCalledWith({
|
||||
id: mockSourceUsers[0].id,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -128,12 +166,28 @@ describe('PlaceholderActions', () => {
|
|||
|
||||
it('renders confirm button as "Reassign"', () => {
|
||||
expect(findConfirmButton().text()).toBe('Reassign');
|
||||
expect(findConfirmButton().props()).toMatchObject({
|
||||
disabled: false,
|
||||
loading: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('emits "confirm" event when Reassign button is clicked', () => {
|
||||
findConfirmButton().vm.$emit('click');
|
||||
describe('when Reassign button is clicked', () => {
|
||||
beforeEach(async () => {
|
||||
findConfirmButton().vm.$emit('click');
|
||||
await nextTick();
|
||||
});
|
||||
|
||||
expect(wrapper.emitted('confirm')[0]).toEqual([mockUser1.id]);
|
||||
it('calls reassign mutation', async () => {
|
||||
expect(findConfirmButton().props('loading')).toBe(true);
|
||||
await waitForPromises();
|
||||
expect(findConfirmButton().props('loading')).toBe(false);
|
||||
|
||||
expect(reassignMutationHandler).toHaveBeenCalledWith({
|
||||
id: mockSourceUsers[0].id,
|
||||
userId: mockUser1.id,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -147,7 +201,7 @@ describe('PlaceholderActions', () => {
|
|||
.mockResolvedValueOnce(mockUsersQueryResponse);
|
||||
|
||||
createComponent({
|
||||
queryHandler: usersPaginatedQueryHandler,
|
||||
seachUsersQueryHandler: usersPaginatedQueryHandler,
|
||||
});
|
||||
await waitForPromises();
|
||||
});
|
||||
|
|
@ -181,18 +235,18 @@ describe('PlaceholderActions', () => {
|
|||
expect(user).toMatchObject({
|
||||
id: allUsers[index].id,
|
||||
name: allUsers[index].name,
|
||||
username: `@${allUsers[index].username}`,
|
||||
value: allUsers[index].id,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when status is pending_assignment', () => {
|
||||
describe('when status is PENDING_ASSIGNMENT', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
props: {
|
||||
placeholder: { status: 'pending_assignment' },
|
||||
sourceUser: { status: 'PENDING_ASSIGNMENT' },
|
||||
},
|
||||
});
|
||||
});
|
||||
|
|
@ -211,13 +265,13 @@ describe('PlaceholderActions', () => {
|
|||
});
|
||||
|
||||
describe('when status is AWAITING_APPROVAL', () => {
|
||||
const mockPlaceholder = mockSourceUsers[3];
|
||||
const mockSourceUser = mockSourceUsers[1];
|
||||
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
props: {
|
||||
placeholder: {
|
||||
...mockPlaceholder,
|
||||
sourceUser: {
|
||||
...mockSourceUser,
|
||||
status: 'AWAITING_APPROVAL',
|
||||
},
|
||||
},
|
||||
|
|
@ -226,7 +280,7 @@ describe('PlaceholderActions', () => {
|
|||
|
||||
it('renders disabled listbox with @username toggle text', () => {
|
||||
expect(findListbox().props()).toMatchObject({
|
||||
toggleText: mockPlaceholder.reassignToUser.username,
|
||||
toggleText: `@${mockSourceUser.reassignToUser.username}`,
|
||||
disabled: true,
|
||||
});
|
||||
});
|
||||
|
|
@ -249,21 +303,32 @@ describe('PlaceholderActions', () => {
|
|||
expect($toast.show).toHaveBeenCalledWith('Notification email sent.');
|
||||
});
|
||||
|
||||
it('emits "cancel" event when Cancel button is clicked', () => {
|
||||
findCancelButton().vm.$emit('click');
|
||||
describe('when Cancel button is clicked', () => {
|
||||
beforeEach(async () => {
|
||||
findCancelButton().vm.$emit('click');
|
||||
await nextTick();
|
||||
});
|
||||
|
||||
expect(wrapper.emitted('cancel')[0]).toEqual([]);
|
||||
it('calls cancelReassignment mutation', async () => {
|
||||
expect(findCancelButton().props('loading')).toBe(true);
|
||||
await waitForPromises();
|
||||
expect(findCancelButton().props('loading')).toBe(false);
|
||||
|
||||
expect(cancelReassignmentMutationHandler).toHaveBeenCalledWith({
|
||||
id: mockSourceUser.id,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when status is REASSIGNMENT_IN_PROGRESS', () => {
|
||||
const mockPlaceholder = mockSourceUsers[3];
|
||||
const mockSourceUser = mockSourceUsers[3];
|
||||
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
props: {
|
||||
placeholder: {
|
||||
...mockPlaceholder,
|
||||
sourceUser: {
|
||||
...mockSourceUser,
|
||||
status: 'REASSIGNMENT_IN_PROGRESS',
|
||||
},
|
||||
},
|
||||
|
|
@ -272,7 +337,7 @@ describe('PlaceholderActions', () => {
|
|||
|
||||
it('renders disabled listbox with @username toggle text', () => {
|
||||
expect(findListbox().props()).toMatchObject({
|
||||
toggleText: mockPlaceholder.reassignToUser.username,
|
||||
toggleText: `@${mockSourceUser.reassignToUser.username}`,
|
||||
disabled: true,
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -102,15 +102,28 @@ describe('PlaceholdersTable', () => {
|
|||
const firstRow = findTableRows().at(0);
|
||||
const actions = firstRow.findComponent(PlaceholderActions);
|
||||
|
||||
expect(actions.props('placeholder')).toEqual(mockSourceUsers[0]);
|
||||
expect(actions.props('sourceUser')).toEqual(mockSourceUsers[0]);
|
||||
});
|
||||
|
||||
it('renders avatar for final user when item is reassigned', () => {
|
||||
it('renders avatar for placeholderUser when item status is KEEP_AS_PLACEHOLDER', () => {
|
||||
createComponent({ mountFn: mount });
|
||||
|
||||
const reassignedItemRow = findTableRows().at(5);
|
||||
const actionsAvatar = reassignedItemRow.findAllComponents(GlAvatarLabeled).at(1);
|
||||
const { reassignToUser } = mockSourceUsers[5];
|
||||
const { placeholderUser } = mockSourceUsers[5];
|
||||
|
||||
expect(actionsAvatar.props()).toMatchObject({
|
||||
label: placeholderUser.name,
|
||||
subLabel: `@${placeholderUser.username}`,
|
||||
});
|
||||
});
|
||||
|
||||
it('renders avatar for reassignToUser when item status is COMPLETED', () => {
|
||||
createComponent({ mountFn: mount });
|
||||
|
||||
const reassignedItemRow = findTableRows().at(6);
|
||||
const actionsAvatar = reassignedItemRow.findAllComponents(GlAvatarLabeled).at(1);
|
||||
const { reassignToUser } = mockSourceUsers[6];
|
||||
|
||||
expect(actionsAvatar.props()).toMatchObject({
|
||||
label: reassignToUser.name,
|
||||
|
|
@ -118,29 +131,6 @@ describe('PlaceholdersTable', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('actions events', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({ mountFn: mount });
|
||||
});
|
||||
|
||||
it('emits "confirm" event with item and selectedUserId', () => {
|
||||
const selectedUserId = 647;
|
||||
const actions = findTableRows().at(2).findComponent(PlaceholderActions);
|
||||
|
||||
actions.vm.$emit('confirm', selectedUserId);
|
||||
|
||||
expect(wrapper.emitted('confirm')[0]).toEqual([mockSourceUsers[2], selectedUserId]);
|
||||
});
|
||||
|
||||
it('emits "cancel" event with item and selectedUserId', () => {
|
||||
const actions = findTableRows().at(2).findComponent(PlaceholderActions);
|
||||
|
||||
actions.vm.$emit('cancel');
|
||||
|
||||
expect(wrapper.emitted('cancel')[0]).toEqual([mockSourceUsers[2]]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when is "Re-assigned" table variant', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
|
|
|
|||
|
|
@ -292,3 +292,33 @@ export const inboundUpdateScopeSuccessResponse = {
|
|||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const mockPermissionsQueryResponse = (pushRepositoryForJobTokenAllowed = false) => ({
|
||||
data: {
|
||||
project: {
|
||||
id: 'gid://gitlab/Project/20',
|
||||
name: 'ops',
|
||||
ciCdSettings: {
|
||||
pushRepositoryForJobTokenAllowed,
|
||||
__typename: 'ProjectCiCdSetting',
|
||||
},
|
||||
__typename: 'Project',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const mockPermissionsMutationResponse = ({
|
||||
pushRepositoryForJobTokenAllowed = true,
|
||||
errors = [],
|
||||
} = {}) => ({
|
||||
data: {
|
||||
projectCiCdSettingsUpdate: {
|
||||
ciCdSettings: {
|
||||
pushRepositoryForJobTokenAllowed,
|
||||
__typename: 'ProjectCiCdSetting',
|
||||
},
|
||||
errors,
|
||||
__typename: 'Project',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,16 +1,24 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import OutboundTokenAccess from '~/token_access/components/outbound_token_access.vue';
|
||||
import InboundTokenAccess from '~/token_access/components/inbound_token_access.vue';
|
||||
import OutboundTokenAccess from '~/token_access/components/outbound_token_access.vue';
|
||||
import TokenAccessApp from '~/token_access/components/token_access_app.vue';
|
||||
import TokenPermissions from '~/token_access/components/token_permissions.vue';
|
||||
|
||||
describe('TokenAccessApp component', () => {
|
||||
let wrapper;
|
||||
|
||||
const findOutboundTokenAccess = () => wrapper.findComponent(OutboundTokenAccess);
|
||||
const findInboundTokenAccess = () => wrapper.findComponent(InboundTokenAccess);
|
||||
const findOutboundTokenAccess = () => wrapper.findComponent(OutboundTokenAccess);
|
||||
const findTokenPermissions = () => wrapper.findComponent(TokenPermissions);
|
||||
|
||||
const createComponent = () => {
|
||||
wrapper = shallowMount(TokenAccessApp);
|
||||
const createComponent = ({ allowPushRepositoryForJobToken = true } = {}) => {
|
||||
wrapper = shallowMount(TokenAccessApp, {
|
||||
provide: {
|
||||
glFeatures: {
|
||||
allowPushRepositoryForJobToken,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
describe('default', () => {
|
||||
|
|
@ -23,9 +31,21 @@ describe('TokenAccessApp component', () => {
|
|||
});
|
||||
|
||||
it('renders the inbound token access component', () => {
|
||||
createComponent(true);
|
||||
|
||||
expect(findInboundTokenAccess().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('renders the token permissions component', () => {
|
||||
expect(findTokenPermissions().exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when allowPushRepositoryForJobToken feature flag is disabled', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({ allowPushRepositoryForJobToken: false });
|
||||
});
|
||||
|
||||
it('does not render the token permissions component', () => {
|
||||
expect(findTokenPermissions().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,169 @@
|
|||
import { GlButton, GlFormCheckbox, GlLoadingIcon } from '@gitlab/ui';
|
||||
import Vue, { nextTick } from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import { createAlert } from '~/alert';
|
||||
import TokenPermissions from '~/token_access/components/token_permissions.vue';
|
||||
import updateCiJobTokenPermissionsMutation from '~/token_access/graphql/mutations/update_ci_job_token_permissions.mutation.graphql';
|
||||
import getCiJobTokenPermissionsQuery from '~/token_access/graphql/queries/get_ci_job_token_permissions.query.graphql';
|
||||
import { mockPermissionsQueryResponse, mockPermissionsMutationResponse } from './mock_data';
|
||||
|
||||
Vue.use(VueApollo);
|
||||
|
||||
jest.mock('~/alert');
|
||||
|
||||
describe('TokenPermissions component', () => {
|
||||
let wrapper;
|
||||
const mockQuery = jest.fn();
|
||||
const mockMutation = jest.fn();
|
||||
const mockToastShow = jest.fn();
|
||||
const fullPath = 'root/my-repo';
|
||||
|
||||
const findButton = () => wrapper.findComponent(GlButton);
|
||||
const findCheckbox = () => wrapper.findComponent(GlFormCheckbox);
|
||||
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
|
||||
|
||||
const createComponent = () => {
|
||||
const handlers = [
|
||||
[getCiJobTokenPermissionsQuery, mockQuery],
|
||||
[updateCiJobTokenPermissionsMutation, mockMutation],
|
||||
];
|
||||
|
||||
wrapper = shallowMountExtended(TokenPermissions, {
|
||||
provide: {
|
||||
fullPath,
|
||||
},
|
||||
apolloProvider: createMockApollo(handlers),
|
||||
mocks: {
|
||||
$toast: {
|
||||
show: mockToastShow,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const updateCheckbox = (value) => {
|
||||
findCheckbox().vm.$emit('input', value);
|
||||
findButton().vm.$emit('click');
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockQuery.mockResolvedValue(mockPermissionsQueryResponse());
|
||||
mockMutation.mockResolvedValue(mockPermissionsMutationResponse());
|
||||
});
|
||||
|
||||
describe('while waiting for query to resolve', () => {
|
||||
it('shows loading state', async () => {
|
||||
createComponent();
|
||||
|
||||
expect(findLoadingIcon().exists()).toBe(true);
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(findLoadingIcon().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when query is not successful', () => {
|
||||
beforeEach(async () => {
|
||||
mockQuery.mockRejectedValue();
|
||||
|
||||
createComponent();
|
||||
await waitForPromises();
|
||||
});
|
||||
|
||||
it('does not show the loading state', () => {
|
||||
expect(findLoadingIcon().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('renders the alert message', () => {
|
||||
expect(createAlert).toHaveBeenCalledWith({
|
||||
message: 'There was a problem fetching the CI/CD job token permissions.',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when query is successful', () => {
|
||||
it('does not show the loading state or the alert message', async () => {
|
||||
createComponent();
|
||||
await waitForPromises();
|
||||
|
||||
expect(findLoadingIcon().exists()).toBe(false);
|
||||
expect(createAlert).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('renders checked checkbox when data returns true', async () => {
|
||||
mockQuery.mockResolvedValue(mockPermissionsQueryResponse(true));
|
||||
createComponent();
|
||||
await waitForPromises();
|
||||
|
||||
expect(findCheckbox().attributes('checked')).toBeDefined();
|
||||
});
|
||||
|
||||
it('renders unchecked checkbox when data returns false', async () => {
|
||||
mockQuery.mockResolvedValue(mockPermissionsQueryResponse(false));
|
||||
createComponent();
|
||||
await waitForPromises();
|
||||
|
||||
expect(findCheckbox().attributes('checked')).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when updating allowPushToRepo', () => {
|
||||
beforeEach(async () => {
|
||||
createComponent();
|
||||
await waitForPromises();
|
||||
});
|
||||
|
||||
it('renders loading icon in button while mutation is resolving', async () => {
|
||||
expect(findButton().props('loading')).toBe(false);
|
||||
|
||||
updateCheckbox(true);
|
||||
await nextTick();
|
||||
|
||||
expect(findButton().props('loading')).toBe(true);
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(findButton().props('loading')).toBe(false);
|
||||
});
|
||||
|
||||
it('renders alert message when mutation has errors', async () => {
|
||||
mockMutation.mockResolvedValue(
|
||||
mockPermissionsMutationResponse({ errors: ['Something went wrong.'] }),
|
||||
);
|
||||
|
||||
updateCheckbox(true);
|
||||
await waitForPromises();
|
||||
|
||||
expect(createAlert).toHaveBeenCalledWith({ message: 'Something went wrong.' });
|
||||
expect(mockToastShow).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('when mutation is successful', () => {
|
||||
it('mutation is called with the correct variables', async () => {
|
||||
updateCheckbox(true);
|
||||
await waitForPromises();
|
||||
|
||||
expect(mockMutation).toHaveBeenCalledWith({
|
||||
input: {
|
||||
fullPath,
|
||||
pushRepositoryForJobTokenAllowed: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('renders toast message', async () => {
|
||||
updateCheckbox(true);
|
||||
await waitForPromises();
|
||||
|
||||
expect(createAlert).not.toHaveBeenCalled();
|
||||
expect(mockToastShow).toHaveBeenCalledWith(
|
||||
`CI/CD job token permissions for 'ops' were successfully updated.`,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import MockAdapter from 'axios-mock-adapter';
|
||||
import { useMockInternalEventsTracking } from 'helpers/tracking_internal_events_helper';
|
||||
import { mountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import api from '~/api';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status';
|
||||
import Poll from '~/lib/utils/poll';
|
||||
|
|
@ -99,6 +99,7 @@ describe('Terraform extension', () => {
|
|||
});
|
||||
|
||||
describe('expanded data', () => {
|
||||
const { bindInternalEventDocument } = useMockInternalEventsTracking();
|
||||
beforeEach(async () => {
|
||||
mockPollingApi(HTTP_STATUS_OK, plans, {});
|
||||
createComponent();
|
||||
|
|
@ -134,19 +135,13 @@ describe('Terraform extension', () => {
|
|||
});
|
||||
|
||||
it('responds with the correct telemetry when the deeply nested "Full log" link is clicked', () => {
|
||||
api.trackRedisHllUserEvent.mockClear();
|
||||
api.trackRedisCounterEvent.mockClear();
|
||||
const { trackEventSpy } = bindInternalEventDocument(wrapper.element);
|
||||
|
||||
findActionButton(0).trigger('click');
|
||||
|
||||
expect(api.trackRedisHllUserEvent).toHaveBeenCalledTimes(1);
|
||||
expect(api.trackRedisHllUserEvent).toHaveBeenCalledWith(
|
||||
'i_code_review_merge_request_widget_terraform_click_full_report',
|
||||
);
|
||||
expect(api.trackRedisCounterEvent).toHaveBeenCalledTimes(1);
|
||||
expect(api.trackRedisCounterEvent).toHaveBeenCalledWith(
|
||||
'i_code_review_merge_request_widget_terraform_count_click_full_report',
|
||||
);
|
||||
expect(trackEventSpy).toHaveBeenCalledWith('click_full_report_on_merge_request_widget', {
|
||||
label: 'terraform',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ RSpec.describe 'new tables with gitlab_main schema', feature_category: :cell do
|
|||
# Specific tables can be exempted from this requirement, and such tables must be added to the `exempted_tables` list.
|
||||
let!(:exempted_tables) do
|
||||
[
|
||||
"sbom_source_packages" # https://gitlab.com/gitlab-org/gitlab/-/issues/437718
|
||||
"sbom_source_packages", # https://gitlab.com/gitlab-org/gitlab/-/issues/437718
|
||||
"oauth_device_grants" # https://gitlab.com/gitlab-org/gitlab/-/issues/463785
|
||||
]
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ RSpec.describe 'Code review events' do
|
|||
i_code_review_create_note_in_ipynb_diff_mr
|
||||
i_code_review_create_note_in_ipynb_diff_commit
|
||||
i_code_review_merge_request_widget_license_compliance_warning
|
||||
click_full_report_on_merge_request_widget
|
||||
]
|
||||
|
||||
all_code_review_events = Gitlab::Usage::MetricDefinition.all.flat_map do |definition|
|
||||
|
|
|
|||
|
|
@ -53,45 +53,35 @@ RSpec.describe ContainerRepository, :aggregate_failures, feature_category: :cont
|
|||
describe '#last_published_at' do
|
||||
subject { repository.last_published_at }
|
||||
|
||||
context 'on GitLab.com', :saas do
|
||||
context 'supports gitlab api' do
|
||||
before do
|
||||
stub_container_registry_gitlab_api_support(supported: true)
|
||||
expect(repository.gitlab_api_client).to receive(:repository_details).with(repository.path, sizing: :self).and_return(response)
|
||||
end
|
||||
|
||||
context 'with a size_bytes field' do
|
||||
let(:timestamp_string) { '2024-04-30T06:07:36.225Z' }
|
||||
let(:response) { { 'last_published_at' => timestamp_string } }
|
||||
|
||||
it { is_expected.to eq(DateTime.iso8601(timestamp_string)) }
|
||||
end
|
||||
|
||||
context 'without a last_published_at field' do
|
||||
let(:response) { { 'foo' => 'bar' } }
|
||||
|
||||
it { is_expected.to eq(nil) }
|
||||
end
|
||||
|
||||
context 'with an invalid value for the last_published_at field' do
|
||||
let(:response) { { 'last_published_at' => 'foobar' } }
|
||||
|
||||
it { is_expected.to eq(nil) }
|
||||
end
|
||||
context 'when the GitLab API is supported' do
|
||||
before do
|
||||
stub_container_registry_gitlab_api_support(supported: true)
|
||||
expect(repository.gitlab_api_client).to receive(:repository_details).with(repository.path, sizing: :self).and_return(response)
|
||||
end
|
||||
|
||||
context 'does not support gitlab api' do
|
||||
before do
|
||||
stub_container_registry_gitlab_api_support(supported: false)
|
||||
expect(repository.gitlab_api_client).not_to receive(:repository_details)
|
||||
end
|
||||
context 'with a size_bytes field' do
|
||||
let(:timestamp_string) { '2024-04-30T06:07:36.225Z' }
|
||||
let(:response) { { 'last_published_at' => timestamp_string } }
|
||||
|
||||
it { is_expected.to eq(DateTime.iso8601(timestamp_string)) }
|
||||
end
|
||||
|
||||
context 'without a last_published_at field' do
|
||||
let(:response) { { 'foo' => 'bar' } }
|
||||
|
||||
it { is_expected.to eq(nil) }
|
||||
end
|
||||
|
||||
context 'with an invalid value for the last_published_at field' do
|
||||
let(:response) { { 'last_published_at' => 'foobar' } }
|
||||
|
||||
it { is_expected.to eq(nil) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'not on GitLab.com' do
|
||||
context 'when the GitLab API is not supported' do
|
||||
before do
|
||||
stub_container_registry_gitlab_api_support(supported: false)
|
||||
expect(repository.gitlab_api_client).not_to receive(:repository_details)
|
||||
end
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue