Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/boards/boards_util.js9
-rw-r--r--app/assets/javascripts/boards/components/board_add_new_column.vue132
-rw-r--r--app/assets/javascripts/boards/components/board_add_new_column_form.vue10
-rw-r--r--app/assets/javascripts/boards/components/board_add_new_column_trigger.vue11
-rw-r--r--app/assets/javascripts/boards/components/board_app.vue7
-rw-r--r--app/assets/javascripts/boards/components/board_column.vue10
-rw-r--r--app/assets/javascripts/boards/components/board_content.vue49
-rw-r--r--app/assets/javascripts/boards/components/board_settings_sidebar.vue6
-rw-r--r--app/assets/javascripts/boards/components/board_top_bar.vue10
-rw-r--r--app/assets/javascripts/boards/constants.js7
-rw-r--r--app/assets/javascripts/error_tracking/components/stacktrace.vue2
-rw-r--r--app/assets/javascripts/error_tracking/components/stacktrace_entry.vue37
-rw-r--r--app/assets/javascripts/issues/show/components/fields/type.vue2
-rw-r--r--app/assets/javascripts/issues/show/components/form.vue2
-rw-r--r--app/assets/javascripts/issues/show/components/header_actions.vue2
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss2
-rw-r--r--app/assets/stylesheets/page_bundles/login.scss4
-rw-r--r--app/controllers/concerns/membership_actions.rb2
-rw-r--r--app/helpers/auth_helper.rb3
-rw-r--r--app/models/packages/package.rb3
-rw-r--r--app/models/user.rb18
-rw-r--r--app/services/releases/create_service.rb8
-rw-r--r--app/services/releases/links/base_service.rb12
-rw-r--r--app/services/releases/links/params.rb29
-rw-r--r--app/views/shared/issue_type/_details_content.html.haml2
-rw-r--r--config/feature_flags/development/authorize_groups_query_without_column_cache.yml8
-rw-r--r--db/migrate/20230516110414_add_ml_model_max_file_size_to_plan_limits.rb7
-rw-r--r--db/post_migrate/20230515142300_add_unique_index_for_ml_model_packages.rb19
-rw-r--r--db/schema_migrations/202305151423001
-rw-r--r--db/schema_migrations/202305161104141
-rw-r--r--db/structure.sql5
-rw-r--r--doc/administration/auth/ldap/ldap_synchronization.md6
-rw-r--r--doc/api/graphql/reference/index.md1
-rw-r--r--doc/development/testing_guide/end_to_end/running_tests_that_require_special_setup.md8
-rw-r--r--doc/integration/google.md7
-rw-r--r--doc/user/profile/index.md2
-rw-r--r--lib/api/maven_packages.rb10
-rw-r--r--lib/api/release/links.rb12
-rw-r--r--lib/api/releases.rb15
-rw-r--r--lib/error_tracking/stacktrace_builder.rb1
-rw-r--r--package.json2
-rw-r--r--qa/qa/page/main/login.rb8
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/login/login_via_oauth_and_oidc_with_gitlab_as_idp_spec.rb158
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/login/login_via_oidc_with_gitlab_as_idp_spec.rb118
-rw-r--r--spec/controllers/projects/project_members_controller_spec.rb31
-rw-r--r--spec/factories/packages/packages.rb6
-rw-r--r--spec/frontend/boards/components/board_add_new_column_form_spec.js14
-rw-r--r--spec/frontend/boards/components/board_add_new_column_spec.js107
-rw-r--r--spec/frontend/boards/components/board_add_new_column_trigger_spec.js22
-rw-r--r--spec/frontend/boards/components/board_content_spec.js6
-rw-r--r--spec/frontend/boards/components/board_top_bar_spec.js1
-rw-r--r--spec/frontend/boards/mock_data.js36
-rw-r--r--spec/frontend/error_tracking/components/stacktrace_entry_spec.js17
-rw-r--r--spec/frontend/error_tracking/components/stacktrace_spec.js26
-rw-r--r--spec/graphql/types/packages/package_type_enum_spec.rb2
-rw-r--r--spec/lib/error_tracking/stacktrace_builder_spec.rb11
-rw-r--r--spec/lib/generators/gitlab/partitioning/foreign_keys_generator_spec.rb2
-rw-r--r--spec/migrations/finalize_issues_iid_scoping_to_namespace_spec.rb2
-rw-r--r--spec/models/user_spec.rb28
-rw-r--r--spec/requests/api/maven_packages_spec.rb19
-rw-r--r--spec/services/releases/links/params_spec.rb37
-rw-r--r--spec/support/helpers/migrations_helpers.rb34
-rw-r--r--spec/support/shared_examples/services/packages_shared_examples.rb1
-rw-r--r--yarn.lock8
64 files changed, 847 insertions, 331 deletions
diff --git a/app/assets/javascripts/boards/boards_util.js b/app/assets/javascripts/boards/boards_util.js
index 3a22b06c72e..fcd1440841b 100644
--- a/app/assets/javascripts/boards/boards_util.js
+++ b/app/assets/javascripts/boards/boards_util.js
@@ -1,4 +1,4 @@
-import { sortBy, cloneDeep } from 'lodash';
+import { sortBy, cloneDeep, find } from 'lodash';
import {
TYPENAME_BOARD,
TYPENAME_ITERATION,
@@ -318,6 +318,13 @@ export function getBoardQuery(boardType) {
return boardQuery[boardType].query;
}
+export function getListByTypeId(lists, type, id) {
+ // type can be assignee/label/milestone/iteration
+ if (type && id) return find(lists, (l) => l.listType === ListType[type] && l[type]?.id === id);
+
+ return null;
+}
+
export default {
getMilestone,
formatIssue,
diff --git a/app/assets/javascripts/boards/components/board_add_new_column.vue b/app/assets/javascripts/boards/components/board_add_new_column.vue
index 90f7059da86..985b9798b36 100644
--- a/app/assets/javascripts/boards/components/board_add_new_column.vue
+++ b/app/assets/javascripts/boards/components/board_add_new_column.vue
@@ -1,4 +1,6 @@
<script>
+import produce from 'immer';
+import { debounce } from 'lodash';
import {
GlTooltipDirective as GlTooltip,
GlButton,
@@ -6,8 +8,12 @@ import {
GlIcon,
} from '@gitlab/ui';
import { mapActions, mapGetters, mapState } from 'vuex';
+import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
import BoardAddNewColumnForm from '~/boards/components/board_add_new_column_form.vue';
import { __ } from '~/locale';
+import { createListMutations, listsQuery, BoardType, ListType } from 'ee_else_ce/boards/constants';
+import boardLabelsQuery from '../graphql/board_labels.query.graphql';
+import { getListByTypeId } from '../boards_util';
export default {
i18n: {
@@ -23,60 +29,150 @@ export default {
directives: {
GlTooltip,
},
- inject: ['scopedLabelsAvailable'],
+ inject: ['scopedLabelsAvailable', 'issuableType', 'fullPath', 'boardType', 'isApolloBoard'],
+ props: {
+ listQueryVariables: {
+ type: Object,
+ required: true,
+ },
+ boardId: {
+ type: String,
+ required: true,
+ },
+ lists: {
+ type: Object,
+ required: true,
+ },
+ },
data() {
return {
selectedId: null,
selectedLabel: null,
selectedIdValid: true,
+ labelsApollo: [],
+ searchTerm: '',
};
},
+ apollo: {
+ labelsApollo: {
+ query: boardLabelsQuery,
+ variables() {
+ return {
+ fullPath: this.fullPath,
+ searchTerm: this.searchTerm,
+ isGroup: this.boardType === BoardType.group,
+ isProject: this.boardType === BoardType.project,
+ };
+ },
+ update(data) {
+ return data[this.boardType].labels.nodes;
+ },
+ skip() {
+ return !this.isApolloBoard;
+ },
+ },
+ },
computed: {
...mapState(['labels', 'labelsLoading']),
...mapGetters(['getListByLabelId']),
+ labelsToUse() {
+ return this.isApolloBoard ? this.labelsApollo : this.labels;
+ },
+ isLabelsLoading() {
+ return this.isApolloBoard ? this.$apollo.queries.labelsApollo.loading : this.labelsLoading;
+ },
columnForSelected() {
+ if (this.isApolloBoard) {
+ return getListByTypeId(this.lists, ListType.label, this.selectedId);
+ }
return this.getListByLabelId(this.selectedId);
},
items() {
- return (
- this.labels.map((i) => ({
- ...i,
- text: i.title,
- value: i.id,
- })) || []
- );
+ return (this.labelsToUse || []).map((i) => ({
+ ...i,
+ text: i.title,
+ value: i.id,
+ }));
},
},
created() {
- this.filterItems();
+ if (!this.isApolloBoard) {
+ this.filterItems();
+ }
},
methods: {
- ...mapActions(['createList', 'fetchLabels', 'highlightList', 'setAddColumnFormVisibility']),
+ ...mapActions(['createList', 'fetchLabels', 'highlightList']),
+ createListApollo({ labelId }) {
+ return this.$apollo.mutate({
+ mutation: createListMutations[this.issuableType].mutation,
+ variables: {
+ labelId,
+ boardId: this.boardId,
+ },
+ update: (
+ store,
+ {
+ data: {
+ boardListCreate: { list },
+ },
+ },
+ ) => {
+ const sourceData = store.readQuery({
+ query: listsQuery[this.issuableType].query,
+ variables: this.listQueryVariables,
+ });
+ const data = produce(sourceData, (draftData) => {
+ draftData[this.boardType].board.lists.nodes.push(list);
+ });
+ store.writeQuery({
+ query: listsQuery[this.issuableType].query,
+ variables: this.listQueryVariables,
+ data,
+ });
+ this.$emit('highlight-list', list.id);
+ },
+ });
+ },
addList() {
if (!this.selectedLabel) {
this.selectedIdValid = false;
return;
}
- this.setAddColumnFormVisibility(false);
-
if (this.columnForSelected) {
const listId = this.columnForSelected.id;
- this.highlightList(listId);
+ if (this.isApolloBoard) {
+ this.$emit('highlight-list', listId);
+ } else {
+ this.highlightList(listId);
+ }
return;
}
- this.createList({ labelId: this.selectedId });
+ if (this.isApolloBoard) {
+ this.createListApollo({ labelId: this.selectedId });
+ } else {
+ this.createList({ labelId: this.selectedId });
+ }
+
+ this.$emit('setAddColumnFormVisibility', false);
},
filterItems(searchTerm) {
this.fetchLabels(searchTerm);
},
+ onSearch: debounce(function debouncedSearch(searchTerm) {
+ this.searchTerm = searchTerm;
+ if (!this.isApolloBoard) {
+ this.filterItems(searchTerm);
+ }
+ }, DEFAULT_DEBOUNCE_AND_THROTTLE_MS),
+
setSelectedItem(selectedId) {
this.selectedId = selectedId;
- const label = this.labels.find(({ id }) => id === selectedId);
+ const label = this.labelsToUse.find(({ id }) => id === selectedId);
if (!selectedId || !label) {
this.selectedLabel = null;
} else {
@@ -95,8 +191,8 @@ export default {
<template>
<board-add-new-column-form
:selected-id-valid="selectedIdValid"
- @filter-items="filterItems"
@add-list="addList"
+ @setAddColumnFormVisibility="$emit('setAddColumnFormVisibility', $event)"
>
<template #dropdown>
<gl-collapsible-listbox
@@ -104,11 +200,11 @@ export default {
:items="items"
searchable
:search-placeholder="__('Search labels')"
- :searching="labelsLoading"
+ :searching="isLabelsLoading"
:selected="selectedId"
:no-results-text="$options.i18n.noResults"
@select="setSelectedItem"
- @search="filterItems"
+ @search="onSearch"
@hidden="onHide"
>
<template #toggle>
diff --git a/app/assets/javascripts/boards/components/board_add_new_column_form.vue b/app/assets/javascripts/boards/components/board_add_new_column_form.vue
index 259423df07f..419d0b41d69 100644
--- a/app/assets/javascripts/boards/components/board_add_new_column_form.vue
+++ b/app/assets/javascripts/boards/components/board_add_new_column_form.vue
@@ -1,6 +1,5 @@
<script>
import { GlButton, GlFormGroup } from '@gitlab/ui';
-import { mapActions } from 'vuex';
import { __ } from '~/locale';
export default {
@@ -33,7 +32,6 @@ export default {
};
},
methods: {
- ...mapActions(['setAddColumnFormVisibility']),
onSubmit() {
this.$emit('add-list');
},
@@ -83,9 +81,11 @@ export default {
@click="onSubmit"
>{{ $options.i18n.add }}</gl-button
>
- <gl-button data-testid="cancelAddNewColumn" @click="setAddColumnFormVisibility(false)">{{
- $options.i18n.cancel
- }}</gl-button>
+ <gl-button
+ data-testid="cancelAddNewColumn"
+ @click="$emit('setAddColumnFormVisibility', false)"
+ >{{ $options.i18n.cancel }}</gl-button
+ >
</div>
</div>
</div>
diff --git a/app/assets/javascripts/boards/components/board_add_new_column_trigger.vue b/app/assets/javascripts/boards/components/board_add_new_column_trigger.vue
index 14c84d3c4e5..d91c8ab4727 100644
--- a/app/assets/javascripts/boards/components/board_add_new_column_trigger.vue
+++ b/app/assets/javascripts/boards/components/board_add_new_column_trigger.vue
@@ -1,6 +1,5 @@
<script>
import { GlButton, GlTooltipDirective } from '@gitlab/ui';
-import { mapActions, mapState } from 'vuex';
import { __ } from '~/locale';
import Tracking from '~/tracking';
@@ -12,16 +11,20 @@ export default {
GlTooltip: GlTooltipDirective,
},
mixins: [Tracking.mixin()],
+ props: {
+ isNewListShowing: {
+ type: Boolean,
+ required: true,
+ },
+ },
computed: {
- ...mapState({ isNewListShowing: ({ addColumnForm }) => addColumnForm.visible }),
tooltip() {
return this.isNewListShowing ? __('The list creation wizard is already open') : '';
},
},
methods: {
- ...mapActions(['setAddColumnFormVisibility']),
handleClick() {
- this.setAddColumnFormVisibility(true);
+ this.$emit('setAddColumnFormVisibility', true);
this.track('click_button', { label: 'create_list' });
},
},
diff --git a/app/assets/javascripts/boards/components/board_app.vue b/app/assets/javascripts/boards/components/board_app.vue
index 3a247819850..0b9243c07c5 100644
--- a/app/assets/javascripts/boards/components/board_app.vue
+++ b/app/assets/javascripts/boards/components/board_app.vue
@@ -35,6 +35,7 @@ export default {
activeListId: '',
boardId: this.initialBoardId,
filterParams: { ...this.initialFilterParams },
+ addColumnFormVisible: false,
isShowingEpicsSwimlanes: Boolean(queryToObject(window.location.search).group_by),
apolloError: null,
};
@@ -79,6 +80,7 @@ export default {
computed: {
...mapGetters(['isSidebarOpen']),
listQueryVariables() {
+ if (this.filterParams.groupBy) delete this.filterParams.groupBy;
return {
...(this.isIssueBoard && {
isGroup: this.isGroupBoard,
@@ -129,19 +131,24 @@ export default {
<div class="boards-app gl-relative" :class="{ 'is-compact': isAnySidebarOpen }">
<board-top-bar
:board-id="boardId"
+ :add-column-form-visible="addColumnFormVisible"
:is-swimlanes-on="isSwimlanesOn"
@switchBoard="switchBoard"
@setFilters="setFilters"
+ @setAddColumnFormVisibility="addColumnFormVisible = $event"
@toggleSwimlanes="isShowingEpicsSwimlanes = $event"
/>
<board-content
v-if="!isApolloBoard || boardListsApollo"
:board-id="boardId"
+ :add-column-form-visible="addColumnFormVisible"
:is-swimlanes-on="isSwimlanesOn"
:filter-params="filterParams"
:board-lists-apollo="boardListsApollo"
:apollo-error="apolloError"
+ :list-query-variables="listQueryVariables"
@setActiveList="setActiveId"
+ @setAddColumnFormVisibility="addColumnFormVisible = $event"
/>
<board-settings-sidebar
v-if="!isApolloBoard || activeList"
diff --git a/app/assets/javascripts/boards/components/board_column.vue b/app/assets/javascripts/boards/components/board_column.vue
index b2054d76e95..2ee0b4593d6 100644
--- a/app/assets/javascripts/boards/components/board_column.vue
+++ b/app/assets/javascripts/boards/components/board_column.vue
@@ -24,12 +24,20 @@ export default {
type: Object,
required: true,
},
+ highlightedListsApollo: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
},
computed: {
...mapState(['filterParams', 'highlightedLists']),
...mapGetters(['getBoardItemsByList']),
+ highlightedListsToUse() {
+ return this.isApolloBoard ? this.highlightedListsApollo : this.highlightedLists;
+ },
highlighted() {
- return this.highlightedLists.includes(this.list.id);
+ return this.highlightedListsToUse.includes(this.list.id);
},
listItems() {
return this.isApolloBoard ? [] : this.getBoardItemsByList(this.list.id);
diff --git a/app/assets/javascripts/boards/components/board_content.vue b/app/assets/javascripts/boards/components/board_content.vue
index 9416cbf1884..ba4e3af79b5 100644
--- a/app/assets/javascripts/boards/components/board_content.vue
+++ b/app/assets/javascripts/boards/components/board_content.vue
@@ -6,7 +6,7 @@ import { mapState, mapActions } from 'vuex';
import eventHub from '~/boards/eventhub';
import BoardAddNewColumn from 'ee_else_ce/boards/components/board_add_new_column.vue';
import { defaultSortableOptions } from '~/sortable/constants';
-import { DraggableItemTypes } from 'ee_else_ce/boards/constants';
+import { DraggableItemTypes, flashAnimationDuration } from 'ee_else_ce/boards/constants';
import BoardColumn from './board_column.vue';
export default {
@@ -44,16 +44,25 @@ export default {
required: false,
default: null,
},
+ listQueryVariables: {
+ type: Object,
+ required: true,
+ },
+ addColumnFormVisible: {
+ type: Boolean,
+ required: true,
+ },
},
data() {
return {
boardHeight: null,
+ highlightedLists: [],
};
},
computed: {
- ...mapState(['boardLists', 'error', 'addColumnForm']),
- addColumnFormVisible() {
- return this.addColumnForm?.visible;
+ ...mapState(['boardLists', 'error']),
+ boardListsById() {
+ return this.isApolloBoard ? this.boardListsApollo : this.boardLists;
},
boardListsToUse() {
const lists = this.isApolloBoard ? this.boardListsApollo : this.boardLists;
@@ -101,6 +110,13 @@ export default {
refetchLists() {
this.$apollo.queries.boardListsApollo.refetch();
},
+ highlightList(listId) {
+ this.highlightedLists.push(listId);
+
+ setTimeout(() => {
+ this.highlightedLists = this.highlightedLists.filter((id) => id !== listId);
+ }, flashAnimationDuration);
+ },
},
};
</script>
@@ -129,13 +145,22 @@ export default {
:board-id="boardId"
:list="list"
:filters="filterParams"
+ :highlighted-lists-apollo="highlightedLists"
:data-draggable-item-type="$options.draggableItemTypes.list"
:class="{ 'gl-display-none! gl-sm-display-inline-block!': addColumnFormVisible }"
@setActiveList="$emit('setActiveList', $event)"
/>
<transition name="slide" @after-enter="afterFormEnters">
- <board-add-new-column v-if="addColumnFormVisible" class="gl-xs-w-full!" />
+ <board-add-new-column
+ v-if="addColumnFormVisible"
+ class="gl-xs-w-full!"
+ :board-id="boardId"
+ :list-query-variables="listQueryVariables"
+ :lists="boardListsById"
+ @setAddColumnFormVisibility="$emit('setAddColumnFormVisibility', $event)"
+ @highlight-list="highlightList"
+ />
</transition>
</component>
@@ -146,8 +171,20 @@ export default {
:lists="boardListsToUse"
:can-admin-list="canAdminList"
:filters="filterParams"
+ :highlighted-lists="highlightedLists"
@setActiveList="$emit('setActiveList', $event)"
- />
+ >
+ <board-add-new-column
+ v-if="addColumnFormVisible"
+ class="gl-sticky gl-top-5"
+ :filter-params="filterParams"
+ :list-query-variables="listQueryVariables"
+ :board-id="boardId"
+ :lists="boardListsById"
+ @setAddColumnFormVisibility="$emit('setAddColumnFormVisibility', $event)"
+ @highlight-list="highlightList"
+ />
+ </epics-swimlanes>
<board-content-sidebar v-if="isIssueBoard" data-testid="issue-boards-sidebar" />
diff --git a/app/assets/javascripts/boards/components/board_settings_sidebar.vue b/app/assets/javascripts/boards/components/board_settings_sidebar.vue
index 23e0f2510a7..0f43aae3936 100644
--- a/app/assets/javascripts/boards/components/board_settings_sidebar.vue
+++ b/app/assets/javascripts/boards/components/board_settings_sidebar.vue
@@ -114,10 +114,10 @@ export default {
showScopedLabels(label) {
return this.scopedLabelsAvailable && isScopedLabel(label);
},
- async deleteBoardList() {
+ deleteBoardList() {
this.track('click_button', { label: 'remove_list' });
if (this.isApolloBoard) {
- await this.deleteList(this.activeListId);
+ this.deleteList(this.activeListId);
} else {
this.removeList(this.activeId);
}
@@ -157,7 +157,7 @@ export default {
<mounting-portal mount-to="#js-right-sidebar-portal" name="board-settings-sidebar" append>
<gl-drawer
v-bind="$attrs"
- class="js-board-settings-sidebar gl-absolute"
+ class="js-board-settings-sidebar gl-absolute boards-sidebar"
:open="showSidebar"
variant="sidebar"
@close="unsetActiveListId"
diff --git a/app/assets/javascripts/boards/components/board_top_bar.vue b/app/assets/javascripts/boards/components/board_top_bar.vue
index c186346b2ac..54d456867e5 100644
--- a/app/assets/javascripts/boards/components/board_top_bar.vue
+++ b/app/assets/javascripts/boards/components/board_top_bar.vue
@@ -35,6 +35,10 @@ export default {
type: String,
required: true,
},
+ addColumnFormVisible: {
+ type: Boolean,
+ required: true,
+ },
isSwimlanesOn: {
type: Boolean,
required: true,
@@ -117,7 +121,11 @@ export default {
@toggleSwimlanes="$emit('toggleSwimlanes', $event)"
/>
<config-toggle :board-has-scope="hasScope" />
- <board-add-new-column-trigger v-if="canAdminList" />
+ <board-add-new-column-trigger
+ v-if="canAdminList"
+ :is-new-list-showing="addColumnFormVisible"
+ @setAddColumnFormVisibility="$emit('setAddColumnFormVisibility', $event)"
+ />
<toggle-focus />
</div>
</div>
diff --git a/app/assets/javascripts/boards/constants.js b/app/assets/javascripts/boards/constants.js
index 7fe89ffbb52..ca188c741a9 100644
--- a/app/assets/javascripts/boards/constants.js
+++ b/app/assets/javascripts/boards/constants.js
@@ -3,6 +3,7 @@ import { TYPE_EPIC, TYPE_ISSUE, WORKSPACE_GROUP, WORKSPACE_PROJECT } from '~/iss
import { s__, __ } from '~/locale';
import updateEpicSubscriptionMutation from '~/sidebar/queries/update_epic_subscription.mutation.graphql';
import updateEpicTitleMutation from '~/sidebar/queries/update_epic_title.mutation.graphql';
+import createBoardListMutation from './graphql/board_list_create.mutation.graphql';
import destroyBoardListMutation from './graphql/board_list_destroy.mutation.graphql';
import updateBoardListMutation from './graphql/board_list_update.mutation.graphql';
@@ -71,6 +72,12 @@ export const listsQuery = {
},
};
+export const createListMutations = {
+ [TYPE_ISSUE]: {
+ mutation: createBoardListMutation,
+ },
+};
+
export const updateListQueries = {
[TYPE_ISSUE]: {
mutation: updateBoardListMutation,
diff --git a/app/assets/javascripts/error_tracking/components/stacktrace.vue b/app/assets/javascripts/error_tracking/components/stacktrace.vue
index f58d54f2933..54b9d37be73 100644
--- a/app/assets/javascripts/error_tracking/components/stacktrace.vue
+++ b/app/assets/javascripts/error_tracking/components/stacktrace.vue
@@ -25,7 +25,7 @@ export default {
v-for="(entry, index) in entries"
:key="`stacktrace-entry-${index}`"
:lines="entry.context"
- :file-path="entry.filename"
+ :file-path="entry.filename || entry.abs_path"
:error-line="entry.lineNo"
:error-fn="entry.function"
:error-column="entry.colNo"
diff --git a/app/assets/javascripts/error_tracking/components/stacktrace_entry.vue b/app/assets/javascripts/error_tracking/components/stacktrace_entry.vue
index 6ddd982ebf1..bf549063031 100644
--- a/app/assets/javascripts/error_tracking/components/stacktrace_entry.vue
+++ b/app/assets/javascripts/error_tracking/components/stacktrace_entry.vue
@@ -1,5 +1,5 @@
<script>
-import { GlTooltip, GlSprintf, GlIcon } from '@gitlab/ui';
+import { GlTooltip, GlSprintf, GlIcon, GlTruncate } from '@gitlab/ui';
import SafeHtml from '~/vue_shared/directives/safe_html';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import FileIcon from '~/vue_shared/components/file_icon.vue';
@@ -10,6 +10,7 @@ export default {
FileIcon,
GlIcon,
GlSprintf,
+ GlTruncate,
},
directives: {
GlTooltip,
@@ -22,7 +23,8 @@ export default {
},
filePath: {
type: String,
- required: true,
+ required: false,
+ default: '',
},
errorFn: {
type: String,
@@ -79,26 +81,23 @@ export default {
<template>
<div class="file-holder">
<div ref="header" class="file-title file-title-flex-parent">
- <div class="file-header-content d-flex align-content-center">
+ <div class="file-header-content d-flex align-content-center gl-flex-wrap overflow-hidden">
<div v-if="hasCode" class="d-inline-block cursor-pointer" @click="toggle()">
<gl-icon :name="collapseIcon" :size="16" class="gl-mr-2" />
</div>
- <file-icon :file-name="filePath" :size="16" aria-hidden="true" css-classes="gl-mr-2" />
- <strong
- v-gl-tooltip
- :title="filePath"
- class="file-title-name d-inline-block overflow-hidden text-truncate limited-width"
- data-container="body"
- >
- {{ filePath }}
- </strong>
- <clipboard-button
- :title="__('Copy file path')"
- :text="filePath"
- category="tertiary"
- size="small"
- css-class="gl-mr-1"
- />
+ <template v-if="filePath">
+ <file-icon :file-name="filePath" :size="16" aria-hidden="true" css-classes="gl-mr-2" />
+ <strong class="file-title-name d-inline-block overflow-hidden limited-width">
+ <gl-truncate with-tooltip :text="filePath" position="middle" />
+ </strong>
+ <clipboard-button
+ :title="__('Copy file path')"
+ :text="filePath"
+ category="tertiary"
+ size="small"
+ css-class="gl-mr-1"
+ />
+ </template>
<gl-sprintf v-if="errorFn" :message="__('%{spanStart}in%{spanEnd} %{errorFn}')">
<template #span="{ content }">
diff --git a/app/assets/javascripts/issues/show/components/fields/type.vue b/app/assets/javascripts/issues/show/components/fields/type.vue
index 775f25bdbc0..576d157e0fc 100644
--- a/app/assets/javascripts/issues/show/components/fields/type.vue
+++ b/app/assets/javascripts/issues/show/components/fields/type.vue
@@ -71,7 +71,7 @@ export default {
:label="$options.i18n.label"
label-class="sr-only"
label-for="issuable-type"
- class="mb-2 mb-md-0"
+ class="gl-mb-0"
>
<gl-collapsible-listbox
v-model="selectedIssueType"
diff --git a/app/assets/javascripts/issues/show/components/form.vue b/app/assets/javascripts/issues/show/components/form.vue
index 2e99c03d250..c9e21b296e4 100644
--- a/app/assets/javascripts/issues/show/components/form.vue
+++ b/app/assets/javascripts/issues/show/components/form.vue
@@ -222,7 +222,7 @@ export default {
<convert-description-modal
v-if="issueId && glFeatures.generateDescriptionAi"
- class="gl-pl-5 gl-sm-pl-0"
+ class="gl-pl-5 gl-md-pl-0"
:resource-id="resourceId"
:user-id="userId"
@contentGenerated="setDescription"
diff --git a/app/assets/javascripts/issues/show/components/header_actions.vue b/app/assets/javascripts/issues/show/components/header_actions.vue
index 4d9b69ddf99..f8c323fccae 100644
--- a/app/assets/javascripts/issues/show/components/header_actions.vue
+++ b/app/assets/javascripts/issues/show/components/header_actions.vue
@@ -326,7 +326,7 @@ export default {
</script>
<template>
- <div class="detail-page-header-actions gl-display-flex gl-align-self-start gl-gap-3">
+ <div class="detail-page-header-actions gl-display-flex gl-align-self-start gl-sm-gap-3">
<gl-dropdown
v-if="hasMobileDropdown"
class="gl-sm-display-none! w-100"
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index 884cb70cb9f..0e6530bccb7 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -169,7 +169,7 @@
.dropdown-menu-toggle-icon {
position: absolute;
- right: $gl-padding-8;
+ right: $gl-padding-12;
color: $gray-500;
}
}
diff --git a/app/assets/stylesheets/page_bundles/login.scss b/app/assets/stylesheets/page_bundles/login.scss
index 98fa45e0e3d..c25e15c1af9 100644
--- a/app/assets/stylesheets/page_bundles/login.scss
+++ b/app/assets/stylesheets/page_bundles/login.scss
@@ -103,6 +103,10 @@
.username .validation-error {
color: $red-500;
}
+
+ .terms .gl-form-checkbox {
+ @include gl-reset-font-size;
+ }
}
}
diff --git a/app/controllers/concerns/membership_actions.rb b/app/controllers/concerns/membership_actions.rb
index 09b82e36b1a..31675a58163 100644
--- a/app/controllers/concerns/membership_actions.rb
+++ b/app/controllers/concerns/membership_actions.rb
@@ -156,7 +156,7 @@ module MembershipActions
[:inherited]
else
if Feature.enabled?(:webui_members_inherited_users, current_user)
- [:inherited, :direct, :shared_from_groups]
+ [:inherited, :direct, :shared_from_groups, (:invited_groups if params[:project_id])].compact
else
[:inherited, :direct]
end
diff --git a/app/helpers/auth_helper.rb b/app/helpers/auth_helper.rb
index 0ee08ba1820..758aa3e294e 100644
--- a/app/helpers/auth_helper.rb
+++ b/app/helpers/auth_helper.rb
@@ -51,7 +51,8 @@ module AuthHelper
{
saml: 'saml_login_button',
openid_connect: 'oidc_login_button',
- github: 'github_login_button'
+ github: 'github_login_button',
+ gitlab: 'gitlab_oauth_login_button'
}[provider.to_sym]
end
diff --git a/app/models/packages/package.rb b/app/models/packages/package.rb
index 5d51b2562c7..56ab09459ad 100644
--- a/app/models/packages/package.rb
+++ b/app/models/packages/package.rb
@@ -23,7 +23,8 @@ class Packages::Package < ApplicationRecord
rubygems: 10,
helm: 11,
terraform_module: 12,
- rpm: 13
+ rpm: 13,
+ ml_model: 14
}
enum status: { default: 0, hidden: 1, processing: 2, error: 3, pending_destruction: 4 }
diff --git a/app/models/user.rb b/app/models/user.rb
index 87e31f84839..2a016df7ee3 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -2393,11 +2393,19 @@ class User < ApplicationRecord
end
def authorized_groups_without_shared_membership
- Group.from_union(
- [
- groups.select(*Namespace.cached_column_list),
- authorized_projects.joins(:namespace).select(*Namespace.cached_column_list)
- ])
+ if Feature.enabled?(:authorize_groups_query_without_column_cache)
+ Group.from_union(
+ [
+ groups.select(Namespace.default_select_columns),
+ authorized_projects.joins(:namespace).select(Namespace.default_select_columns)
+ ])
+ else
+ Group.from_union(
+ [
+ groups.select(*Namespace.cached_column_list),
+ authorized_projects.joins(:namespace).select(*Namespace.cached_column_list)
+ ])
+ end
end
def authorized_groups_with_shared_membership
diff --git a/app/services/releases/create_service.rb b/app/services/releases/create_service.rb
index e5883ca06f4..f0243d844d9 100644
--- a/app/services/releases/create_service.rb
+++ b/app/services/releases/create_service.rb
@@ -81,11 +81,17 @@ module Releases
tag: tag.name,
sha: tag.dereferenced_target.sha,
released_at: released_at,
- links_attributes: params.dig(:assets, 'links') || [],
+ links_attributes: links_attributes,
milestones: milestones
)
end
+ def links_attributes
+ (params.dig(:assets, 'links') || []).map do |link_params|
+ Releases::Links::Params.new(link_params).allowed_params
+ end
+ end
+
def create_evidence!(release, pipeline)
return if release.historical_release? || release.upcoming_release?
diff --git a/app/services/releases/links/base_service.rb b/app/services/releases/links/base_service.rb
index 8bab258f80a..4c260e3183f 100644
--- a/app/services/releases/links/base_service.rb
+++ b/app/services/releases/links/base_service.rb
@@ -18,17 +18,7 @@ module Releases
private
def allowed_params
- @allowed_params ||= params.slice(:name, :url, :link_type).tap do |hash|
- hash[:filepath] = filepath if provided_filepath?
- end
- end
-
- def provided_filepath?
- params.key?(:direct_asset_path) || params.key?(:filepath)
- end
-
- def filepath
- params[:direct_asset_path] || params[:filepath]
+ Params.new(params).allowed_params
end
end
end
diff --git a/app/services/releases/links/params.rb b/app/services/releases/links/params.rb
new file mode 100644
index 00000000000..124ab333bbc
--- /dev/null
+++ b/app/services/releases/links/params.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module Releases
+ module Links
+ class Params
+ def initialize(params)
+ @params = params.with_indifferent_access
+ end
+
+ def allowed_params
+ @allowed_params ||= params.slice(:name, :url, :link_type).tap do |hash|
+ hash[:filepath] = filepath if provided_filepath?
+ end
+ end
+
+ private
+
+ attr_reader :params
+
+ def provided_filepath?
+ params.key?(:direct_asset_path) || params.key?(:filepath)
+ end
+
+ def filepath
+ params[:direct_asset_path] || params[:filepath]
+ end
+ end
+ end
+end
diff --git a/app/views/shared/issue_type/_details_content.html.haml b/app/views/shared/issue_type/_details_content.html.haml
index fdbe247c6ba..40a02fddbf3 100644
--- a/app/views/shared/issue_type/_details_content.html.haml
+++ b/app/views/shared/issue_type/_details_content.html.haml
@@ -2,7 +2,7 @@
- api_awards_path = local_assigns.fetch(:api_awards_path, nil)
.issue-details.issuable-details.js-issue-details
- .detail-page-description.content-block.js-detail-page-description.gl-pt-4.gl-pb-0.gl-border-none
+ .detail-page-description.content-block.js-detail-page-description.gl-pt-3.gl-pb-0.gl-border-none
#js-issuable-app{ data: { initial: issuable_initial_data(issuable).to_json,
issuable_id: issuable.id,
full_path: @project.full_path,
diff --git a/config/feature_flags/development/authorize_groups_query_without_column_cache.yml b/config/feature_flags/development/authorize_groups_query_without_column_cache.yml
new file mode 100644
index 00000000000..48440649d63
--- /dev/null
+++ b/config/feature_flags/development/authorize_groups_query_without_column_cache.yml
@@ -0,0 +1,8 @@
+---
+name: authorize_groups_query_without_column_cache
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/121613
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/412833
+milestone: '16.1'
+type: development
+group: group::tenant scale
+default_enabled: false
diff --git a/db/migrate/20230516110414_add_ml_model_max_file_size_to_plan_limits.rb b/db/migrate/20230516110414_add_ml_model_max_file_size_to_plan_limits.rb
new file mode 100644
index 00000000000..938536dc4bc
--- /dev/null
+++ b/db/migrate/20230516110414_add_ml_model_max_file_size_to_plan_limits.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class AddMlModelMaxFileSizeToPlanLimits < Gitlab::Database::Migration[2.1]
+ def change
+ add_column(:plan_limits, :ml_model_max_file_size, :bigint, default: 10.gigabytes, null: false)
+ end
+end
diff --git a/db/post_migrate/20230515142300_add_unique_index_for_ml_model_packages.rb b/db/post_migrate/20230515142300_add_unique_index_for_ml_model_packages.rb
new file mode 100644
index 00000000000..a776c227032
--- /dev/null
+++ b/db/post_migrate/20230515142300_add_unique_index_for_ml_model_packages.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class AddUniqueIndexForMlModelPackages < Gitlab::Database::Migration[2.1]
+ INDEX_NAME = 'uniq_idx_packages_packages_on_project_id_name_version_ml_model'
+ PACKAGE_TYPE_ML_MODEL = 14
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :packages_packages, [:project_id, :name, :version],
+ unique: true,
+ where: "package_type = #{PACKAGE_TYPE_ML_MODEL}",
+ name: INDEX_NAME
+ end
+
+ def down
+ remove_concurrent_index_by_name(:packages_packages, INDEX_NAME)
+ end
+end
diff --git a/db/schema_migrations/20230515142300 b/db/schema_migrations/20230515142300
new file mode 100644
index 00000000000..362c553959a
--- /dev/null
+++ b/db/schema_migrations/20230515142300
@@ -0,0 +1 @@
+365a9c05c660ba40b1b66256fa696cf8064388b4589b6d0bc507780e081526f2 \ No newline at end of file
diff --git a/db/schema_migrations/20230516110414 b/db/schema_migrations/20230516110414
new file mode 100644
index 00000000000..207e7fe65aa
--- /dev/null
+++ b/db/schema_migrations/20230516110414
@@ -0,0 +1 @@
+8a7d9123c689553d0aed06eea5714e9abd9f52cf6bc07b7349dcc723a3d8552a \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 5dfb95d10ef..440e5a3e086 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -20109,7 +20109,8 @@ CREATE TABLE plan_limits (
dashboard_limit_enabled_at timestamp with time zone,
web_hook_calls integer DEFAULT 0 NOT NULL,
project_access_token_limit integer DEFAULT 0 NOT NULL,
- google_cloud_logging_configurations integer DEFAULT 5 NOT NULL
+ google_cloud_logging_configurations integer DEFAULT 5 NOT NULL,
+ ml_model_max_file_size bigint DEFAULT '10737418240'::bigint NOT NULL
);
CREATE SEQUENCE plan_limits_id_seq
@@ -33197,6 +33198,8 @@ CREATE INDEX tmp_index_vulnerability_dismissal_info ON vulnerabilities USING btr
CREATE INDEX tmp_index_vulnerability_overlong_title_html ON vulnerabilities USING btree (id) WHERE (length(title_html) > 800);
+CREATE UNIQUE INDEX uniq_idx_packages_packages_on_project_id_name_version_ml_model ON packages_packages USING btree (project_id, name, version) WHERE (package_type = 14);
+
CREATE UNIQUE INDEX uniq_pkgs_deb_grp_architectures_on_distribution_id_and_name ON packages_debian_group_architectures USING btree (distribution_id, name);
CREATE UNIQUE INDEX uniq_pkgs_deb_grp_components_on_distribution_id_and_name ON packages_debian_group_components USING btree (distribution_id, name);
diff --git a/doc/administration/auth/ldap/ldap_synchronization.md b/doc/administration/auth/ldap/ldap_synchronization.md
index f32a4af9e27..cc788d6d4c8 100644
--- a/doc/administration/auth/ldap/ldap_synchronization.md
+++ b/doc/administration/auth/ldap/ldap_synchronization.md
@@ -15,7 +15,7 @@ You can change when synchronization occurs.
## User sync
-> Preventing LDAP username synchronization [introduced](<https://gitlab.com/gitlab-org/gitlab/-/issues/11336>) in GitLab 15.11.
+> Preventing LDAP user's profile name synchronization [introduced](<https://gitlab.com/gitlab-org/gitlab/-/issues/11336>) in GitLab 15.11.
Once per day, GitLab runs a worker to check and update GitLab
users against LDAP.
@@ -45,9 +45,9 @@ The process also updates the following user information:
- SSH public keys if `sync_ssh_keys` is set.
- Kerberos identity if Kerberos is enabled.
-### Synchronize LDAP username
+### Synchronize LDAP user's profile name
-By default, GitLab synchronizes the LDAP username field.
+By default, GitLab synchronizes the LDAP user's profile name field.
To prevent this synchronization, you can set `sync_name` to `false`.
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 974da5560e4..a50de40069e 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -25545,6 +25545,7 @@ Values for sorting package.
| <a id="packagetypeenumgolang"></a>`GOLANG` | Packages from the Golang package manager. |
| <a id="packagetypeenumhelm"></a>`HELM` | Packages from the Helm package manager. |
| <a id="packagetypeenummaven"></a>`MAVEN` | Packages from the Maven package manager. |
+| <a id="packagetypeenumml_model"></a>`ML_MODEL` | Packages from the Ml_model package manager. |
| <a id="packagetypeenumnpm"></a>`NPM` | Packages from the npm package manager. |
| <a id="packagetypeenumnuget"></a>`NUGET` | Packages from the Nuget package manager. |
| <a id="packagetypeenumpypi"></a>`PYPI` | Packages from the PyPI package manager. |
diff --git a/doc/development/testing_guide/end_to_end/running_tests_that_require_special_setup.md b/doc/development/testing_guide/end_to_end/running_tests_that_require_special_setup.md
index 146ad95b255..0544b331b1a 100644
--- a/doc/development/testing_guide/end_to_end/running_tests_that_require_special_setup.md
+++ b/doc/development/testing_guide/end_to_end/running_tests_that_require_special_setup.md
@@ -585,18 +585,18 @@ You can verify whether GitLab is appropriately redirecting your session to the `
The above spec is verbose, written specifically this way to ensure the idea behind the implementation is clear. We recommend following the practices detailed within our [Beginner's guide to writing end-to-end tests](beginners_guide.md).
-## OpenID Connect (OIDC) tests
+## Tests for GitLab as OpenID Connect (OIDC) and OAuth provider
-To run the [`login_via_oidc_with_gitlab_as_idp_spec`](https://gitlab.com/gitlab-org/gitlab/-/blob/188e2c876a17a097448d7f3ed35bdf264fed0d3b/qa/qa/specs/features/browser_ui/1_manage/login/login_via_oidc_with_gitlab_as_idp_spec.rb) on your local machine:
+To run the [`login_via_oauth_and_oidc_with_gitlab_as_idp_spec`](https://gitlab.com/gitlab-org/gitlab/-/blob/2e2c8bcfa4f68cd39041806af531038ce4d2ab04/qa/qa/specs/features/browser_ui/1_manage/login/login_via_oauth_and_oidc_with_gitlab_as_idp_spec.rb) on your local machine:
1. Make sure your GDK is set to run on a non-localhost address such as `gdk.test:3000`.
1. Configure a [loopback interface to 172.16.123.1](https://gitlab.com/gitlab-org/gitlab-development-kit/-/blob/6fe7b46403229f12ab6d903f99b024e0b82cb94a/doc/howto/local_network.md#create-loopback-interface).
1. Make sure Docker Desktop or Rancher Desktop is running.
-1. Add an entry to your `/etc/hosts` file for `gitlab-oidc-consumer.bridge` pointing to `127.0.0.1`.
+1. Add an entry to your `/etc/hosts` file for `gitlab-oidc-consumer.bridge` and `gitlab-oauth-consumer.bridge` pointing to `127.0.0.1`.
1. From the `qa` directory, run the following command. To set the GitLab image you want to use, update the `RELEASE` variable. For example, to use the latest EE image, set `RELEASE` to `gitlab/gitlab-ee:latest`:
```shell
bundle install
- RELEASE_REGISTRY_URL='registry.gitlab.com' RELEASE_REGISTRY_USERNAME='<your_gitlab_username>' RELEASE_REGISTRY_PASSWORD='<your_gitlab_personal_access_token>' RELEASE='registry.gitlab.com/gitlab-org/build/omnibus-gitlab-mirror/gitlab-ee:1d5a644145dfe901ea7648d825f8f9f3006d0acf' GITLAB_QA_ADMIN_ACCESS_TOKEN="<your_gdk_admin_personal_access_token>" QA_DEBUG=true CHROME_HEADLESS=false bundle exec bin/qa Test::Instance::All http://gdk.test:3000 qa/specs/features/browser_ui/1_manage/login/login_via_oidc_with_gitlab_as_idp_spec.rb
+ RELEASE_REGISTRY_URL='registry.gitlab.com' RELEASE_REGISTRY_USERNAME='<your_gitlab_username>' RELEASE_REGISTRY_PASSWORD='<your_gitlab_personal_access_token>' RELEASE='registry.gitlab.com/gitlab-org/build/omnibus-gitlab-mirror/gitlab-ee:c0ae46db6b31ea231b2de88961cd687acf634179' GITLAB_QA_ADMIN_ACCESS_TOKEN="<your_gdk_admin_personal_access_token>" QA_DEBUG=true CHROME_HEADLESS=false bundle exec bin/qa Test::Instance::All http://gdk.test:3000 qa/specs/features/browser_ui/1_manage/login/login_via_oauth_and_oidc_with_gitlab_as_idp_spec.rb
```
diff --git a/doc/integration/google.md b/doc/integration/google.md
index d60c1b43ed6..e9c52bc3e95 100644
--- a/doc/integration/google.md
+++ b/doc/integration/google.md
@@ -23,10 +23,9 @@ In Google's side:
1. Refresh the page and you should see your new project in the list
1. Go to the [Google API Console](https://console.developers.google.com/apis/dashboard)
1. In the upper-left corner, select the previously created project
-1. Select **Credentials** from the sidebar
-1. Select **OAuth consent screen** and fill the form with the required information
-1. In the **Credentials** tab, select **Create credentials > OAuth client ID**
-1. Fill in the required information
+1. Select **OAuth consent screen** from the sidebar and fill the form with the required information
+1. Select **Credentials** from the sidebar, then select **Create credentials > OAuth client ID**
+1. Fill in the required information:
- **Application type** - Choose "Web Application"
- **Name** - Use the default one or provide your own
- **Authorized JavaScript origins** -This isn't really used by GitLab but go
diff --git a/doc/user/profile/index.md b/doc/user/profile/index.md
index b7d8f6aba58..35f6f07f0be 100644
--- a/doc/user/profile/index.md
+++ b/doc/user/profile/index.md
@@ -363,7 +363,7 @@ When you sign in, three cookies are set:
- A session cookie called `_gitlab_session`.
This cookie has no set expiration date. However, it expires based on its `session_expire_delay`.
-- A session cookied called `about_gitlab_active_user`.
+- A session cookie called `about_gitlab_active_user`.
This cookie is used by the [marketing site](https://about.gitlab.com/) to determine if a user has an active GitLab session. No user information is passed to the cookie and it expires with the session.
- A persistent cookie called `remember_user_token`, which is set only if you selected **Remember me** on the sign-in page.
diff --git a/lib/api/maven_packages.rb b/lib/api/maven_packages.rb
index e075a917fa9..241cd93f380 100644
--- a/lib/api/maven_packages.rb
+++ b/lib/api/maven_packages.rb
@@ -60,16 +60,6 @@ module API
if stored_sha256 == expected_sha256
no_content!
else
- # Track sha1 conflicts.
- # See https://gitlab.com/gitlab-org/gitlab/-/issues/367356
- Gitlab::ErrorTracking.log_exception(
- ArgumentError.new,
- message: 'maven package file sha1 conflict',
- stored_sha1: package_file.file_sha1,
- received_sha256: uploaded_file.sha256,
- sha256_hexdigest_of_stored_sha1: stored_sha256
- )
-
conflict!
end
end
diff --git a/lib/api/release/links.rb b/lib/api/release/links.rb
index 311fcf9aba1..d0234439057 100644
--- a/lib/api/release/links.rb
+++ b/lib/api/release/links.rb
@@ -20,7 +20,7 @@ module API
end
resource 'projects/:id', requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
params do
- requires :tag_name, type: String, desc: 'The tag associated with the release', as: :tag
+ requires :tag_name, type: String, desc: 'The tag associated with the release'
end
resource 'releases/:tag_name', requirements: RELEASE_ENDPOINT_REQUIREMENTS do
resource :assets do
@@ -56,7 +56,8 @@ module API
params do
requires :name, type: String, desc: 'The name of the link. Link names must be unique in the release'
requires :url, type: String, desc: 'The URL of the link. Link URLs must be unique in the release.'
- optional :direct_asset_path, type: String, desc: 'Optional path for a direct asset link', as: :filepath
+ optional :direct_asset_path, type: String, desc: 'Optional path for a direct asset link'
+ optional :filepath, type: String, desc: 'Deprecated: optional path for a direct asset link'
optional :link_type,
type: String,
values: %w[other runbook image package],
@@ -110,7 +111,8 @@ module API
params do
optional :name, type: String, desc: 'The name of the link'
optional :url, type: String, desc: 'The URL of the link'
- optional :direct_asset_path, type: String, desc: 'Optional path for a direct asset link', as: :filepath
+ optional :direct_asset_path, type: String, desc: 'Optional path for a direct asset link'
+ optional :filepath, type: String, desc: 'Deprecated: optional path for a direct asset link'
optional :link_type,
type: String,
values: %w[other runbook image package],
@@ -164,11 +166,11 @@ module API
helpers do
def release
- @release ||= user_project.releases.find_by_tag!(params[:tag])
+ @release ||= user_project.releases.find_by_tag!(declared_params(include_parent_namespaces: true)[:tag_name])
end
def link
- @link ||= release.links.find(params[:link_id])
+ @link ||= release.links.find(declared_params(include_parent_namespaces: true)[:link_id])
end
end
end
diff --git a/lib/api/releases.rb b/lib/api/releases.rb
index 0b31a3e0309..5d056ade3da 100644
--- a/lib/api/releases.rb
+++ b/lib/api/releases.rb
@@ -109,7 +109,7 @@ module API
cache_context: -> (_) { "user:{#{current_user&.id}}" },
expires_in: 5.minutes,
current_user: current_user,
- include_html_description: params[:include_html_description]
+ include_html_description: declared_params[:include_html_description]
end
desc 'Get a release by a tag name' do
@@ -135,7 +135,7 @@ module API
not_found! unless release
- present release, with: Entities::Release, current_user: current_user, include_html_description: params[:include_html_description]
+ present release, with: Entities::Release, current_user: current_user, include_html_description: declared_params[:include_html_description]
end
desc 'Download a project release asset file' do
@@ -162,8 +162,8 @@ module API
not_found! unless release
- link = release.links.find_by_filepath!("/#{params[:filepath]}")
-
+ filepath = declared_params(include_missing: false)[:filepath]
+ link = release.links.find_by_filepath!("/#{filepath}")
not_found! unless link
redirect link.url
@@ -196,7 +196,7 @@ module API
redirect_url = api_v4_projects_releases_path(id: user_project.id, tag_name: latest_release.tag)
# Include the additional suffix_path if present
- redirect_url += "/#{params[:suffix_path]}" if params[:suffix_path].present?
+ redirect_url += "/#{declared_params[:suffix_path]}" if declared_params[:suffix_path].present?
# Include any query parameter except `order_by` since we have plans to extend it in the future.
# See https://gitlab.com/gitlab-org/gitlab/-/issues/352945 for reference.
@@ -238,7 +238,8 @@ module API
optional :links, type: Array do
requires :name, type: String, desc: 'The name of the link. Link names must be unique within the release'
requires :url, type: String, desc: 'The URL of the link. Link URLs must be unique within the release'
- optional :direct_asset_path, type: String, desc: 'Optional path for a direct asset link', as: :filepath
+ optional :direct_asset_path, type: String, desc: 'Optional path for a direct asset link'
+ optional :filepath, type: String, desc: 'Deprecated: optional path for a direct asset link'
optional :link_type, type: String, desc: 'The type of the link: `other`, `runbook`, `image`, `package`. Defaults to `other`'
end
end
@@ -392,7 +393,7 @@ module API
end
def release
- @release ||= user_project.releases.find_by_tag(params[:tag])
+ @release ||= user_project.releases.find_by_tag(declared_params[:tag])
end
def find_latest_release
diff --git a/lib/error_tracking/stacktrace_builder.rb b/lib/error_tracking/stacktrace_builder.rb
index 024587e8683..a2d7091a62a 100644
--- a/lib/error_tracking/stacktrace_builder.rb
+++ b/lib/error_tracking/stacktrace_builder.rb
@@ -19,6 +19,7 @@ module ErrorTracking
'lineNo' => entry['lineno'],
'context' => build_stacktrace_context(entry),
'filename' => entry['filename'],
+ 'abs_path' => entry['abs_path'],
'function' => entry['function'],
'colNo' => 0 # we don't support colNo yet.
}
diff --git a/package.json b/package.json
index 6c979699811..a41671efb23 100644
--- a/package.json
+++ b/package.json
@@ -56,7 +56,7 @@
"@gitlab/cluster-client": "^1.2.0",
"@gitlab/favicon-overlay": "2.0.0",
"@gitlab/fonts": "^1.2.0",
- "@gitlab/svgs": "3.47.0",
+ "@gitlab/svgs": "3.49.0",
"@gitlab/ui": "62.12.0",
"@gitlab/visual-review-tools": "1.7.3",
"@gitlab/web-ide": "0.0.1-dev-20230524134151",
diff --git a/qa/qa/page/main/login.rb b/qa/qa/page/main/login.rb
index bea01a5bbc7..f18e227424b 100644
--- a/qa/qa/page/main/login.rb
+++ b/qa/qa/page/main/login.rb
@@ -42,6 +42,7 @@ module QA
element :saml_login_button
element :github_login_button
element :oidc_login_button
+ element :gitlab_oauth_login_button
end
view 'app/views/layouts/devise.html.haml' do
@@ -189,11 +190,16 @@ module QA
click_element :saml_login_button
end
- def sign_in_with_oidc
+ def sign_in_with_gitlab_oidc
set_initial_password_if_present
click_element :oidc_login_button
end
+ def sign_in_with_gitlab_oauth
+ set_initial_password_if_present
+ click_element :gitlab_oauth_login_button
+ end
+
def sign_out_and_sign_in_as(user:)
Menu.perform(&:sign_out_if_signed_in)
has_sign_in_tab?
diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/login_via_oauth_and_oidc_with_gitlab_as_idp_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/login_via_oauth_and_oidc_with_gitlab_as_idp_spec.rb
new file mode 100644
index 00000000000..943ec6681b2
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/1_manage/login/login_via_oauth_and_oidc_with_gitlab_as_idp_spec.rb
@@ -0,0 +1,158 @@
+# frozen_string_literal: true
+
+module QA
+ RSpec.describe 'Manage', :skip_live_env, requires_admin: 'creates users and instance OAuth application',
+ product_group: :authentication_and_authorization, quarantine: {
+ only: { pipeline: :nightly },
+ type: :investigating,
+ issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/408317'
+ } do
+ let!(:user) { Resource::User.fabricate_via_api! }
+ let(:consumer_host) { "http://#{consumer_name}.#{Runtime::Env.running_in_ci? ? 'test' : 'bridge'}" }
+ let(:instance_oauth_app) do
+ Resource::InstanceOauthApplication.fabricate! do |application|
+ application.redirect_uri = redirect_uri
+ application.scopes = scopes
+ end
+ end
+
+ after do
+ instance_oauth_app.remove_via_api!
+ remove_gitlab_service(consumer_name)
+ end
+
+ def run_gitlab_service(name:, app_id:, app_secret:)
+ Service::DockerRun::Gitlab.new(
+ image: Runtime::Env.release,
+ name: name,
+ omnibus_config: omnibus_configuration(app_id: app_id, app_secret: app_secret)).tap do |gitlab|
+ gitlab.login
+ gitlab.pull
+ gitlab.register!
+ end
+ end
+
+ def remove_gitlab_service(name)
+ Service::DockerRun::Gitlab.new(name: name).remove!
+ end
+
+ def wait_for_service(service)
+ Support::Waiter.wait_until(max_duration: 900, sleep_interval: 5, raise_on_failure: true) do
+ service.health == "healthy"
+ end
+ end
+
+ shared_examples 'Instance OAuth Application' do |app_type, testcase|
+ it "creates #{app_type} application and uses it to login", testcase: testcase do
+ instance_oauth_app
+
+ Page::Main::Menu.perform(&:sign_out_if_signed_in)
+
+ app_id = instance_oauth_app.application_id
+ app_secret = instance_oauth_app.application_secret
+
+ consumer_gitlab_service = run_gitlab_service(name: consumer_name, app_id: app_id, app_secret: app_secret)
+
+ wait_for_service(consumer_gitlab_service)
+
+ page.visit consumer_host
+
+ expect(page.driver.current_url).to include(consumer_host)
+
+ Page::Main::Login.perform do |login_page|
+ login_page.public_send("sign_in_with_gitlab_#{app_type}")
+ end
+
+ expect(page.driver.current_url).to include(Runtime::Scenario.gitlab_address)
+
+ Flow::Login.sign_in(as: user)
+
+ expect(page.driver.current_url).to include(consumer_host)
+
+ Page::Dashboard::Welcome.perform do |welcome|
+ expect(welcome).to have_welcome_title("Welcome to GitLab")
+ end
+ end
+ end
+
+ describe 'OIDC' do
+ let(:consumer_name) { 'gitlab-oidc-consumer' }
+ let(:redirect_uri) { "#{consumer_host}/users/auth/openid_connect/callback" }
+ let(:scopes) { %w[openid profile email] }
+
+ def omnibus_configuration(app_id:, app_secret:)
+ <<~OMNIBUS
+ gitlab_rails['initial_root_password']='5iveL\!fe';
+ gitlab_rails['omniauth_enabled'] = true;
+ gitlab_rails['omniauth_allow_single_sign_on'] = true;
+ gitlab_rails['omniauth_block_auto_created_users'] = false;
+ gitlab_rails['omniauth_providers'] = [
+ {
+ name: 'openid_connect',
+ label: 'GitLab OIDC',
+ args: {
+ name: 'openid_connect',
+ scope: ['openid','profile','email'],
+ response_type: 'code',
+ issuer: '#{Runtime::Scenario.gitlab_address}',
+ discovery: false,
+ uid_field: 'preferred_username',
+ send_scope_to_token_endpoint: 'false',
+ client_options: {
+ identifier: '#{app_id}',
+ secret: '#{app_secret}',
+ redirect_uri: '#{consumer_host}/users/auth/openid_connect/callback',
+ jwks_uri: '#{Runtime::Scenario.gitlab_address}/oauth/discovery/keys',
+ userinfo_endpoint: '#{Runtime::Scenario.gitlab_address}/oauth/userinfo',
+ token_endpoint: '#{Runtime::Scenario.gitlab_address}/oauth/token',
+ authorization_endpoint: '#{Runtime::Scenario.gitlab_address}/oauth/authorize'
+ }
+ }
+ }
+ ];
+ OMNIBUS
+ end
+
+ # The host GitLab instance with address Runtime::Scenario.gitlab_address is the OIDC idP - OIDC application will
+ # be created here.
+ # GitLab instance stood up in docker with address gitlab-oidc-consumer.test (or gitlab-oidc-consumer.bridge) is
+ # the consumer - The GitLab OIDC Login button will be displayed here.
+ it_behaves_like 'Instance OAuth Application', :oidc, 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/405137'
+ end
+
+ describe 'OAuth' do
+ let(:consumer_name) { 'gitlab-oauth-consumer' }
+ let(:redirect_uri) { "#{consumer_host}/users/auth/gitlab/callback" }
+ let(:scopes) { %w[read_user] }
+
+ def omnibus_configuration(app_id:, app_secret:)
+ <<~OMNIBUS
+ gitlab_rails['initial_root_password']='5iveL\!fe';
+ gitlab_rails['omniauth_enabled'] = true;
+ gitlab_rails['omniauth_allow_single_sign_on'] = true;
+ gitlab_rails['omniauth_block_auto_created_users'] = false;
+ gitlab_rails['omniauth_providers'] = [
+ {
+ name: 'gitlab',
+ label: 'GitLab OAuth',
+ app_id: '#{app_id}',
+ app_secret: '#{app_secret}',
+ args: {
+ scope: 'read_user',
+ client_options: {
+ site: '#{Runtime::Scenario.gitlab_address}'
+ }
+ }
+ }
+ ];
+ OMNIBUS
+ end
+
+ # The host GitLab instance with address Runtime::Scenario.gitlab_address is the OAuth idP - OAuth application will
+ # be created here.
+ # GitLab instance stood up in docker with address gitlab-oauth-consumer.test (or gitlab-oauth-consumer.bridge) is
+ # the consumer - The GitLab OAuth Login button will be displayed here.
+ it_behaves_like 'Instance OAuth Application', :oauth, 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/412111'
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/login_via_oidc_with_gitlab_as_idp_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/login_via_oidc_with_gitlab_as_idp_spec.rb
deleted file mode 100644
index 1be51f40f0e..00000000000
--- a/qa/qa/specs/features/browser_ui/1_manage/login/login_via_oidc_with_gitlab_as_idp_spec.rb
+++ /dev/null
@@ -1,118 +0,0 @@
-# frozen_string_literal: true
-
-module QA
- RSpec.describe 'Manage', :skip_live_env, requires_admin: 'creates users and instance OAuth application',
- product_group: :authentication_and_authorization do
- let!(:user) { Resource::User.fabricate_via_api! }
- let(:oidc_consumer_name) { 'gitlab-oidc-consumer' }
- let(:oidc_consumer_host) { "http://#{oidc_consumer_name}.#{Runtime::Env.running_in_ci? ? 'test' : 'bridge'}" }
- let(:instance_oauth_app) do
- Resource::InstanceOauthApplication.fabricate! do |application|
- application.redirect_uri = "#{oidc_consumer_host}/users/auth/openid_connect/callback"
- application.scopes = %w[openid profile email]
- end
- end
-
- after do
- instance_oauth_app.remove_via_api!
- remove_gitlab_service(oidc_consumer_name)
- end
-
- # The host GitLab instance with address Runtime::Scenario.gitlab_address is the OIDC idP - OIDC application will be
- # created here.
- # GitLab instance stood up in docker with address gitlab-oidc-consumer.test (or gitlab-oidc-consumer.bridge) is
- # the consumer - The GitLab OIDC Login button will be displayed here.
- describe 'OIDC' do
- it(
- 'creates GitLab OIDC application and uses it to login',
- testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/405137',
- quarantine: {
- only: { pipeline: :nightly },
- type: :investigating,
- issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/408317'
- }
- ) do
- instance_oauth_app
-
- Page::Main::Menu.perform(&:sign_out_if_signed_in)
-
- app_id = instance_oauth_app.application_id
- app_secret = instance_oauth_app.application_secret
-
- consumer_gitlab_service = run_gitlab_service(name: oidc_consumer_name, app_id: app_id, app_secret: app_secret)
-
- wait_for_service(consumer_gitlab_service)
-
- page.visit oidc_consumer_host
-
- expect(page.driver.current_url).to include(oidc_consumer_host)
-
- Page::Main::Login.perform(&:sign_in_with_oidc)
-
- expect(page.driver.current_url).to include(Runtime::Scenario.gitlab_address)
-
- Flow::Login.sign_in(as: user)
-
- expect(page.driver.current_url).to include(oidc_consumer_host)
-
- Page::Dashboard::Welcome.perform do |welcome|
- expect(welcome).to have_welcome_title("Welcome to GitLab")
- end
- end
-
- def run_gitlab_service(name:, app_id:, app_secret:)
- Service::DockerRun::Gitlab.new(
- image: Runtime::Env.release,
- name: name,
- omnibus_config: omnibus_configuration(app_id: app_id, app_secret: app_secret)).tap do |gitlab|
- gitlab.login
- gitlab.pull
- gitlab.register!
- end
- end
-
- def remove_gitlab_service(name)
- Service::DockerRun::Gitlab.new(name: name).remove!
- end
-
- def wait_for_service(service)
- Support::Waiter.wait_until(max_duration: 900, sleep_interval: 5, raise_on_failure: true) do
- service.health == "healthy"
- end
- end
-
- def omnibus_configuration(app_id:, app_secret:)
- <<~OMNIBUS
- gitlab_rails['initial_root_password']='5iveL\!fe';
- gitlab_rails['omniauth_enabled'] = true;
- gitlab_rails['omniauth_allow_single_sign_on'] = true;
- gitlab_rails['omniauth_block_auto_created_users'] = false;
- gitlab_rails['omniauth_providers'] = [
- {
- name: 'openid_connect',
- label: 'GitLab OIDC',
- args: {
- name: 'openid_connect',
- scope: ['openid','profile','email'],
- response_type: 'code',
- issuer: '#{Runtime::Scenario.gitlab_address}',
- discovery: false,
- uid_field: 'preferred_username',
- send_scope_to_token_endpoint: 'false',
- client_options: {
- identifier: '#{app_id}',
- secret: '#{app_secret}',
- redirect_uri: '#{oidc_consumer_host}/users/auth/openid_connect/callback',
- jwks_uri: '#{Runtime::Scenario.gitlab_address}/oauth/discovery/keys',
- userinfo_endpoint: '#{Runtime::Scenario.gitlab_address}/oauth/userinfo',
- token_endpoint: '#{Runtime::Scenario.gitlab_address}/oauth/token',
- authorization_endpoint: '#{Runtime::Scenario.gitlab_address}/oauth/authorize'
- }
- }
- }
- ];
- OMNIBUS
- end
- end
- end
-end
diff --git a/spec/controllers/projects/project_members_controller_spec.rb b/spec/controllers/projects/project_members_controller_spec.rb
index dbea3592e24..ad49529b426 100644
--- a/spec/controllers/projects/project_members_controller_spec.rb
+++ b/spec/controllers/projects/project_members_controller_spec.rb
@@ -97,6 +97,37 @@ RSpec.describe Projects::ProjectMembersController do
expect(assigns(:project_members).map(&:invite_email)).not_to contain_exactly(invited_member.invite_email)
end
end
+
+ context 'when invited group members are present' do
+ let_it_be(:invited_group_member) { create(:user) }
+
+ before do
+ group.add_owner(invited_group_member)
+
+ project.invited_groups << group
+ project.add_maintainer(user)
+
+ sign_in(user)
+ end
+
+ context 'when webui_members_inherited_users is disabled' do
+ before do
+ stub_feature_flags(webui_members_inherited_users: false)
+ end
+
+ it 'lists only direct members' do
+ get :index, params: { namespace_id: project.namespace, project_id: project }
+
+ expect(assigns(:project_members).map(&:user_id)).not_to include(invited_group_member.id)
+ end
+ end
+
+ it 'lists invited group members by default' do
+ get :index, params: { namespace_id: project.namespace, project_id: project }
+
+ expect(assigns(:project_members).map(&:user_id)).to include(invited_group_member.id)
+ end
+ end
end
context 'invited members' do
diff --git a/spec/factories/packages/packages.rb b/spec/factories/packages/packages.rb
index 09f710d545d..75f540fabbe 100644
--- a/spec/factories/packages/packages.rb
+++ b/spec/factories/packages/packages.rb
@@ -300,5 +300,11 @@ FactoryBot.define do
end
end
end
+
+ factory :ml_model_package do
+ sequence(:name) { |n| "mlmodel-package-#{n}" }
+ version { '1.0.0' }
+ package_type { :ml_model }
+ end
end
end
diff --git a/spec/frontend/boards/components/board_add_new_column_form_spec.js b/spec/frontend/boards/components/board_add_new_column_form_spec.js
index 4fc9a6859a6..35296f36b89 100644
--- a/spec/frontend/boards/components/board_add_new_column_form_spec.js
+++ b/spec/frontend/boards/components/board_add_new_column_form_spec.js
@@ -29,10 +29,7 @@ describe('BoardAddNewColumnForm', () => {
},
slots,
store: createStore({
- actions: {
- setAddColumnFormVisibility: jest.fn(),
- ...actions,
- },
+ actions,
}),
});
};
@@ -48,16 +45,11 @@ describe('BoardAddNewColumnForm', () => {
});
it('clicking cancel hides the form', () => {
- const setAddColumnFormVisibility = jest.fn();
- mountComponent({
- actions: {
- setAddColumnFormVisibility,
- },
- });
+ mountComponent();
cancelButton().vm.$emit('click');
- expect(setAddColumnFormVisibility).toHaveBeenCalledWith(expect.anything(), false);
+ expect(wrapper.emitted('setAddColumnFormVisibility')).toEqual([[false]]);
});
describe('Add list button', () => {
diff --git a/spec/frontend/boards/components/board_add_new_column_spec.js b/spec/frontend/boards/components/board_add_new_column_spec.js
index a09c3aaa55e..8d6cc9373af 100644
--- a/spec/frontend/boards/components/board_add_new_column_spec.js
+++ b/spec/frontend/boards/components/board_add_new_column_spec.js
@@ -1,18 +1,36 @@
import { GlCollapsibleListbox } from '@gitlab/ui';
import Vue, { nextTick } from 'vue';
+import VueApollo from 'vue-apollo';
import Vuex from 'vuex';
+import createMockApollo from 'helpers/mock_apollo_helper';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import BoardAddNewColumn from '~/boards/components/board_add_new_column.vue';
import BoardAddNewColumnForm from '~/boards/components/board_add_new_column_form.vue';
import defaultState from '~/boards/stores/state';
-import { mockLabelList } from '../mock_data';
+import createBoardListMutation from 'ee_else_ce/boards/graphql/board_list_create.mutation.graphql';
+import boardLabelsQuery from '~/boards/graphql/board_labels.query.graphql';
+import {
+ mockLabelList,
+ createBoardListResponse,
+ labelsQueryResponse,
+ boardListsQueryResponse,
+} from '../mock_data';
Vue.use(Vuex);
+Vue.use(VueApollo);
-describe('Board card layout', () => {
+describe('BoardAddNewColumn', () => {
let wrapper;
+ const createBoardListQueryHandler = jest.fn().mockResolvedValue(createBoardListResponse);
+ const labelsQueryHandler = jest.fn().mockResolvedValue(labelsQueryResponse);
+ const mockApollo = createMockApollo([
+ [boardLabelsQuery, labelsQueryHandler],
+ [createBoardListMutation, createBoardListQueryHandler],
+ ]);
+
const findDropdown = () => wrapper.findComponent(GlCollapsibleListbox);
+ const findAddNewColumnForm = () => wrapper.findComponent(BoardAddNewColumnForm);
const selectLabel = (id) => {
findDropdown().vm.$emit('select', id);
};
@@ -33,8 +51,22 @@ describe('Board card layout', () => {
labels = [],
getListByLabelId = jest.fn(),
actions = {},
+ provide = {},
+ lists = {},
} = {}) => {
wrapper = shallowMountExtended(BoardAddNewColumn, {
+ apolloProvider: mockApollo,
+ propsData: {
+ listQueryVariables: {
+ isGroup: false,
+ isProject: true,
+ fullPath: 'gitlab-org/gitlab',
+ boardId: 'gid://gitlab/Board/1',
+ filters: {},
+ },
+ boardId: 'gid://gitlab/Board/1',
+ lists,
+ },
data() {
return {
selectedId,
@@ -43,7 +75,6 @@ describe('Board card layout', () => {
store: createStore({
actions: {
fetchLabels: jest.fn(),
- setAddColumnFormVisibility: jest.fn(),
...actions,
},
getters: {
@@ -57,6 +88,11 @@ describe('Board card layout', () => {
provide: {
scopedLabelsAvailable: true,
isEpicBoard: false,
+ issuableType: 'issue',
+ fullPath: 'gitlab-org/gitlab',
+ boardType: 'project',
+ isApolloBoard: false,
+ ...provide,
},
stubs: {
GlCollapsibleListbox,
@@ -67,6 +103,12 @@ describe('Board card layout', () => {
if (selectedId) {
selectLabel(selectedId);
}
+
+ // Necessary for cache update
+ mockApollo.clients.defaultClient.cache.readQuery = jest
+ .fn()
+ .mockReturnValue(boardListsQueryResponse.data);
+ mockApollo.clients.defaultClient.cache.writeQuery = jest.fn();
};
describe('Add list button', () => {
@@ -85,7 +127,7 @@ describe('Board card layout', () => {
},
});
- wrapper.findComponent(BoardAddNewColumnForm).vm.$emit('add-list');
+ findAddNewColumnForm().vm.$emit('add-list');
await nextTick();
@@ -110,7 +152,7 @@ describe('Board card layout', () => {
},
});
- wrapper.findComponent(BoardAddNewColumnForm).vm.$emit('add-list');
+ findAddNewColumnForm().vm.$emit('add-list');
await nextTick();
@@ -118,4 +160,59 @@ describe('Board card layout', () => {
expect(createList).not.toHaveBeenCalled();
});
});
+
+ describe('Apollo boards', () => {
+ describe('when list is new', () => {
+ beforeEach(() => {
+ mountComponent({ selectedId: mockLabelList.label.id, provide: { isApolloBoard: true } });
+ });
+
+ it('fetches labels and adds list', async () => {
+ findDropdown().vm.$emit('show');
+
+ await nextTick();
+ expect(labelsQueryHandler).toHaveBeenCalled();
+
+ selectLabel(mockLabelList.label.id);
+
+ findAddNewColumnForm().vm.$emit('add-list');
+
+ await nextTick();
+
+ expect(wrapper.emitted('highlight-list')).toBeUndefined();
+ expect(createBoardListQueryHandler).toHaveBeenCalledWith({
+ labelId: mockLabelList.label.id,
+ boardId: 'gid://gitlab/Board/1',
+ });
+ });
+ });
+
+ describe('when list already exists in board', () => {
+ beforeEach(() => {
+ mountComponent({
+ lists: {
+ [mockLabelList.id]: mockLabelList,
+ },
+ selectedId: mockLabelList.label.id,
+ provide: { isApolloBoard: true },
+ });
+ });
+
+ it('highlights existing list if trying to re-add', async () => {
+ findDropdown().vm.$emit('show');
+
+ await nextTick();
+ expect(labelsQueryHandler).toHaveBeenCalled();
+
+ selectLabel(mockLabelList.label.id);
+
+ findAddNewColumnForm().vm.$emit('add-list');
+
+ await nextTick();
+
+ expect(wrapper.emitted('highlight-list')).toEqual([[mockLabelList.id]]);
+ expect(createBoardListQueryHandler).not.toHaveBeenCalledWith();
+ });
+ });
+ });
});
diff --git a/spec/frontend/boards/components/board_add_new_column_trigger_spec.js b/spec/frontend/boards/components/board_add_new_column_trigger_spec.js
index d8b93e1f3b6..825cfc9453a 100644
--- a/spec/frontend/boards/components/board_add_new_column_trigger_spec.js
+++ b/spec/frontend/boards/components/board_add_new_column_trigger_spec.js
@@ -1,5 +1,5 @@
import { GlButton } from '@gitlab/ui';
-import Vue, { nextTick } from 'vue';
+import Vue from 'vue';
import Vuex from 'vuex';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import BoardAddNewColumnTrigger from '~/boards/components/board_add_new_column_trigger.vue';
@@ -13,12 +13,16 @@ describe('BoardAddNewColumnTrigger', () => {
const findBoardsCreateList = () => wrapper.findByTestId('boards-create-list');
const findTooltipText = () => getBinding(findBoardsCreateList().element, 'gl-tooltip');
+ const findCreateButton = () => wrapper.findComponent(GlButton);
- const mountComponent = () => {
+ const mountComponent = ({ isNewListShowing = false } = {}) => {
wrapper = mountExtended(BoardAddNewColumnTrigger, {
directives: {
GlTooltip: createMockDirective('gl-tooltip'),
},
+ propsData: {
+ isNewListShowing,
+ },
store: createStore(),
});
};
@@ -35,17 +39,19 @@ describe('BoardAddNewColumnTrigger', () => {
});
it('renders an enabled button', () => {
- const button = wrapper.findComponent(GlButton);
+ expect(findCreateButton().props('disabled')).toBe(false);
+ });
- expect(button.props('disabled')).toBe(false);
+ it('shows form on click button', () => {
+ findCreateButton().vm.$emit('click');
+
+ expect(wrapper.emitted('setAddColumnFormVisibility')).toEqual([[true]]);
});
});
describe('when button is disabled', () => {
- it('shows the tooltip', async () => {
- wrapper.findComponent(GlButton).vm.$emit('click');
-
- await nextTick();
+ it('shows the tooltip', () => {
+ mountComponent({ isNewListShowing: true });
const tooltip = findTooltipText();
diff --git a/spec/frontend/boards/components/board_content_spec.js b/spec/frontend/boards/components/board_content_spec.js
index c8b1811cdeb..23b3b13d69c 100644
--- a/spec/frontend/boards/components/board_content_spec.js
+++ b/spec/frontend/boards/components/board_content_spec.js
@@ -25,7 +25,7 @@ describe('BoardContent', () => {
const defaultState = {
isShowingEpicsSwimlanes: false,
- boardLists: mockLists,
+ boardLists: mockListsById,
error: undefined,
issuableType: 'issue',
};
@@ -57,6 +57,8 @@ describe('BoardContent', () => {
filterParams: {},
isSwimlanesOn: false,
boardListsApollo: mockListsById,
+ listQueryVariables: {},
+ addColumnFormVisible: false,
...props,
},
provide: {
@@ -166,7 +168,7 @@ describe('BoardContent', () => {
describe('when "add column" form is visible', () => {
beforeEach(() => {
- createComponent({ state: { addColumnForm: { visible: true } } });
+ createComponent({ props: { addColumnFormVisible: true } });
});
it('shows the "add column" form', () => {
diff --git a/spec/frontend/boards/components/board_top_bar_spec.js b/spec/frontend/boards/components/board_top_bar_spec.js
index d97a1dbff47..afc7da97617 100644
--- a/spec/frontend/boards/components/board_top_bar_spec.js
+++ b/spec/frontend/boards/components/board_top_bar_spec.js
@@ -46,6 +46,7 @@ describe('BoardTopBar', () => {
propsData: {
boardId: 'gid://gitlab/Board/1',
isSwimlanesOn: false,
+ addColumnFormVisible: false,
},
provide: {
swimlanesFeatureAvailable: false,
diff --git a/spec/frontend/boards/mock_data.js b/spec/frontend/boards/mock_data.js
index ec3ae27b6a1..60f906d2157 100644
--- a/spec/frontend/boards/mock_data.js
+++ b/spec/frontend/boards/mock_data.js
@@ -526,6 +526,27 @@ export const mockList = {
__typename: 'BoardList',
};
+export const labelsQueryResponse = {
+ data: {
+ project: {
+ id: 'gid://gitlab/Project/33',
+ labels: {
+ nodes: [
+ {
+ id: 'gid://gitlab/GroupLabel/121',
+ title: 'To Do',
+ color: '#F0AD4E',
+ textColor: '#FFFFFF',
+ description: null,
+ descriptionHtml: null,
+ },
+ ],
+ },
+ __typename: 'Project',
+ },
+ },
+};
+
export const mockLabelList = {
id: 'gid://gitlab/List/2',
title: 'To Do',
@@ -913,8 +934,8 @@ export const mockGroupLabelsResponse = {
export const boardListsQueryResponse = {
data: {
- group: {
- id: 'gid://gitlab/Group/1',
+ project: {
+ id: 'gid://gitlab/Project/1',
board: {
id: 'gid://gitlab/Board/1',
hideBacklogList: false,
@@ -922,7 +943,7 @@ export const boardListsQueryResponse = {
nodes: mockLists,
},
},
- __typename: 'Group',
+ __typename: 'Project',
},
},
};
@@ -989,6 +1010,15 @@ export const updateEpicTitleResponse = {
},
};
+export const createBoardListResponse = {
+ data: {
+ boardListCreate: {
+ list: mockLabelList,
+ errors: [],
+ },
+ },
+};
+
export const updateBoardListResponse = {
data: {
updateBoardList: {
diff --git a/spec/frontend/error_tracking/components/stacktrace_entry_spec.js b/spec/frontend/error_tracking/components/stacktrace_entry_spec.js
index 45fc1ad04ff..9bb68c6f277 100644
--- a/spec/frontend/error_tracking/components/stacktrace_entry_spec.js
+++ b/spec/frontend/error_tracking/components/stacktrace_entry_spec.js
@@ -1,4 +1,4 @@
-import { GlSprintf, GlIcon } from '@gitlab/ui';
+import { GlSprintf, GlIcon, GlTruncate } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { trimText } from 'helpers/text_helper';
import StackTraceEntry from '~/error_tracking/components/stacktrace_entry.vue';
@@ -44,6 +44,21 @@ describe('Stacktrace Entry', () => {
expect(wrapper.findAll('.line_content.old').length).toBe(1);
});
+ it('should render file information if filePath exists', () => {
+ mountComponent({ lines });
+ expect(wrapper.findComponent(FileIcon).exists()).toBe(true);
+ expect(wrapper.findComponent(ClipboardButton).exists()).toBe(true);
+ expect(wrapper.findComponent(GlTruncate).exists()).toBe(true);
+ expect(wrapper.findComponent(GlTruncate).props('text')).toBe('sidekiq/util.rb');
+ });
+
+ it('should not render file information if filePath does not exists', () => {
+ mountComponent({ lines, filePath: undefined });
+ expect(wrapper.findComponent(FileIcon).exists()).toBe(false);
+ expect(wrapper.findComponent(ClipboardButton).exists()).toBe(false);
+ expect(wrapper.findComponent(GlTruncate).exists()).toBe(false);
+ });
+
describe('entry caption', () => {
const findFileHeaderContent = () => wrapper.find('.file-header-content').text();
diff --git a/spec/frontend/error_tracking/components/stacktrace_spec.js b/spec/frontend/error_tracking/components/stacktrace_spec.js
index 29301c3e5ee..75c631617c3 100644
--- a/spec/frontend/error_tracking/components/stacktrace_spec.js
+++ b/spec/frontend/error_tracking/components/stacktrace_spec.js
@@ -14,6 +14,8 @@ describe('ErrorDetails', () => {
[25, ' watchdog(name, \u0026block)\n'],
],
lineNo: 24,
+ function: 'fn',
+ colNo: 1,
};
function mountComponent(entries) {
@@ -27,13 +29,33 @@ describe('ErrorDetails', () => {
describe('Stacktrace', () => {
it('should render single Stacktrace entry', () => {
mountComponent([stackTraceEntry]);
- expect(wrapper.findAllComponents(StackTraceEntry).length).toBe(1);
+ const allEntries = wrapper.findAllComponents(StackTraceEntry);
+ expect(allEntries.length).toBe(1);
+ const entry = allEntries.at(0);
+ expect(entry.props()).toEqual({
+ lines: stackTraceEntry.context,
+ filePath: stackTraceEntry.filename,
+ errorLine: stackTraceEntry.lineNo,
+ errorFn: stackTraceEntry.function,
+ errorColumn: stackTraceEntry.colNo,
+ expanded: true,
+ });
});
it('should render multiple Stacktrace entry', () => {
const entriesNum = 3;
mountComponent(new Array(entriesNum).fill(stackTraceEntry));
- expect(wrapper.findAllComponents(StackTraceEntry).length).toBe(entriesNum);
+ const entries = wrapper.findAllComponents(StackTraceEntry);
+ expect(entries.length).toBe(entriesNum);
+ expect(entries.at(0).props('expanded')).toBe(true);
+ expect(entries.at(1).props('expanded')).toBe(false);
+ expect(entries.at(2).props('expanded')).toBe(false);
+ });
+
+ it('should use the entry abs_path if filename is missing', () => {
+ mountComponent([{ ...stackTraceEntry, filename: undefined, abs_path: 'abs_path' }]);
+
+ expect(wrapper.findComponent(StackTraceEntry).props('filePath')).toBe('abs_path');
});
});
});
diff --git a/spec/graphql/types/packages/package_type_enum_spec.rb b/spec/graphql/types/packages/package_type_enum_spec.rb
index fb93b1c8c8a..027ce660679 100644
--- a/spec/graphql/types/packages/package_type_enum_spec.rb
+++ b/spec/graphql/types/packages/package_type_enum_spec.rb
@@ -4,6 +4,6 @@ require 'spec_helper'
RSpec.describe GitlabSchema.types['PackageTypeEnum'] do
it 'exposes all package types' do
- expect(described_class.values.keys).to contain_exactly(*%w[MAVEN NPM CONAN NUGET PYPI COMPOSER GENERIC GOLANG DEBIAN RUBYGEMS HELM TERRAFORM_MODULE RPM])
+ expect(described_class.values.keys).to contain_exactly(*%w[MAVEN NPM CONAN NUGET PYPI COMPOSER GENERIC GOLANG DEBIAN RUBYGEMS HELM TERRAFORM_MODULE RPM ML_MODEL])
end
end
diff --git a/spec/lib/error_tracking/stacktrace_builder_spec.rb b/spec/lib/error_tracking/stacktrace_builder_spec.rb
index 57eead13fc0..b7ef2e8545a 100644
--- a/spec/lib/error_tracking/stacktrace_builder_spec.rb
+++ b/spec/lib/error_tracking/stacktrace_builder_spec.rb
@@ -31,7 +31,9 @@ RSpec.describe ErrorTracking::StacktraceBuilder do
'context' => expected_context,
'filename' => 'puma/thread_pool.rb',
'function' => 'block in spawn_thread',
- 'colNo' => 0
+ 'colNo' => 0,
+ 'abs_path' =>
+ "/Users/developer/.asdf/installs/ruby/2.5.1/lib/ruby/gems/2.5.0/gems/puma-3.12.6/lib/puma/thread_pool.rb"
}
expect(stacktrace).to be_kind_of(Array)
@@ -48,7 +50,8 @@ RSpec.describe ErrorTracking::StacktraceBuilder do
'context' => [],
'filename' => 'webpack-internal:///./node_modules/vue/dist/vue.runtime.esm.js',
'function' => 'hydrate',
- 'colNo' => 0
+ 'colNo' => 0,
+ 'abs_path' => nil
}
expect(stacktrace).to be_kind_of(Array)
@@ -77,7 +80,9 @@ RSpec.describe ErrorTracking::StacktraceBuilder do
],
'filename' => nil,
'function' => 'main',
- 'colNo' => 0
+ 'colNo' => 0,
+ 'abs_path' =>
+ "/Users/stanhu/github/sentry-go/example/basic/main.go"
}
expect(stacktrace).to be_kind_of(Array)
diff --git a/spec/lib/generators/gitlab/partitioning/foreign_keys_generator_spec.rb b/spec/lib/generators/gitlab/partitioning/foreign_keys_generator_spec.rb
index 8c6dcbf4b96..7c7ca8207ff 100644
--- a/spec/lib/generators/gitlab/partitioning/foreign_keys_generator_spec.rb
+++ b/spec/lib/generators/gitlab/partitioning/foreign_keys_generator_spec.rb
@@ -60,7 +60,7 @@ feature_category: :continuous_integration do
RemoveFkToTestTmpBuildsTestTmpMetadataOnBuildsId
])
- schema_migrate_up!(only_databases: [:main])
+ schema_migrate_up!
fks = Gitlab::Database::PostgresForeignKey
.by_referenced_table_identifier('public._test_tmp_builds')
diff --git a/spec/migrations/finalize_issues_iid_scoping_to_namespace_spec.rb b/spec/migrations/finalize_issues_iid_scoping_to_namespace_spec.rb
index c4f091d0d80..1834e8c6e0e 100644
--- a/spec/migrations/finalize_issues_iid_scoping_to_namespace_spec.rb
+++ b/spec/migrations/finalize_issues_iid_scoping_to_namespace_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
require_migration!
RSpec.describe FinalizeIssuesIidScopingToNamespace, :migration, feature_category: :team_planning do
- let(:batched_migrations) { table(:batched_background_migrations, database: :main) }
+ let(:batched_migrations) { table(:batched_background_migrations) }
let!(:migration) { described_class::MIGRATION }
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 29e99dbfacb..c9120262b14 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -4418,7 +4418,7 @@ RSpec.describe User, feature_category: :user_profile do
end
end
- describe '#authorized_groups' do
+ shared_examples '#authorized_groups shared' do
let!(:user) { create(:user) }
let!(:private_group) { create(:group) }
let!(:child_group) { create(:group, parent: private_group) }
@@ -4449,6 +4449,32 @@ RSpec.describe User, feature_category: :user_profile do
end
end
+ describe '#authorized_groups' do
+ context 'authorize_groups_query_without_column_cache FF enabled' do
+ it_behaves_like '#authorized_groups shared' do
+ context 'when a new column is added to namespaces table' do
+ before do
+ ApplicationRecord.connection.execute "ALTER TABLE namespaces ADD COLUMN _test_column_xyz INT NULL"
+ end
+
+ # We sanity check that we don't get:
+ # ActiveRecord::StatementInvalid: PG::SyntaxError: ERROR: each UNION query must have the same number of columns
+ it 'will not raise errors' do
+ expect { subject.count }.not_to raise_error
+ end
+ end
+ end
+ end
+
+ context 'authorize_groups_query_without_column_cache FF disabled' do
+ before do
+ stub_feature_flags(authorize_groups_query_without_column_cache: false)
+ end
+
+ it_behaves_like '#authorized_groups shared'
+ end
+ end
+
describe '#membership_groups' do
let_it_be(:user) { create(:user) }
diff --git a/spec/requests/api/maven_packages_spec.rb b/spec/requests/api/maven_packages_spec.rb
index 7baec5dd551..4e746802500 100644
--- a/spec/requests/api/maven_packages_spec.rb
+++ b/spec/requests/api/maven_packages_spec.rb
@@ -1155,25 +1155,6 @@ RSpec.describe API::MavenPackages, feature_category: :package_registry do
expect(response).to have_gitlab_http_status(:no_content)
end
-
- context 'when the stored sha1 is not the same' do
- let(:sent_sha1) { File.read(file_upload.path) }
- let(:stored_sha1) { 'wrong sha1' }
-
- it 'logs an error and returns conflict' do
- expect(Gitlab::ErrorTracking).to receive(:log_exception).with(
- instance_of(ArgumentError),
- message: 'maven package file sha1 conflict',
- stored_sha1: stored_sha1,
- received_sha256: Digest::SHA256.hexdigest(sent_sha1),
- sha256_hexdigest_of_stored_sha1: Digest::SHA256.hexdigest(stored_sha1)
- )
-
- upload
-
- expect(response).to have_gitlab_http_status(:conflict)
- end
- end
end
context 'for md5 file' do
diff --git a/spec/services/releases/links/params_spec.rb b/spec/services/releases/links/params_spec.rb
new file mode 100644
index 00000000000..580bddf4fd9
--- /dev/null
+++ b/spec/services/releases/links/params_spec.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Releases::Links::Params, feature_category: :release_orchestration do
+ subject(:filter) { described_class.new(params) }
+
+ let(:params) { { name: name, url: url, direct_asset_path: direct_asset_path, link_type: link_type, unknown: '?' } }
+ let(:name) { 'link' }
+ let(:url) { 'https://example.com' }
+ let(:direct_asset_path) { '/path' }
+ let(:link_type) { 'other' }
+
+ describe '#allowed_params' do
+ subject { filter.allowed_params }
+
+ it 'returns only allowed params' do
+ is_expected.to eq('name' => name, 'url' => url, 'filepath' => direct_asset_path, 'link_type' => link_type)
+ end
+
+ context 'when deprecated filepath is used' do
+ let(:params) { super().merge(direct_asset_path: nil, filepath: 'filepath') }
+
+ it 'uses filepath value' do
+ is_expected.to eq('name' => name, 'url' => url, 'filepath' => 'filepath', 'link_type' => link_type)
+ end
+ end
+
+ context 'when both direct_asset_path and filepath are provided' do
+ let(:params) { super().merge(filepath: 'filepath') }
+
+ it 'uses direct_asset_path value' do
+ is_expected.to eq('name' => name, 'url' => url, 'filepath' => direct_asset_path, 'link_type' => link_type)
+ end
+ end
+ end
+end
diff --git a/spec/support/helpers/migrations_helpers.rb b/spec/support/helpers/migrations_helpers.rb
index 0084835ff8d..1b8c3388051 100644
--- a/spec/support/helpers/migrations_helpers.rb
+++ b/spec/support/helpers/migrations_helpers.rb
@@ -130,45 +130,19 @@ module MigrationsHelpers
end
end
- # TODO: use Gitlab::Database::EachDatabase class (https://gitlab.com/gitlab-org/gitlab/-/issues/410154)
- def migrate_databases!(only_databases: nil, version: nil)
- only_databases ||= if Gitlab::Database.database_mode == Gitlab::Database::MODE_SINGLE_DATABASE
- [:main]
- else
- %i[main ci]
- end
-
- # unique in the context of database, host, port
- configurations = Gitlab::Database.database_base_models.each_with_object({}) do |(_name, model), h|
- config = model.connection_db_config
-
- h[config.configuration_hash.slice(:database, :host, :port)] ||= config
- end
-
- with_reestablished_active_record_base do
- configurations.each_value do |configuration|
- next unless only_databases.include? configuration.name.to_sym
-
- ActiveRecord::Base.establish_connection(configuration) # rubocop:disable Database/EstablishConnection
-
- migration_context.migrate(version) # rubocop:disable Database/MultipleDatabases
- end
- end
- end
-
- def schema_migrate_down!(only_databases: nil)
+ def schema_migrate_down!
disable_migrations_output do
- migrate_databases!(only_databases: only_databases, version: migration_schema_version)
+ migration_context.down(migration_schema_version)
end
reset_column_in_all_models
end
- def schema_migrate_up!(only_databases: nil)
+ def schema_migrate_up!
reset_column_in_all_models
disable_migrations_output do
- migrate_databases!(only_databases: only_databases)
+ migration_context.up
end
reset_column_in_all_models
diff --git a/spec/support/shared_examples/services/packages_shared_examples.rb b/spec/support/shared_examples/services/packages_shared_examples.rb
index e09cca42846..7e7d8605d0b 100644
--- a/spec/support/shared_examples/services/packages_shared_examples.rb
+++ b/spec/support/shared_examples/services/packages_shared_examples.rb
@@ -214,6 +214,7 @@ RSpec.shared_examples 'filters on each package_type' do |is_project: false|
let_it_be(:package11) { create(:helm_package, project: project) }
let_it_be(:package12) { create(:terraform_module_package, project: project) }
let_it_be(:package13) { create(:rpm_package, project: project) }
+ let_it_be(:package14) { create(:ml_model_package, project: project) }
Packages::Package.package_types.keys.each do |package_type|
context "for package type #{package_type}" do
diff --git a/yarn.lock b/yarn.lock
index e2736e2e3a8..67b035463cb 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1110,10 +1110,10 @@
stylelint-declaration-strict-value "1.8.0"
stylelint-scss "4.2.0"
-"@gitlab/svgs@3.47.0":
- version "3.47.0"
- resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.47.0.tgz#1a18f008aef1ecb5407688017c3bbdbc597b7ec1"
- integrity sha512-xP8AyuFYRFmlxtcBYRqCnLmBgMjrACa0mUliRk/hAKUWcXoz/U4vdK69T1DhWalVi4cpUqmi4+rrIWI6fBdzew==
+"@gitlab/svgs@3.49.0":
+ version "3.49.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.49.0.tgz#5ac366ce709c20f3a94f108556d00c3baf637e1e"
+ integrity sha512-2HkBtkf4X7NtTgd+1b7pnmeTRFDYoEmXduNov4yWRFB7UHy3SlGcmeH7HHWfXKwjr52iuYrmwdqSsXQUV3sbvg==
"@gitlab/ui@62.12.0":
version "62.12.0"