Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
82d72ee0ea
commit
e7bfce6d9f
|
|
@ -280,7 +280,6 @@ export default {
|
||||||
'app/assets/javascripts/work_items/components/work_item_links/work_item_rolled_up_count.vue',
|
'app/assets/javascripts/work_items/components/work_item_links/work_item_rolled_up_count.vue',
|
||||||
'app/assets/javascripts/work_items/components/work_item_links/work_item_rolled_up_data.vue',
|
'app/assets/javascripts/work_items/components/work_item_links/work_item_rolled_up_data.vue',
|
||||||
'app/assets/javascripts/work_items/components/work_item_links/work_item_tree.vue',
|
'app/assets/javascripts/work_items/components/work_item_links/work_item_tree.vue',
|
||||||
'app/assets/javascripts/work_items/components/work_item_sticky_header.vue',
|
|
||||||
'ee/app/assets/javascripts/admin/subscriptions/show/components/subscription_breakdown.vue',
|
'ee/app/assets/javascripts/admin/subscriptions/show/components/subscription_breakdown.vue',
|
||||||
'ee/app/assets/javascripts/ai/components/duo_chat_feedback_modal.vue',
|
'ee/app/assets/javascripts/ai/components/duo_chat_feedback_modal.vue',
|
||||||
'ee/app/assets/javascripts/ai/components/user_feedback.vue',
|
'ee/app/assets/javascripts/ai/components/user_feedback.vue',
|
||||||
|
|
@ -444,11 +443,6 @@ export default {
|
||||||
'ee/app/assets/javascripts/vulnerabilities/components/new_vulnerability/section_details.vue',
|
'ee/app/assets/javascripts/vulnerabilities/components/new_vulnerability/section_details.vue',
|
||||||
'ee/app/assets/javascripts/vulnerabilities/components/vulnerability_details.vue',
|
'ee/app/assets/javascripts/vulnerabilities/components/vulnerability_details.vue',
|
||||||
'ee/app/assets/javascripts/vulnerabilities/components/vulnerability_training.vue',
|
'ee/app/assets/javascripts/vulnerabilities/components/vulnerability_training.vue',
|
||||||
'ee/app/assets/javascripts/work_items/components/work_item_custom_fields_multi_select.vue',
|
|
||||||
'ee/app/assets/javascripts/work_items/components/work_item_custom_fields_single_select.vue',
|
|
||||||
'ee/app/assets/javascripts/work_items/components/work_item_health_status.vue',
|
|
||||||
'ee/app/assets/javascripts/work_items/components/work_item_iteration.vue',
|
|
||||||
'ee/app/assets/javascripts/work_items/components/work_item_links/work_item_rolled_up_health_status.vue',
|
|
||||||
],
|
],
|
||||||
rules: {
|
rules: {
|
||||||
'vue/no-unused-properties': 'off',
|
'vue/no-unused-properties': 'off',
|
||||||
|
|
|
||||||
|
|
@ -158,7 +158,7 @@ db:check-schema-single-db:
|
||||||
db:check-migrations:
|
db:check-migrations:
|
||||||
extends:
|
extends:
|
||||||
- .db-job-base
|
- .db-job-base
|
||||||
- .use-pg14 # Should match the db same version used by GDK
|
- .use-pg16 # Should match the db same version used by GDK
|
||||||
- .rails:rules:ee-and-foss-mr-with-migration
|
- .rails:rules:ee-and-foss-mr-with-migration
|
||||||
script:
|
script:
|
||||||
- |
|
- |
|
||||||
|
|
|
||||||
|
|
@ -23,4 +23,4 @@
|
||||||
<!-- Make sure to add one of the type labels (as per https://handbook.gitlab.com/handbook/product/groups/product-analysis/engineering/metrics/#work-type-classification):-->
|
<!-- Make sure to add one of the type labels (as per https://handbook.gitlab.com/handbook/product/groups/product-analysis/engineering/metrics/#work-type-classification):-->
|
||||||
<!-- /label ~"type::bug" ~"type::feature" ~"type::tooling" ~"type::maintenance" -->
|
<!-- /label ~"type::bug" ~"type::feature" ~"type::tooling" ~"type::maintenance" -->
|
||||||
|
|
||||||
/label ~devops::analytics ~"group::analytics instrumentation"
|
/label ~"group::analytics instrumentation"
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,6 @@ Gitlab/FeatureFlagWithoutActor:
|
||||||
- 'ee/app/views/projects/on_demand_scans/index.html.haml'
|
- 'ee/app/views/projects/on_demand_scans/index.html.haml'
|
||||||
- 'ee/app/views/projects/settings/merge_requests/_merge_trains_settings.html.haml'
|
- 'ee/app/views/projects/settings/merge_requests/_merge_trains_settings.html.haml'
|
||||||
- 'ee/lib/api/code_suggestions.rb'
|
- 'ee/lib/api/code_suggestions.rb'
|
||||||
- 'ee/lib/api/internal/search/zoekt.rb'
|
|
||||||
- 'ee/lib/api/internal/suggested_reviewers.rb'
|
- 'ee/lib/api/internal/suggested_reviewers.rb'
|
||||||
- 'ee/lib/ee/api/entities/application_setting.rb'
|
- 'ee/lib/ee/api/entities/application_setting.rb'
|
||||||
- 'ee/lib/ee/api/geo.rb'
|
- 'ee/lib/ee/api/geo.rb'
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
2fb6e0b3a55ae7554b353ea70a54a97b7fe6512f
|
184f295d9e55a273ce85a2c3c32da526f32a8f6b
|
||||||
|
|
|
||||||
|
|
@ -26,15 +26,7 @@ export default () => {
|
||||||
|
|
||||||
const isGroupPage = pageType === 'groups';
|
const isGroupPage = pageType === 'groups';
|
||||||
|
|
||||||
// This is a mini state to help the breadcrumb have the correct name in the details page
|
const router = createRouter(endpoint);
|
||||||
const breadCrumbState = Vue.observable({
|
|
||||||
name: '',
|
|
||||||
updateName(value) {
|
|
||||||
this.name = value;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const router = createRouter(endpoint, breadCrumbState);
|
|
||||||
|
|
||||||
const attachMainComponent = () =>
|
const attachMainComponent = () =>
|
||||||
new Vue({
|
new Vue({
|
||||||
|
|
@ -50,7 +42,6 @@ export default () => {
|
||||||
npmGroupUrl,
|
npmGroupUrl,
|
||||||
projectListUrl,
|
projectListUrl,
|
||||||
groupListUrl,
|
groupListUrl,
|
||||||
breadCrumbState,
|
|
||||||
settingsPath,
|
settingsPath,
|
||||||
canDeletePackages: parseBoolean(canDeletePackages),
|
canDeletePackages: parseBoolean(canDeletePackages),
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,7 @@ export default {
|
||||||
GlModal: GlModalDirective,
|
GlModal: GlModalDirective,
|
||||||
},
|
},
|
||||||
mixins: [Tracking.mixin()],
|
mixins: [Tracking.mixin()],
|
||||||
inject: ['emptyListIllustration', 'projectListUrl', 'groupListUrl', 'breadCrumbState'],
|
inject: ['emptyListIllustration', 'projectListUrl', 'groupListUrl'],
|
||||||
trackingActions: {
|
trackingActions: {
|
||||||
DELETE_PACKAGE_TRACKING_ACTION,
|
DELETE_PACKAGE_TRACKING_ACTION,
|
||||||
REQUEST_DELETE_PACKAGE_TRACKING_ACTION,
|
REQUEST_DELETE_PACKAGE_TRACKING_ACTION,
|
||||||
|
|
@ -117,11 +117,6 @@ export default {
|
||||||
error,
|
error,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
result() {
|
|
||||||
this.breadCrumbState.updateName(
|
|
||||||
`${this.packageEntity?.name} v${this.packageEntity?.version}`,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
groupSettings: {
|
groupSettings: {
|
||||||
query: getGroupPackageSettings,
|
query: getGroupPackageSettings,
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import { PACKAGE_REGISTRY_TITLE } from '~/packages_and_registries/package_regist
|
||||||
|
|
||||||
Vue.use(VueRouter);
|
Vue.use(VueRouter);
|
||||||
|
|
||||||
export default function createRouter(base, breadCrumbState) {
|
export default function createRouter(base) {
|
||||||
const router = new VueRouter({
|
const router = new VueRouter({
|
||||||
base,
|
base,
|
||||||
mode: 'history',
|
mode: 'history',
|
||||||
|
|
@ -24,16 +24,9 @@ export default function createRouter(base, breadCrumbState) {
|
||||||
name: 'details',
|
name: 'details',
|
||||||
path: '/:id',
|
path: '/:id',
|
||||||
component: Details,
|
component: Details,
|
||||||
meta: {
|
|
||||||
nameGenerator: () => breadCrumbState.name,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
router.afterEach(() => {
|
|
||||||
breadCrumbState.updateName('');
|
|
||||||
});
|
|
||||||
|
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ export default {
|
||||||
return this.$route.name === this.rootRoute.name;
|
return this.$route.name === this.rootRoute.name;
|
||||||
},
|
},
|
||||||
detailsRouteName() {
|
detailsRouteName() {
|
||||||
return this.detailsRoute.meta.nameGenerator();
|
return `${this.$route.params?.id}`;
|
||||||
},
|
},
|
||||||
isLoaded() {
|
isLoaded() {
|
||||||
return this.isRootRoute || this.detailsRouteName;
|
return this.isRootRoute || this.detailsRouteName;
|
||||||
|
|
@ -35,7 +35,7 @@ export default {
|
||||||
if (!this.isRootRoute) {
|
if (!this.isRootRoute) {
|
||||||
crumbs.push({
|
crumbs.push({
|
||||||
text: this.detailsRouteName,
|
text: this.detailsRouteName,
|
||||||
href: this.detailsRoute.meta.path,
|
href: this.detailsRoute.path,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return crumbs;
|
return crumbs;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { GlButton, GlTooltipDirective, GlIcon } from '@gitlab/ui';
|
import { GlButton, GlTooltipDirective, GlIcon, GlAnimatedLoaderIcon } from '@gitlab/ui';
|
||||||
import { TYPE_ISSUE } from '~/issues/constants';
|
import { TYPE_ISSUE } from '~/issues/constants';
|
||||||
import { __, sprintf, s__ } from '~/locale';
|
import { __, sprintf, s__ } from '~/locale';
|
||||||
import ReviewerAvatarLink from './reviewer_avatar_link.vue';
|
import ReviewerAvatarLink from './reviewer_avatar_link.vue';
|
||||||
|
|
@ -44,6 +44,7 @@ export default {
|
||||||
components: {
|
components: {
|
||||||
GlButton,
|
GlButton,
|
||||||
GlIcon,
|
GlIcon,
|
||||||
|
GlAnimatedLoaderIcon,
|
||||||
ReviewerAvatarLink,
|
ReviewerAvatarLink,
|
||||||
},
|
},
|
||||||
directives: {
|
directives: {
|
||||||
|
|
@ -216,7 +217,15 @@ export default {
|
||||||
:class="reviewStateIcon(user).class"
|
:class="reviewStateIcon(user).class"
|
||||||
data-testid="reviewer-state-icon-parent"
|
data-testid="reviewer-state-icon-parent"
|
||||||
>
|
>
|
||||||
|
<gl-animated-loader-icon
|
||||||
|
v-if="
|
||||||
|
user.type === 'DUO_CODE_REVIEW_BOT' &&
|
||||||
|
user.mergeRequestInteraction.reviewState === 'REVIEW_STARTED'
|
||||||
|
"
|
||||||
|
is-on
|
||||||
|
/>
|
||||||
<gl-icon
|
<gl-icon
|
||||||
|
v-else
|
||||||
:size="reviewStateIcon(user).size || 16"
|
:size="reviewStateIcon(user).size || 16"
|
||||||
:name="reviewStateIcon(user).name"
|
:name="reviewStateIcon(user).name"
|
||||||
:class="reviewStateIcon(user).iconClass"
|
:class="reviewStateIcon(user).iconClass"
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ query mergeRequestReviewers($fullPath: ID!, $iid: String!) {
|
||||||
nodes {
|
nodes {
|
||||||
...User
|
...User
|
||||||
...UserAvailability
|
...UserAvailability
|
||||||
|
type
|
||||||
mergeRequestInteraction {
|
mergeRequestInteraction {
|
||||||
canMerge
|
canMerge
|
||||||
canUpdate
|
canUpdate
|
||||||
|
|
|
||||||
|
|
@ -39,8 +39,8 @@ export const SEARCH_SCOPE = {
|
||||||
export const GLOBAL_COMMANDS_GROUP_TITLE = s__('CommandPalette|Global Commands');
|
export const GLOBAL_COMMANDS_GROUP_TITLE = s__('CommandPalette|Global Commands');
|
||||||
export const USERS_GROUP_TITLE = s__('GlobalSearch|Users');
|
export const USERS_GROUP_TITLE = s__('GlobalSearch|Users');
|
||||||
export const PAGES_GROUP_TITLE = s__('CommandPalette|Pages');
|
export const PAGES_GROUP_TITLE = s__('CommandPalette|Pages');
|
||||||
export const PROJECTS_GROUP_TITLE = s__('GlobalSearch|Projects');
|
export const PROJECTS_GROUP_TITLE = s__("GlobalSearch|Projects I'm a member of");
|
||||||
export const GROUPS_GROUP_TITLE = s__('GlobalSearch|Groups');
|
export const GROUPS_GROUP_TITLE = s__("GlobalSearch|Groups I'm a member of");
|
||||||
export const ISSUES_GROUP_TITLE = s__('GlobalSearch|Issues');
|
export const ISSUES_GROUP_TITLE = s__('GlobalSearch|Issues');
|
||||||
export const PATH_GROUP_TITLE = s__('CommandPalette|Project files');
|
export const PATH_GROUP_TITLE = s__('CommandPalette|Project files');
|
||||||
export const SETTINGS_GROUP_TITLE = s__('CommandPalette|Settings');
|
export const SETTINGS_GROUP_TITLE = s__('CommandPalette|Settings');
|
||||||
|
|
@ -75,3 +75,6 @@ export const OVERLAY_GOTO = s__('GlobalSearch|Go to %{kbdStart}↵%{kbdEnd}');
|
||||||
|
|
||||||
export const FREQUENTLY_VISITED_PROJECTS_HANDLE = 'FREQUENTLY_VISITED_PROJECTS_HANDLE';
|
export const FREQUENTLY_VISITED_PROJECTS_HANDLE = 'FREQUENTLY_VISITED_PROJECTS_HANDLE';
|
||||||
export const FREQUENTLY_VISITED_GROUPS_HANDLE = 'FREQUENTLY_VISITED_GROUPS_HANDLE';
|
export const FREQUENTLY_VISITED_GROUPS_HANDLE = 'FREQUENTLY_VISITED_GROUPS_HANDLE';
|
||||||
|
|
||||||
|
export const GROUPS_GROUP_HANDLE = 'Groups';
|
||||||
|
export const PROJECTS_GROUP_HANDLE = 'Projects';
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,8 @@ import {
|
||||||
ISSUES_GROUP_TITLE,
|
ISSUES_GROUP_TITLE,
|
||||||
PAGES_GROUP_TITLE,
|
PAGES_GROUP_TITLE,
|
||||||
GROUPS_GROUP_TITLE,
|
GROUPS_GROUP_TITLE,
|
||||||
|
GROUPS_GROUP_HANDLE,
|
||||||
|
PROJECTS_GROUP_HANDLE,
|
||||||
} from '../command_palette/constants';
|
} from '../command_palette/constants';
|
||||||
import SearchResultFocusLayover from './global_search_focus_overlay.vue';
|
import SearchResultFocusLayover from './global_search_focus_overlay.vue';
|
||||||
import GlobalSearchNoResults from './global_search_no_results.vue';
|
import GlobalSearchNoResults from './global_search_no_results.vue';
|
||||||
|
|
@ -81,7 +83,7 @@ export default {
|
||||||
groups() {
|
groups() {
|
||||||
return this.autocompleteGroupedSearchOptions.map((group) => {
|
return this.autocompleteGroupedSearchOptions.map((group) => {
|
||||||
return {
|
return {
|
||||||
name: group?.name,
|
name: this.modifiedGroupName(group?.name),
|
||||||
items: group?.items?.map((item) => {
|
items: group?.items?.map((item) => {
|
||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
|
|
@ -160,6 +162,17 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
modifiedGroupName(groupName) {
|
||||||
|
if (groupName === GROUPS_GROUP_HANDLE) {
|
||||||
|
return this.$options.i18n.GROUPS_GROUP_TITLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (groupName === PROJECTS_GROUP_HANDLE) {
|
||||||
|
return this.$options.i18n.PROJECTS_GROUP_TITLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return groupName;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
AVATAR_SHAPE_OPTION_RECT,
|
AVATAR_SHAPE_OPTION_RECT,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -162,9 +162,7 @@ export default {
|
||||||
<template #description>
|
<template #description>
|
||||||
<gl-sprintf
|
<gl-sprintf
|
||||||
:message="
|
:message="
|
||||||
s__(
|
s__('CICD|Authentication events using the job token. %{linkStart}Learn more.%{linkEnd}')
|
||||||
'CICD|Authentication events from the last 30 days. %{linkStart}Learn more.%{linkEnd}',
|
|
||||||
)
|
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<template #link="{ content }">
|
<template #link="{ content }">
|
||||||
|
|
@ -214,7 +212,7 @@ export default {
|
||||||
class="gl-text-center"
|
class="gl-text-center"
|
||||||
data-testid="auth-logs-no-events"
|
data-testid="auth-logs-no-events"
|
||||||
>
|
>
|
||||||
{{ s__('CICD|No authentication events in the last 30 days.') }}
|
{{ s__('CICD|No authentication events to display.') }}
|
||||||
</div>
|
</div>
|
||||||
</crud-component>
|
</crud-component>
|
||||||
<gl-keyset-pagination
|
<gl-keyset-pagination
|
||||||
|
|
|
||||||
|
|
@ -1051,7 +1051,6 @@ export default {
|
||||||
:work-item-type="selectedWorkItemTypeName"
|
:work-item-type="selectedWorkItemTypeName"
|
||||||
:custom-fields="workItemCustomFields"
|
:custom-fields="workItemCustomFields"
|
||||||
:full-path="selectedProjectFullPath"
|
:full-path="selectedProjectFullPath"
|
||||||
:is-group="isGroup"
|
|
||||||
:can-update="canUpdate"
|
:can-update="canUpdate"
|
||||||
@error="$emit('error', $event)"
|
@error="$emit('error', $event)"
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -330,7 +330,6 @@ export default {
|
||||||
:custom-fields="customFields"
|
:custom-fields="customFields"
|
||||||
:full-path="fullPath"
|
:full-path="fullPath"
|
||||||
:can-update="canUpdateMetadata"
|
:can-update="canUpdateMetadata"
|
||||||
:is-group="isGroup"
|
|
||||||
@error="$emit('error', $event)"
|
@error="$emit('error', $event)"
|
||||||
/>
|
/>
|
||||||
<work-item-parent
|
<work-item-parent
|
||||||
|
|
|
||||||
|
|
@ -896,21 +896,10 @@ export default {
|
||||||
v-if="showIntersectionObserver"
|
v-if="showIntersectionObserver"
|
||||||
:current-user-todos="currentUserTodos"
|
:current-user-todos="currentUserTodos"
|
||||||
:show-work-item-current-user-todos="showWorkItemCurrentUserTodos"
|
:show-work-item-current-user-todos="showWorkItemCurrentUserTodos"
|
||||||
:parent-work-item-confidentiality="parentWorkItemConfidentiality"
|
|
||||||
:update-in-progress="updateInProgress"
|
:update-in-progress="updateInProgress"
|
||||||
:full-path="workItemFullPath"
|
|
||||||
:is-modal="isModal"
|
|
||||||
:work-item="workItem"
|
:work-item="workItem"
|
||||||
:is-sticky-header-showing="isStickyHeaderShowing"
|
:is-sticky-header-showing="isStickyHeaderShowing"
|
||||||
:work-item-notifications-subscribed="workItemNotificationsSubscribed"
|
:work-item-notifications-subscribed="workItemNotificationsSubscribed"
|
||||||
:work-item-author-id="workItemAuthorId"
|
|
||||||
:is-group="isGroupWorkItem"
|
|
||||||
:allowed-child-types="allowedChildTypes"
|
|
||||||
:parent-id="parentWorkItemId"
|
|
||||||
:namespace-full-name="namespaceFullName"
|
|
||||||
:has-children="hasChildren"
|
|
||||||
:show-sidebar="showSidebar"
|
|
||||||
:truncation-enabled="truncationEnabled"
|
|
||||||
@hideStickyHeader="hideStickyHeader"
|
@hideStickyHeader="hideStickyHeader"
|
||||||
@showStickyHeader="showStickyHeader"
|
@showStickyHeader="showStickyHeader"
|
||||||
@deleteWorkItem="$emit('deleteWorkItem', { workItemType, workItemId: workItem.id })"
|
@deleteWorkItem="$emit('deleteWorkItem', { workItemType, workItemId: workItem.id })"
|
||||||
|
|
|
||||||
|
|
@ -27,10 +27,6 @@ export default {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
fullPath: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
isStickyHeaderShowing: {
|
isStickyHeaderShowing: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: true,
|
required: true,
|
||||||
|
|
@ -44,63 +40,16 @@ export default {
|
||||||
required: false,
|
required: false,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
parentWorkItemConfidentiality: {
|
|
||||||
type: Boolean,
|
|
||||||
required: false,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
parentId: {
|
|
||||||
type: String,
|
|
||||||
required: false,
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
showWorkItemCurrentUserTodos: {
|
showWorkItemCurrentUserTodos: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
isModal: {
|
|
||||||
type: Boolean,
|
|
||||||
required: false,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
currentUserTodos: {
|
currentUserTodos: {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: false,
|
required: false,
|
||||||
default: () => [],
|
default: () => [],
|
||||||
},
|
},
|
||||||
workItemAuthorId: {
|
|
||||||
type: Number,
|
|
||||||
required: false,
|
|
||||||
default: 0,
|
|
||||||
},
|
|
||||||
isGroup: {
|
|
||||||
type: Boolean,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
allowedChildTypes: {
|
|
||||||
type: Array,
|
|
||||||
required: false,
|
|
||||||
default: () => [],
|
|
||||||
},
|
|
||||||
namespaceFullName: {
|
|
||||||
type: String,
|
|
||||||
required: false,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
hasChildren: {
|
|
||||||
type: Boolean,
|
|
||||||
required: false,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
showSidebar: {
|
|
||||||
type: Boolean,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
truncationEnabled: {
|
|
||||||
type: Boolean,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
canUpdate() {
|
canUpdate() {
|
||||||
|
|
|
||||||
|
|
@ -233,7 +233,13 @@ class Issue < ApplicationRecord
|
||||||
scope :confidential_only, -> { where(confidential: true) }
|
scope :confidential_only, -> { where(confidential: true) }
|
||||||
|
|
||||||
scope :without_hidden, -> {
|
scope :without_hidden, -> {
|
||||||
where('NOT EXISTS (?)', Users::BannedUser.select(1).where('issues.author_id = banned_users.user_id'))
|
if Feature.enabled?(:optimize_issues_banned_users_query, :instance)
|
||||||
|
# We add `+ 0` to the author_id to make the query planner use a nested loop and prevent
|
||||||
|
# loading of all banned user IDs for certain queries
|
||||||
|
where_not_exists(Users::BannedUser.where('banned_users.user_id = (issues.author_id + 0)'))
|
||||||
|
else
|
||||||
|
where_not_exists(Users::BannedUser.where('banned_users.user_id = issues.author_id'))
|
||||||
|
end
|
||||||
}
|
}
|
||||||
|
|
||||||
scope :counts_by_state, -> { reorder(nil).group(:state_id).count }
|
scope :counts_by_state, -> { reorder(nil).group(:state_id).count }
|
||||||
|
|
|
||||||
|
|
@ -473,7 +473,6 @@ class User < ApplicationRecord
|
||||||
delegate :webauthn_xid, :webauthn_xid=, to: :user_detail, allow_nil: true
|
delegate :webauthn_xid, :webauthn_xid=, to: :user_detail, allow_nil: true
|
||||||
delegate :pronouns, :pronouns=, to: :user_detail, allow_nil: true
|
delegate :pronouns, :pronouns=, to: :user_detail, allow_nil: true
|
||||||
delegate :pronunciation, :pronunciation=, to: :user_detail, allow_nil: true
|
delegate :pronunciation, :pronunciation=, to: :user_detail, allow_nil: true
|
||||||
delegate :registration_objective, :registration_objective=, to: :user_detail, allow_nil: true
|
|
||||||
delegate :bluesky, :bluesky=, to: :user_detail, allow_nil: true
|
delegate :bluesky, :bluesky=, to: :user_detail, allow_nil: true
|
||||||
delegate :mastodon, :mastodon=, to: :user_detail, allow_nil: true
|
delegate :mastodon, :mastodon=, to: :user_detail, allow_nil: true
|
||||||
delegate :linkedin, :linkedin=, to: :user_detail, allow_nil: true
|
delegate :linkedin, :linkedin=, to: :user_detail, allow_nil: true
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,6 @@
|
||||||
class UserDetail < ApplicationRecord
|
class UserDetail < ApplicationRecord
|
||||||
extend ::Gitlab::Utils::Override
|
extend ::Gitlab::Utils::Override
|
||||||
|
|
||||||
REGISTRATION_OBJECTIVE_PAIRS = { basics: 0, move_repository: 1, code_storage: 2, exploring: 3, ci: 4, other: 5, joining_team: 6 }.freeze
|
|
||||||
|
|
||||||
belongs_to :user
|
belongs_to :user
|
||||||
belongs_to :bot_namespace, class_name: 'Namespace', optional: true, inverse_of: :bot_user_details
|
belongs_to :bot_namespace, class_name: 'Namespace', optional: true, inverse_of: :bot_user_details
|
||||||
|
|
||||||
|
|
@ -15,6 +13,8 @@ class UserDetail < ApplicationRecord
|
||||||
|
|
||||||
validate :bot_namespace_user_type, if: :bot_namespace_id_changed?
|
validate :bot_namespace_user_type, if: :bot_namespace_id_changed?
|
||||||
|
|
||||||
|
ignore_column :registration_objective, remove_after: '2025-07-17', remove_with: '18.2'
|
||||||
|
|
||||||
DEFAULT_FIELD_LENGTH = 500
|
DEFAULT_FIELD_LENGTH = 500
|
||||||
|
|
||||||
# specification for bluesky identifier https://web.plc.directory/spec/v0.1/did-plc
|
# specification for bluesky identifier https://web.plc.directory/spec/v0.1/did-plc
|
||||||
|
|
@ -56,8 +56,6 @@ class UserDetail < ApplicationRecord
|
||||||
before_validation :sanitize_attrs
|
before_validation :sanitize_attrs
|
||||||
before_save :prevent_nil_fields
|
before_save :prevent_nil_fields
|
||||||
|
|
||||||
enum :registration_objective, REGISTRATION_OBJECTIVE_PAIRS, suffix: true
|
|
||||||
|
|
||||||
def sanitize_attrs
|
def sanitize_attrs
|
||||||
%i[bluesky discord linkedin mastodon skype twitter website_url].each do |attr|
|
%i[bluesky discord linkedin mastodon skype twitter website_url].each do |attr|
|
||||||
value = self[attr]
|
value = self[attr]
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
---
|
||||||
|
name: optimize_issues_banned_users_query
|
||||||
|
description: Optimize banned user clause for issue list queries
|
||||||
|
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/394980
|
||||||
|
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/189141
|
||||||
|
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/537923
|
||||||
|
milestone: '18.0'
|
||||||
|
group: group::project management
|
||||||
|
type: gitlab_com_derisk
|
||||||
|
default_enabled: false
|
||||||
|
|
@ -35,6 +35,10 @@ approval_merge_request_rules:
|
||||||
- table: approval_policy_rules
|
- table: approval_policy_rules
|
||||||
column: approval_policy_rule_id
|
column: approval_policy_rule_id
|
||||||
on_delete: async_nullify
|
on_delete: async_nullify
|
||||||
|
ci_build_needs:
|
||||||
|
- table: projects
|
||||||
|
column: project_id
|
||||||
|
on_delete: async_delete
|
||||||
ci_build_report_results:
|
ci_build_report_results:
|
||||||
- table: projects
|
- table: projects
|
||||||
column: project_id
|
column: project_id
|
||||||
|
|
|
||||||
|
|
@ -8,15 +8,6 @@ description: Dependencies for a specific CI/CD job.
|
||||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/31328
|
introduced_by_url: https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/31328
|
||||||
milestone: '12.2'
|
milestone: '12.2'
|
||||||
gitlab_schema: gitlab_ci
|
gitlab_schema: gitlab_ci
|
||||||
desired_sharding_key:
|
sharding_key:
|
||||||
project_id:
|
project_id: projects
|
||||||
references: projects
|
|
||||||
backfill_via:
|
|
||||||
parent:
|
|
||||||
foreign_key: build_id
|
|
||||||
table: p_ci_builds
|
|
||||||
sharding_key: project_id
|
|
||||||
belongs_to: build
|
|
||||||
foreign_key_name: fk_rails_3cf221d4ed_p
|
|
||||||
desired_sharding_key_migration_job_name: BackfillCiBuildNeedsProjectId
|
|
||||||
table_size: over_limit
|
table_size: over_limit
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AddAuthorEmailToSshSignatures < Gitlab::Database::Migration[2.2]
|
||||||
|
disable_ddl_transaction!
|
||||||
|
milestone '18.0'
|
||||||
|
|
||||||
|
# rubocop:disable Migration/AddLimitToTextColumns -- limit is added in a separate migration 20250425111203
|
||||||
|
def up
|
||||||
|
add_column :ssh_signatures, :author_email, :text
|
||||||
|
end
|
||||||
|
# rubocop:enable Migration/AddLimitToTextColumns
|
||||||
|
|
||||||
|
def down
|
||||||
|
remove_column :ssh_signatures, :author_email if column_exists?(:ssh_signatures, :author_email)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AddAuthorEmailLimitToSshSignatures < Gitlab::Database::Migration[2.2]
|
||||||
|
disable_ddl_transaction!
|
||||||
|
milestone '18.0'
|
||||||
|
|
||||||
|
def up
|
||||||
|
add_text_limit :ssh_signatures, :author_email, 255
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
remove_text_limit :ssh_signatures, :author_email
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class ValidateCiBuildNeedsProjectIdNotNull < Gitlab::Database::Migration[2.2]
|
||||||
|
milestone '18.0'
|
||||||
|
|
||||||
|
def up
|
||||||
|
validate_not_null_constraint :ci_build_needs, :project_id, constraint_name: 'check_4fab85ecdc'
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
# no-op
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
49bb1fbe7f7055a16c167199fe7c5c1dd62e0c46e68f50dff05f2470b22241f6
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
a07dd290fe3abc6c4e74f41ec0a83218b4ef8cc8c83a99e0da820fce11c7c732
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
3672a1886f77ed6e9072ef2e4bb2f1b00f87e1b9f7c7289bc930db1f0f4e376d
|
||||||
|
|
@ -10912,7 +10912,8 @@ CREATE TABLE ci_build_needs (
|
||||||
build_id bigint NOT NULL,
|
build_id bigint NOT NULL,
|
||||||
partition_id bigint NOT NULL,
|
partition_id bigint NOT NULL,
|
||||||
id bigint NOT NULL,
|
id bigint NOT NULL,
|
||||||
project_id bigint
|
project_id bigint,
|
||||||
|
CONSTRAINT check_4fab85ecdc CHECK ((project_id IS NOT NULL))
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE SEQUENCE ci_build_needs_id_seq
|
CREATE SEQUENCE ci_build_needs_id_seq
|
||||||
|
|
@ -23245,7 +23246,9 @@ CREATE TABLE ssh_signatures (
|
||||||
verification_status smallint DEFAULT 0 NOT NULL,
|
verification_status smallint DEFAULT 0 NOT NULL,
|
||||||
commit_sha bytea NOT NULL,
|
commit_sha bytea NOT NULL,
|
||||||
user_id bigint,
|
user_id bigint,
|
||||||
key_fingerprint_sha256 bytea
|
key_fingerprint_sha256 bytea,
|
||||||
|
author_email text,
|
||||||
|
CONSTRAINT check_5ff707c7f9 CHECK ((char_length(author_email) <= 255))
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE SEQUENCE ssh_signatures_id_seq
|
CREATE SEQUENCE ssh_signatures_id_seq
|
||||||
|
|
@ -29140,9 +29143,6 @@ ALTER TABLE security_scans
|
||||||
ALTER TABLE vulnerability_scanners
|
ALTER TABLE vulnerability_scanners
|
||||||
ADD CONSTRAINT check_37608c9db5 CHECK ((char_length(vendor) <= 255)) NOT VALID;
|
ADD CONSTRAINT check_37608c9db5 CHECK ((char_length(vendor) <= 255)) NOT VALID;
|
||||||
|
|
||||||
ALTER TABLE ci_build_needs
|
|
||||||
ADD CONSTRAINT check_4fab85ecdc CHECK ((project_id IS NOT NULL)) NOT VALID;
|
|
||||||
|
|
||||||
ALTER TABLE ONLY instance_type_ci_runners
|
ALTER TABLE ONLY instance_type_ci_runners
|
||||||
ADD CONSTRAINT check_5c34a3c1db UNIQUE (id);
|
ADD CONSTRAINT check_5c34a3c1db UNIQUE (id);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -165,7 +165,7 @@ After you sign in to Switchboard, follow these steps to create your instance:
|
||||||
|
|
||||||
- **Reference architecture**: The maximum number of users allowed in your instance. For more information, see [availability and scalability](../../../subscriptions/gitlab_dedicated/data_residency_and_high_availability.md#availability-and-scalability). For example, up to 3,000 users.
|
- **Reference architecture**: The maximum number of users allowed in your instance. For more information, see [availability and scalability](../../../subscriptions/gitlab_dedicated/data_residency_and_high_availability.md#availability-and-scalability). For example, up to 3,000 users.
|
||||||
|
|
||||||
- **Total repository capacity**: The total storage space available for all repositories in your instance. For example, 16 GB. This setting cannot be reduced after you create your instance. You can increase storage capacity later if needed.
|
- **Total repository capacity**: The total storage space available for all repositories in your instance. For example, 16 GB. This setting cannot be reduced after you create your instance. You can increase storage capacity later if needed. For more information about how storage is calculated for GitLab Dedicated, see [GitLab Dedicated storage types](storage_types.md).
|
||||||
|
|
||||||
If you need to change either of these values, [submit a support ticket](https://support.gitlab.com/hc/en-us/requests/new?ticket_form_id=4414917877650).
|
If you need to change either of these values, [submit a support ticket](https://support.gitlab.com/hc/en-us/requests/new?ticket_form_id=4414917877650).
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ The **Overview** page displays:
|
||||||
The top section shows important information about your tenant, including:
|
The top section shows important information about your tenant, including:
|
||||||
|
|
||||||
- Tenant name and URL
|
- Tenant name and URL
|
||||||
- Total Git repository capacity
|
- [Total Git repository capacity](create_instance/storage_types.md#view-repository-storage-per-gitaly-node)
|
||||||
- Current GitLab version
|
- Current GitLab version
|
||||||
- Reference architecture
|
- Reference architecture
|
||||||
- Maintenance window
|
- Maintenance window
|
||||||
|
|
|
||||||
|
|
@ -93,8 +93,8 @@ Test the regex patterns carefully. Tool output formats can change over time, and
|
||||||
| Tool | Command | Regex pattern |
|
| Tool | Command | Regex pattern |
|
||||||
|------|--------------------------------------|---------------|
|
|------|--------------------------------------|---------------|
|
||||||
| tap | `tap --coverage-report=text-summary` | `/^Statements\s*:\s*([^%]+)/` |
|
| tap | `tap --coverage-report=text-summary` | `/^Statements\s*:\s*([^%]+)/` |
|
||||||
| nyc | `nyc npm test` | `/All files[^\|]*\|[^\|]*\s+([\d\.]+)/` |
|
| nyc | `nyc npm test` | `/All files[^\|]*\\|[^\|]*\s+([\d\.]+)/` |
|
||||||
| jest | `jest --ci --coverage` | `/All files[^\|]*\|[^\|]*\s+([\d\.]+)/` |
|
| jest | `jest --ci --coverage` | `/All files[^\|]*\\|[^\|]*\s+([\d\.]+)/` |
|
||||||
|
|
||||||
{{< /tab >}}
|
{{< /tab >}}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -64,12 +64,12 @@ with the deployed staging AI gateway. To do this:
|
||||||
|
|
||||||
### Setup instructions to use GDK with the Code Suggestions Add-on
|
### Setup instructions to use GDK with the Code Suggestions Add-on
|
||||||
|
|
||||||
**Option 1 - Recommended**
|
#### Option 1 - Recommended
|
||||||
|
|
||||||
1. Ensure that you have a [GitLab Team Member License](https://handbook.gitlab.com/handbook/engineering/developer-onboarding/#working-on-gitlab-ee-developer-licenses) and that it is [activated](../../administration/license_file.md).
|
1. Ensure that you have a [GitLab Team Member License](https://handbook.gitlab.com/handbook/engineering/developer-onboarding/#working-on-gitlab-ee-developer-licenses) and that it is [activated](../../administration/license_file.md).
|
||||||
1. Follow the [Setup and Run GDK](_index.md#set-up-and-run-gdk) guide under the AI Features doc.
|
1. Follow the [Setup and Run GDK](_index.md#set-up-and-run-gdk) guide under the AI Features doc.
|
||||||
|
|
||||||
**Option 2**
|
#### Option 2
|
||||||
|
|
||||||
You can set up Duo on your GDK by going through CustomersDot. This is a more complex process, but it more accurately reflects the GitLab Self-Managed setup of our customers.
|
You can set up Duo on your GDK by going through CustomersDot. This is a more complex process, but it more accurately reflects the GitLab Self-Managed setup of our customers.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -222,17 +222,6 @@ Optionally, the context can contain:
|
||||||
- `namespace`. If not provided, `project.namespace` will be used (if `project` is available).
|
- `namespace`. If not provided, `project.namespace` will be used (if `project` is available).
|
||||||
- `category`
|
- `category`
|
||||||
- `additional_properties`
|
- `additional_properties`
|
||||||
- `event_attribute_overrides` - is used when its necessary to override the attributes available in parent context. For example:
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
let(:event) { 'create_new_issue' }
|
|
||||||
|
|
||||||
it_behaves_like 'internal event tracking' do
|
|
||||||
let(:event_attribute_overrides) { { event: 'create_new_milestone'} }
|
|
||||||
|
|
||||||
subject(:service_action) { described_class.new(issue).save }
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
If present in the context, the following legacy options will be respected by the shared example but are discouraged:
|
If present in the context, the following legacy options will be respected by the shared example but are discouraged:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ For each of the vulnerabilities listed in this document, AppSec aims to have a S
|
||||||
| Guideline | Rule | Status |
|
| Guideline | Rule | Status |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| [Regular Expressions](#regular-expressions-guidelines) | [1](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/blob/main/secure-coding-guidelines/ruby/ruby_insecure_regex.yml) | ✅ |
|
| [Regular Expressions](#regular-expressions-guidelines) | [1](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/blob/main/secure-coding-guidelines/ruby/ruby_insecure_regex.yml) | ✅ |
|
||||||
| [ReDOS](#denial-of-service-redos--catastrophic-backtracking) | [1](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/blob/main/secure-coding-guidelines/ruby/ruby_redos_1.yml), [2](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/blob/main/secure-coding-guidelines/ruby/ruby_redos_2.yml) | ✅ |
|
| [ReDOS](#denial-of-service-redos--catastrophic-backtracking) | [1](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/blob/main/secure-coding-guidelines/ruby/ruby_redos_1.yml), [2](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/blob/main/secure-coding-guidelines/ruby/ruby_redos_2.yml), [3](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/merge_requests/59#note_2443657926) | ✅ |
|
||||||
| [JWT](#json-web-tokens-jwt) | Pending | ❌ |
|
| [JWT](#json-web-tokens-jwt) | Pending | ❌ |
|
||||||
| [SSRF](#server-side-request-forgery-ssrf) | [1](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/blob/main/secure-coding-guidelines/ruby/ruby_insecure_url-1.yml), [2](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/blob/main/secure-coding-guidelines/ruby/ruby_insecure_http.yml?ref_type=heads) | ✅ |
|
| [SSRF](#server-side-request-forgery-ssrf) | [1](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/blob/main/secure-coding-guidelines/ruby/ruby_insecure_url-1.yml), [2](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/blob/main/secure-coding-guidelines/ruby/ruby_insecure_http.yml?ref_type=heads) | ✅ |
|
||||||
| [XSS](#xss-guidelines) | [1](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/blob/main/secure-coding-guidelines/ruby/ruby_xss_redirect.yml), [2](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/blob/main/secure-coding-guidelines/ruby/ruby_xss_html_safe.yml) | ✅ |
|
| [XSS](#xss-guidelines) | [1](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/blob/main/secure-coding-guidelines/ruby/ruby_xss_redirect.yml), [2](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/blob/main/secure-coding-guidelines/ruby/ruby_xss_html_safe.yml) | ✅ |
|
||||||
|
|
@ -301,6 +301,37 @@ For other regular expressions, here are a few guidelines:
|
||||||
|
|
||||||
Go's [`regexp`](https://pkg.go.dev/regexp) package uses `re2` and isn't vulnerable to backtracking issues.
|
Go's [`regexp`](https://pkg.go.dev/regexp) package uses `re2` and isn't vulnerable to backtracking issues.
|
||||||
|
|
||||||
|
#### Python Regular Expression Denial of Service (ReDoS) Prevention
|
||||||
|
|
||||||
|
Python offers three main regular expression libraries:
|
||||||
|
|
||||||
|
| Library | Security | Notes |
|
||||||
|
|---------|---------------------|-----------------------------------------------------------------------|
|
||||||
|
| `re` | Vulnerable to ReDoS | Built-in library. Must use timeout parameter. |
|
||||||
|
| `regex` | Vulnerable to ReDoS | Third-party library with extended features. Must use timeout parameter. |
|
||||||
|
| `re2` | Secure by default | Wrapper for the Google RE2 engine. Prevents backtracking by design. |
|
||||||
|
|
||||||
|
Both `re` and `regex` use backtracking algorithms that can cause exponential execution time with certain patterns.
|
||||||
|
|
||||||
|
```python
|
||||||
|
evil_input = 'a' * 30 + '!'
|
||||||
|
|
||||||
|
# Vulnerable - can cause exponential execution time with nested quantifiers
|
||||||
|
# 30 'a's -> ~30 seconds
|
||||||
|
# 31 'a's -> ~60 seconds
|
||||||
|
re.match(r'^(a+)+$', evil_input)
|
||||||
|
regex.match(r'^(a|aa)+$', evil_input)
|
||||||
|
|
||||||
|
# Secure - adds timeout to limit execution time
|
||||||
|
re.match(r'^(a+)+$', evil_input, timeout=1.0)
|
||||||
|
regex.match(r'^(a|aa)+$', evil_input, timeout=1.0)
|
||||||
|
|
||||||
|
# Preferred - re2 prevents catastrophic backtracking by design
|
||||||
|
re2.match(r'^(a+)+$', evil_input)
|
||||||
|
```
|
||||||
|
|
||||||
|
When working with regular expressions in Python, use `re2` when possible or always include timeouts with `re` and `regex`.
|
||||||
|
|
||||||
### Further Links
|
### Further Links
|
||||||
|
|
||||||
- [Rubular](https://rubular.com/) is a nice online tool to fiddle with Ruby Regexps.
|
- [Rubular](https://rubular.com/) is a nice online tool to fiddle with Ruby Regexps.
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,8 @@ Workflow:
|
||||||
For a click-through demo, see [GitLab Duo Workflow](https://gitlab.navattic.com/duo-workflow).
|
For a click-through demo, see [GitLab Duo Workflow](https://gitlab.navattic.com/duo-workflow).
|
||||||
<!-- Demo published on 2025-03-18 -->
|
<!-- Demo published on 2025-03-18 -->
|
||||||
|
|
||||||
|
For an overview, watch <i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [Enhancing your quality assurance with GitLab Duo Workflow](https://youtu.be/Tuj7TgqY81Q?si=IbxaKv7IhAHYnHkN). <!-- Video published on 2025-03-20-->
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
Before you can use Workflow, you must:
|
Before you can use Workflow, you must:
|
||||||
|
|
@ -183,3 +185,7 @@ On your GitLab Self-Managed instance, you can view these events on the
|
||||||
Workflow is a private beta and your feedback is crucial to improve it for you and others.
|
Workflow is a private beta and your feedback is crucial to improve it for you and others.
|
||||||
To report issues or suggest improvements,
|
To report issues or suggest improvements,
|
||||||
[complete this survey](https://gitlab.fra1.qualtrics.com/jfe/form/SV_9GmCPTV7oH9KNuu).
|
[complete this survey](https://gitlab.fra1.qualtrics.com/jfe/form/SV_9GmCPTV7oH9KNuu).
|
||||||
|
|
||||||
|
## Related topics
|
||||||
|
|
||||||
|
- [Use GitLab Duo Workflow to improve application quality assurance](https://about.gitlab.com/blog/2025/04/10/use-gitlab-duo-workflow-to-improve-application-quality-assurance/)
|
||||||
|
|
|
||||||
|
|
@ -77,12 +77,6 @@ To add an emoji reaction to a comment or description:
|
||||||
To use them in a text box, type the filename between two colons.
|
To use them in a text box, type the filename between two colons.
|
||||||
For example, `:thank-you:`.
|
For example, `:thank-you:`.
|
||||||
|
|
||||||
You can upload custom emoji to a GitLab instance with the GraphQL API.
|
|
||||||
For more information, see [Use custom emoji with GraphQL](../api/graphql/custom_emoji.md).
|
|
||||||
|
|
||||||
For a list of custom emoji available for GitLab.com, see
|
|
||||||
[the `custom_emoji` project](https://gitlab.com/custom_emoji/custom_emoji/-/tree/main/img).
|
|
||||||
|
|
||||||
### Upload custom emoji to a group
|
### Upload custom emoji to a group
|
||||||
|
|
||||||
{{< history >}}
|
{{< history >}}
|
||||||
|
|
|
||||||
|
|
@ -164,7 +164,7 @@ Code Suggestions is aware of the context you're working in.
|
||||||
| [Open tab files](#using-open-files-as-context) | Files open in tabs in your IDE. These files give GitLab Duo more information about the standards and practices in your code project. | Optional, but on by default. |
|
| [Open tab files](#using-open-files-as-context) | Files open in tabs in your IDE. These files give GitLab Duo more information about the standards and practices in your code project. | Optional, but on by default. |
|
||||||
| [Imported files](#using-imported-files-as-context) | Files imported in the current opened file. These imported files give GitLab Duo more information about the classes and methods used in the current file. | Optional and off by default. |
|
| [Imported files](#using-imported-files-as-context) | Files imported in the current opened file. These imported files give GitLab Duo more information about the classes and methods used in the current file. | Optional and off by default. |
|
||||||
|
|
||||||
**Footnotes:**
|
Footnotes:
|
||||||
|
|
||||||
1. Code completion is aware of all [supported languages](supported_extensions.md#supported-languages-by-ide).
|
1. Code completion is aware of all [supported languages](supported_extensions.md#supported-languages-by-ide).
|
||||||
Code generation is aware of files in these languages only:
|
Code generation is aware of files in these languages only:
|
||||||
|
|
@ -321,10 +321,10 @@ When using Code Suggestions, [code review best practice](../../../../development
|
||||||
|
|
||||||
To learn about the code that builds the prompt, see these files:
|
To learn about the code that builds the prompt, see these files:
|
||||||
|
|
||||||
- **Code generation**:
|
- Code generation:
|
||||||
[`ee/lib/api/code_suggestions.rb`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/api/code_suggestions.rb#L76)
|
[`ee/lib/api/code_suggestions.rb`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/api/code_suggestions.rb#L76)
|
||||||
in the `gitlab` repository.
|
in the `gitlab` repository.
|
||||||
- **Code completion**:
|
- Code completion:
|
||||||
[`ai_gateway/code_suggestions/processing/completions.py`](https://gitlab.com/gitlab-org/modelops/applied-ml/code-suggestions/ai-assist/-/blob/fcb3f485a8f047a86a8166aad81f93b6d82106a7/ai_gateway/code_suggestions/processing/completions.py#L273)
|
[`ai_gateway/code_suggestions/processing/completions.py`](https://gitlab.com/gitlab-org/modelops/applied-ml/code-suggestions/ai-assist/-/blob/fcb3f485a8f047a86a8166aad81f93b6d82106a7/ai_gateway/code_suggestions/processing/completions.py#L273)
|
||||||
in the `modelops` repository.
|
in the `modelops` repository.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,7 @@ The Repository X-Ray searches a maximum of two directory levels from the reposit
|
||||||
| Python | Poetry | `poetry.lock`, `pyproject.toml` | 17.5 or later |
|
| Python | Poetry | `poetry.lock`, `pyproject.toml` | 17.5 or later |
|
||||||
| Ruby | RubyGems | `Gemfile.lock` | 17.4 or later |
|
| Ruby | RubyGems | `Gemfile.lock` | 17.4 or later |
|
||||||
|
|
||||||
**Footnotes**:
|
Footnotes:
|
||||||
|
|
||||||
1. For Python Pip, all configuration files matching the `*requirements*.txt` glob pattern are processed.
|
1. For Python Pip, all configuration files matching the `*requirements*.txt` glob pattern are processed.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -156,7 +156,7 @@ To do this:
|
||||||
|
|
||||||
1. Find your desired language in the list of
|
1. Find your desired language in the list of
|
||||||
[language identifiers](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocumentItem).
|
[language identifiers](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocumentItem).
|
||||||
You need the **Identifier** for your languages in a later step.
|
You need the identifier for your languages in a later step.
|
||||||
1. In your IDE, on the top bar, select your IDE name, then select **Settings**.
|
1. In your IDE, on the top bar, select your IDE name, then select **Settings**.
|
||||||
1. On the left sidebar, select **Tools > GitLab Duo**.
|
1. On the left sidebar, select **Tools > GitLab Duo**.
|
||||||
1. Under **Code Suggestions Enabled Languages > Additional languages**, add the identifier for each language
|
1. Under **Code Suggestions Enabled Languages > Additional languages**, add the identifier for each language
|
||||||
|
|
|
||||||
|
|
@ -191,7 +191,7 @@ For non-Code Suggestions troubleshooting for Microsoft Visual Studio, see
|
||||||
|
|
||||||
### IntelliCode is missing
|
### IntelliCode is missing
|
||||||
|
|
||||||
Code Suggestions requires the **IntelliCode** component of Visual Studio. If the component
|
Code Suggestions requires the IntelliCode component of Visual Studio. If the component
|
||||||
is missing, you might see an error like this when you start Visual Studio:
|
is missing, you might see an error like this when you start Visual Studio:
|
||||||
|
|
||||||
```plaintext
|
```plaintext
|
||||||
|
|
@ -211,7 +211,7 @@ but found 0 after applying applicable constraints.
|
||||||
[...]
|
[...]
|
||||||
```
|
```
|
||||||
|
|
||||||
To fix this problem, install the **IntelliCode** component:
|
To fix this problem, install the IntelliCode component:
|
||||||
|
|
||||||
1. In the Windows start menu, search for the **Visual Studio Installer** and open it.
|
1. In the Windows start menu, search for the **Visual Studio Installer** and open it.
|
||||||
1. Select your Visual Studio instance, then select **Modify**.
|
1. Select your Visual Studio instance, then select **Modify**.
|
||||||
|
|
|
||||||
|
|
@ -11629,7 +11629,7 @@ msgstr ""
|
||||||
msgid "CICD|Are you sure you want to remove %{namespace} from the job token allowlist? This action cannot be undone."
|
msgid "CICD|Are you sure you want to remove %{namespace} from the job token allowlist? This action cannot be undone."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "CICD|Authentication events from the last 30 days. %{linkStart}Learn more.%{linkEnd}"
|
msgid "CICD|Authentication events using the job token. %{linkStart}Learn more.%{linkEnd}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "CICD|Authentication log"
|
msgid "CICD|Authentication log"
|
||||||
|
|
@ -11728,7 +11728,7 @@ msgstr ""
|
||||||
msgid "CICD|Maintainer"
|
msgid "CICD|Maintainer"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "CICD|No authentication events in the last 30 days."
|
msgid "CICD|No authentication events to display."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "CICD|No resources selected (minimal access only)"
|
msgid "CICD|No resources selected (minimal access only)"
|
||||||
|
|
@ -28487,6 +28487,9 @@ msgstr ""
|
||||||
msgid "GlobalSearch|Groups"
|
msgid "GlobalSearch|Groups"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "GlobalSearch|Groups I'm a member of"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "GlobalSearch|Help"
|
msgid "GlobalSearch|Help"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
@ -28598,6 +28601,9 @@ msgstr ""
|
||||||
msgid "GlobalSearch|Projects"
|
msgid "GlobalSearch|Projects"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "GlobalSearch|Projects I'm a member of"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "GlobalSearch|Projects not indexed"
|
msgid "GlobalSearch|Projects not indexed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
@ -39502,9 +39508,6 @@ msgstr ""
|
||||||
msgid "NamespaceStorageSize|If %{namespace_name} exceeds the %{storage_docs_link_start}storage quota%{link_end}, your ability to write new data to this namespace will be restricted. %{read_only_link_start}Which actions become restricted?%{link_end}"
|
msgid "NamespaceStorageSize|If %{namespace_name} exceeds the %{storage_docs_link_start}storage quota%{link_end}, your ability to write new data to this namespace will be restricted. %{read_only_link_start}Which actions become restricted?%{link_end}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "NamespaceStorageSize|If a project reaches 100%% of the %{storage_docs_link_start}storage quota%{link_end} (%{free_size_limit}) the project will be in a read-only state, and you won't be able to push to your repository or add large files."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "NamespaceStorageSize|To manage your usage and prevent your projects from being placed in a read-only state, you should immediately %{manage_storage_link_start}reduce storage%{link_end}, or %{support_link_start}contact support%{link_end} to help you manage your usage."
|
msgid "NamespaceStorageSize|To manage your usage and prevent your projects from being placed in a read-only state, you should immediately %{manage_storage_link_start}reduce storage%{link_end}, or %{support_link_start}contact support%{link_end} to help you manage your usage."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
@ -39523,16 +39526,22 @@ msgstr ""
|
||||||
msgid "NamespaceStorageSize|To remove the read-only state %{manage_storage_link_start}manage your storage usage%{link_end}, or contact a user with the %{group_member_link_start}owner role for this namespace%{link_end} and ask them to %{purchase_more_link_start}purchase more storage%{link_end}."
|
msgid "NamespaceStorageSize|To remove the read-only state %{manage_storage_link_start}manage your storage usage%{link_end}, or contact a user with the %{group_member_link_start}owner role for this namespace%{link_end} and ask them to %{purchase_more_link_start}purchase more storage%{link_end}."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "NamespaceStorageSize|To remove the read-only state, reduce git repository and git LFS storage, or %{purchase_more_link_start}purchase more storage%{link_end}."
|
msgid "NamespaceStorageSize|To remove the read-only state contact a user with the %{group_member_link_start}owner role for this namespace%{link_end} and ask them to %{purchase_more_link_start}purchase more storage%{link_end}."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "NamespaceStorageSize|To remove the read-only state, reduce git repository and git LFS storage, or contact a user with the %{group_member_link_start}owner role for this namespace%{link_end} and ask them to %{purchase_more_link_start}purchase more storage%{link_end}."
|
msgid "NamespaceStorageSize|To remove the read-only state, %{manage_storage_link_start}manage your storage usage%{link_end} or %{purchase_more_link_start}purchase more storage%{link_end}."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "NamespaceStorageSize|To remove the read-only state, %{manage_storage_link_start}manage your storage usage%{link_end} or %{support_link_start}contact support%{link_end}."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "NamespaceStorageSize|We've noticed an unusually high storage usage on %{namespace_name}"
|
msgid "NamespaceStorageSize|We've noticed an unusually high storage usage on %{namespace_name}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "NamespaceStorageSize|You have consumed all available %{storage_docs_link_start}storage%{link_end} and you can't push or add large files to projects over the free tier limit (%{free_size_limit})."
|
msgid "NamespaceStorageSize|When a project reaches 100%% of the %{storage_docs_link_start}allocated storage%{link_end} (%{free_size_limit}) it will be placed in a read-only state. You won't be able to push and add large files to your repository."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "NamespaceStorageSize|You have consumed all available %{storage_docs_link_start}storage%{link_end} and can't push or add large files to projects over the included storage (%{free_size_limit})."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "NamespaceStorageSize|You have reached the free storage limit of %{free_size_limit} for %{namespace_name}"
|
msgid "NamespaceStorageSize|You have reached the free storage limit of %{free_size_limit} for %{namespace_name}"
|
||||||
|
|
|
||||||
|
|
@ -53,11 +53,8 @@ module QA
|
||||||
end
|
end
|
||||||
|
|
||||||
def run_unregister_command!
|
def run_unregister_command!
|
||||||
cmd = <<~CMD.tr("\n", ' ')
|
output = shell("docker exec #{@name} sh -c '#{unregister_command}'", mask_secrets: [runner_auth_token])
|
||||||
docker exec --detach #{@name} sh -c "#{unregister_command}"
|
confirm_unregistered(output)
|
||||||
CMD
|
|
||||||
|
|
||||||
shell(cmd, mask_secrets: [runner_auth_token])
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def tags=(tags)
|
def tags=(tags)
|
||||||
|
|
@ -115,13 +112,21 @@ module QA
|
||||||
|
|
||||||
def runner_auth_token
|
def runner_auth_token
|
||||||
runner_list = shell("docker exec #{@name} sh -c 'gitlab-runner list'")
|
runner_list = shell("docker exec #{@name} sh -c 'gitlab-runner list'")
|
||||||
runner_list.match(/Token\e\[0;m=([^ ]+)/)&.[](1)
|
runner_list.match(/Token\e\[0;m=([^ ]+)/)&.[](1) || raise("No token found in runner list output")
|
||||||
end
|
end
|
||||||
|
|
||||||
def unregister_command
|
def unregister_command
|
||||||
"gitlab-runner unregister --url #{@address} --token #{runner_auth_token}"
|
"gitlab-runner unregister --url #{@address} --token #{runner_auth_token}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def unregister_message_pattern
|
||||||
|
/Unregistering runner( manager)? from GitLab succeeded/
|
||||||
|
end
|
||||||
|
|
||||||
|
def confirm_unregistered(output)
|
||||||
|
raise("Failed to unregister. Shell response: #{output}") unless output&.match?(unregister_message_pattern)
|
||||||
|
end
|
||||||
|
|
||||||
# Ping Cloudflare DNS, should fail
|
# Ping Cloudflare DNS, should fail
|
||||||
# Ping Registry, should fail to resolve
|
# Ping Registry, should fail to resolve
|
||||||
def prove_airgap
|
def prove_airgap
|
||||||
|
|
|
||||||
|
|
@ -11,10 +11,6 @@ module QA
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'user unregisters a runner with authentication token',
|
it 'user unregisters a runner with authentication token',
|
||||||
quarantine: {
|
|
||||||
issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/513860',
|
|
||||||
type: :stale
|
|
||||||
},
|
|
||||||
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/510652' do
|
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/510652' do
|
||||||
Flow::Login.sign_in
|
Flow::Login.sign_in
|
||||||
|
|
||||||
|
|
@ -28,16 +24,8 @@ module QA
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# The output of the unregister command is verified inside the GitlabRunner class
|
||||||
runner.unregister!
|
runner.unregister!
|
||||||
|
|
||||||
page.refresh
|
|
||||||
|
|
||||||
Page::Project::Settings::CiCd.perform do |settings|
|
|
||||||
settings.expand_runners_settings do |page|
|
|
||||||
expect(page).to have_content(executor)
|
|
||||||
expect(page).to have_offline_runner
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -202,9 +202,10 @@ module QA
|
||||||
|
|
||||||
describe '#unregister!' do
|
describe '#unregister!' do
|
||||||
let(:run_unregister_command) { subject.send(:run_unregister_command!) }
|
let(:run_unregister_command) { subject.send(:run_unregister_command!) }
|
||||||
|
let(:unregister_message) { 'Unregistering runner manager from GitLab succeeded' }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
allow(subject).to receive(:shell)
|
allow(subject).to receive(:shell).and_return(unregister_message)
|
||||||
|
|
||||||
subject.instance_eval do
|
subject.instance_eval do
|
||||||
def runner_auth_token
|
def runner_auth_token
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
require 'parallel'
|
require 'parallel'
|
||||||
require 'rainbow'
|
require 'rainbow'
|
||||||
|
|
||||||
UNUSED_METHODS = 56
|
UNUSED_METHODS = 52
|
||||||
|
|
||||||
print_output = %w[true 1].include? ENV["REPORT_ALL_UNUSED_METHODS"]
|
print_output = %w[true 1].include? ENV["REPORT_ALL_UNUSED_METHODS"]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,7 @@ RSpec.describe 'Container Registry', :js, feature_category: :container_registry
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'shows the details breadcrumb' do
|
it 'shows the details breadcrumb' do
|
||||||
expect(find_by_testid('breadcrumb-links')).to have_link 'my/image'
|
expect(find_by_testid('breadcrumb-links')).to have_link 'Container registry'
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'shows the image title' do
|
it 'shows the image title' do
|
||||||
|
|
|
||||||
|
|
@ -124,7 +124,7 @@ RSpec.describe 'Container Registry', :js, feature_category: :container_registry
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'shows the details breadcrumb' do
|
it 'shows the details breadcrumb' do
|
||||||
expect(find_by_testid('breadcrumb-links')).to have_link 'my/image'
|
expect(find_by_testid('breadcrumb-links')).to have_link 'Container registry'
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'shows the image title' do
|
it 'shows the image title' do
|
||||||
|
|
|
||||||
|
|
@ -336,15 +336,6 @@ describe('PackagesApp', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calls the appropriate function to set the breadcrumbState', async () => {
|
|
||||||
const { name, version } = packageData();
|
|
||||||
createComponent();
|
|
||||||
|
|
||||||
await waitForPromises();
|
|
||||||
|
|
||||||
expect(breadCrumbState.updateName).toHaveBeenCalledWith(`${name} v${version}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('delete package', () => {
|
describe('delete package', () => {
|
||||||
const originalReferrer = document.referrer;
|
const originalReferrer = document.referrer;
|
||||||
const setReferrer = (value = packageDetailsQuery().data.package.project.name) => {
|
const setReferrer = (value = packageDetailsQuery().data.package.project.name) => {
|
||||||
|
|
|
||||||
|
|
@ -53,16 +53,13 @@ describe('Registry Breadcrumb', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('passes root and details to `items` prop', () => {
|
it('passes root and details to `items` prop', () => {
|
||||||
expect(wrapper.findComponent(GlBreadcrumb).props('items')).toEqual([
|
const breadcrumbItems = wrapper.findComponent(GlBreadcrumb).props('items');
|
||||||
{
|
expect(breadcrumbItems).toHaveLength(2);
|
||||||
text: 'mock name',
|
expect(breadcrumbItems[0]).toEqual({
|
||||||
to: '/',
|
text: 'mock name',
|
||||||
},
|
to: '/',
|
||||||
{
|
});
|
||||||
text: 'mock name',
|
expect(breadcrumbItems[1].href).toBe('/:id');
|
||||||
href: '/details',
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -274,15 +274,15 @@ describe('GlobalSearchAutocompleteItems', () => {
|
||||||
|
|
||||||
describe('tracking', () => {
|
describe('tracking', () => {
|
||||||
it.each`
|
it.each`
|
||||||
action | event
|
action | event
|
||||||
${'Projects'} | ${EVENT_CLICK_PROJECT_RESULT_IN_COMMAND_PALETTE}
|
${"Projects I'm a member of"} | ${EVENT_CLICK_PROJECT_RESULT_IN_COMMAND_PALETTE}
|
||||||
${'Groups'} | ${EVENT_CLICK_GROUP_RESULT_IN_COMMAND_PALETTE}
|
${"Groups I'm a member of"} | ${EVENT_CLICK_GROUP_RESULT_IN_COMMAND_PALETTE}
|
||||||
${'Merge requests'} | ${EVENT_CLICK_MERGE_REQUEST_RESULT_IN_COMMAND_PALETTE}
|
${'Merge requests'} | ${EVENT_CLICK_MERGE_REQUEST_RESULT_IN_COMMAND_PALETTE}
|
||||||
${'Issues'} | ${EVENT_CLICK_ISSUE_RESULT_IN_COMMAND_PALETTE}
|
${'Issues'} | ${EVENT_CLICK_ISSUE_RESULT_IN_COMMAND_PALETTE}
|
||||||
${'Recent issues'} | ${EVENT_CLICK_RECENT_ISSUE_RESULT_IN_COMMAND_PALETTE}
|
${'Recent issues'} | ${EVENT_CLICK_RECENT_ISSUE_RESULT_IN_COMMAND_PALETTE}
|
||||||
${'Recent epics'} | ${EVENT_CLICK_RECENT_EPIC_RESULT_IN_COMMAND_PALETTE}
|
${'Recent epics'} | ${EVENT_CLICK_RECENT_EPIC_RESULT_IN_COMMAND_PALETTE}
|
||||||
${'Recent merge requests'} | ${EVENT_CLICK_RECENT_MERGE_REQUEST_RESULT_IN_COMMAND_PALETTE}
|
${'Recent merge requests'} | ${EVENT_CLICK_RECENT_MERGE_REQUEST_RESULT_IN_COMMAND_PALETTE}
|
||||||
${undefined} | ${EVENT_CLICK_USER_RESULT_IN_COMMAND_PALETTE}
|
${undefined} | ${EVENT_CLICK_USER_RESULT_IN_COMMAND_PALETTE}
|
||||||
`(
|
`(
|
||||||
"triggers tracking event '$event' after emiting action '$action'",
|
"triggers tracking event '$event' after emiting action '$action'",
|
||||||
({ action, event }) => {
|
({ action, event }) => {
|
||||||
|
|
|
||||||
|
|
@ -87,9 +87,7 @@ describe('TokenAccess component', () => {
|
||||||
|
|
||||||
await waitForPromises();
|
await waitForPromises();
|
||||||
|
|
||||||
expect(findCrudComponentBody().text()).toContain(
|
expect(findCrudComponentBody().text()).toContain('No authentication events to display.');
|
||||||
'No authentication events in the last 30 days.',
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('displays a table when data is available', async () => {
|
it('displays a table when data is available', async () => {
|
||||||
|
|
|
||||||
|
|
@ -1222,13 +1222,6 @@ describe('WorkItemDetail component', () => {
|
||||||
describe('work item parent id', () => {
|
describe('work item parent id', () => {
|
||||||
const parentId = 'gid://gitlab/Issue/1';
|
const parentId = 'gid://gitlab/Issue/1';
|
||||||
|
|
||||||
it('passes the `parentWorkItemId` value down to the `WorkItemStickyHeader` component', async () => {
|
|
||||||
createComponent();
|
|
||||||
await waitForPromises();
|
|
||||||
|
|
||||||
expect(findStickyHeader().props('parentId')).toBe(parentId);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('passes the `parentWorkItemId` value down to the `WorkItemActions` component', async () => {
|
it('passes the `parentWorkItemId` value down to the `WorkItemActions` component', async () => {
|
||||||
createComponent();
|
createComponent();
|
||||||
await waitForPromises();
|
await waitForPromises();
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ describe('WorkItemStickyHeader', () => {
|
||||||
discussionLocked = false,
|
discussionLocked = false,
|
||||||
canUpdate = true,
|
canUpdate = true,
|
||||||
features = {},
|
features = {},
|
||||||
parentId = null,
|
|
||||||
movedToWorkItemUrl = null,
|
movedToWorkItemUrl = null,
|
||||||
duplicatedToWorkItemUrl = null,
|
duplicatedToWorkItemUrl = null,
|
||||||
promotedToEpicUrl = null,
|
promotedToEpicUrl = null,
|
||||||
|
|
@ -34,19 +33,12 @@ describe('WorkItemStickyHeader', () => {
|
||||||
duplicatedToWorkItemUrl,
|
duplicatedToWorkItemUrl,
|
||||||
promotedToEpicUrl,
|
promotedToEpicUrl,
|
||||||
}).data.workItem,
|
}).data.workItem,
|
||||||
fullPath: '/test',
|
|
||||||
isStickyHeaderShowing: true,
|
isStickyHeaderShowing: true,
|
||||||
workItemNotificationsSubscribed: true,
|
workItemNotificationsSubscribed: true,
|
||||||
updateInProgress: false,
|
updateInProgress: false,
|
||||||
parentWorkItemConfidentiality: false,
|
|
||||||
showWorkItemCurrentUserTodos: true,
|
showWorkItemCurrentUserTodos: true,
|
||||||
isModal: false,
|
|
||||||
currentUserTodos: [],
|
currentUserTodos: [],
|
||||||
workItemState: STATE_OPEN,
|
workItemState: STATE_OPEN,
|
||||||
isGroup: false,
|
|
||||||
parentId,
|
|
||||||
showSidebar: true,
|
|
||||||
truncationEnabled: true,
|
|
||||||
},
|
},
|
||||||
provide: {
|
provide: {
|
||||||
glFeatures: {
|
glFeatures: {
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,6 @@ RSpec.describe UserDetail, feature_category: :system_access do
|
||||||
it { is_expected.to belong_to(:user) }
|
it { is_expected.to belong_to(:user) }
|
||||||
it { is_expected.to belong_to(:bot_namespace).inverse_of(:bot_user_details) }
|
it { is_expected.to belong_to(:bot_namespace).inverse_of(:bot_user_details) }
|
||||||
|
|
||||||
specify do
|
|
||||||
values = [:basics, :move_repository, :code_storage, :exploring, :ci, :other, :joining_team]
|
|
||||||
is_expected.to define_enum_for(:registration_objective).with_values(values).with_suffix
|
|
||||||
end
|
|
||||||
|
|
||||||
describe 'validations' do
|
describe 'validations' do
|
||||||
context 'for onboarding_status json schema' do
|
context 'for onboarding_status json schema' do
|
||||||
let(:step_url) { '_some_string_' }
|
let(:step_url) { '_some_string_' }
|
||||||
|
|
|
||||||
|
|
@ -135,9 +135,6 @@ RSpec.describe User, feature_category: :user_profile do
|
||||||
it { is_expected.to delegate_method(:bio).to(:user_detail).allow_nil }
|
it { is_expected.to delegate_method(:bio).to(:user_detail).allow_nil }
|
||||||
it { is_expected.to delegate_method(:bio=).to(:user_detail).with_arguments(:args).allow_nil }
|
it { is_expected.to delegate_method(:bio=).to(:user_detail).with_arguments(:args).allow_nil }
|
||||||
|
|
||||||
it { is_expected.to delegate_method(:registration_objective).to(:user_detail).allow_nil }
|
|
||||||
it { is_expected.to delegate_method(:registration_objective=).to(:user_detail).with_arguments(:args).allow_nil }
|
|
||||||
|
|
||||||
it { is_expected.to delegate_method(:discord).to(:user_detail).allow_nil }
|
it { is_expected.to delegate_method(:discord).to(:user_detail).allow_nil }
|
||||||
it { is_expected.to delegate_method(:discord=).to(:user_detail).with_arguments(:args).allow_nil }
|
it { is_expected.to delegate_method(:discord=).to(:user_detail).with_arguments(:args).allow_nil }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -245,12 +245,22 @@ RSpec.describe API::Todos, feature_category: :source_code_management do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when user is a bot' do
|
context 'when user is a bot' do
|
||||||
it_behaves_like 'internal event tracking' do
|
let(:user) { create(:user, :service_account) }
|
||||||
let(:event) { 'request_todos_by_bot_user' }
|
|
||||||
let(:user) { create(:user, :service_account) }
|
it "triggers an internal event" do
|
||||||
let(:additional_properties) { { label: 'user_type', property: user.user_type } }
|
expect { get api('/todos', user) }
|
||||||
let(:event_attribute_overrides) { { project: nil, namespace: nil } }
|
.to trigger_internal_events('request_todos_by_bot_user')
|
||||||
subject(:api_request) { get api('/todos', user) }
|
.with(
|
||||||
|
category: 'InternalEventTracking',
|
||||||
|
user: user,
|
||||||
|
additional_properties: {
|
||||||
|
label: 'user_type',
|
||||||
|
property: user.user_type
|
||||||
|
}
|
||||||
|
).and increment_usage_metrics(
|
||||||
|
'redis_hll_counters.count_distinct_user_id_from_request_todos_by_bot_user_weekly',
|
||||||
|
'redis_hll_counters.count_distinct_user_id_from_request_todos_by_bot_user_monthly'
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@
|
||||||
# - namespace
|
# - namespace
|
||||||
# - category
|
# - category
|
||||||
# - additional_properties
|
# - additional_properties
|
||||||
# - event_attribute_overrides - is used when its necessary to override the attributes available in parent context.
|
|
||||||
#
|
#
|
||||||
# [Legacy] If present in the context, the following will be respected by the shared example but are discouraged:
|
# [Legacy] If present in the context, the following will be respected by the shared example but are discouraged:
|
||||||
# - label
|
# - label
|
||||||
|
|
@ -57,7 +56,7 @@ RSpec.shared_examples 'internal event tracking' do
|
||||||
value: try(:value)
|
value: try(:value)
|
||||||
}.compact
|
}.compact
|
||||||
}
|
}
|
||||||
}.merge(try(:event_attribute_overrides) || {})
|
}
|
||||||
|
|
||||||
expect { subject }
|
expect { subject }
|
||||||
.to trigger_internal_events(event)
|
.to trigger_internal_events(event)
|
||||||
|
|
|
||||||
|
|
@ -512,10 +512,6 @@ RSpec.describe 'Internal Events matchers', :clean_gitlab_redis_shared_state, fea
|
||||||
let(:label) { expected_label }
|
let(:label) { expected_label }
|
||||||
end
|
end
|
||||||
|
|
||||||
it_behaves_like 'internal event tracking' do
|
|
||||||
let(:event_attribute_overrides) { { additional_properties: { label: expected_label } } }
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'with incorrect value being provided in additional_properties.' do
|
context 'with incorrect value being provided in additional_properties.' do
|
||||||
let(:unexpected_label) { 'BAD label value' }
|
let(:unexpected_label) { 'BAD label value' }
|
||||||
|
|
||||||
|
|
@ -531,10 +527,6 @@ RSpec.describe 'Internal Events matchers', :clean_gitlab_redis_shared_state, fea
|
||||||
it_behaves_like 'internal event tracking' do
|
it_behaves_like 'internal event tracking' do
|
||||||
let(:label) { unexpected_label }
|
let(:label) { unexpected_label }
|
||||||
end
|
end
|
||||||
|
|
||||||
it_behaves_like 'internal event tracking' do
|
|
||||||
let(:event_attribute_overrides) { { additional_properties: { label: unexpected_label } } }
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue