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/components/board_column.vue2
-rw-r--r--app/assets/javascripts/boards/components/board_content.vue15
-rw-r--r--app/assets/javascripts/boards/graphql/board_lists.query.graphql2
-rw-r--r--app/assets/javascripts/boards/stores/actions.js66
-rw-r--r--app/assets/javascripts/boards/stores/mutation_types.js3
-rw-r--r--app/assets/javascripts/boards/stores/mutations.js17
-rw-r--r--app/assets/stylesheets/components/whats_new.scss8
-rw-r--r--db/post_migrate/20210709024048_finalize_push_event_payloads_bigint_conversion_2.rb71
-rw-r--r--db/schema_migrations/202107090240481
-rw-r--r--db/structure.sql4
-rw-r--r--doc/api/oauth2.md2
-rw-r--r--doc/integration/jira/issues.md2
-rw-r--r--lib/gitlab/kubernetes/default_namespace.rb17
-rw-r--r--locale/gitlab.pot2
-rw-r--r--qa/qa/page/project/registry/show.rb2
-rw-r--r--spec/features/boards/boards_spec.rb42
-rw-r--r--spec/frontend/boards/stores/actions_spec.js168
-rw-r--r--spec/frontend/boards/stores/mutations_spec.js40
-rw-r--r--spec/lib/gitlab/kubernetes/default_namespace_spec.rb30
19 files changed, 310 insertions, 184 deletions
diff --git a/app/assets/javascripts/boards/components/board_column.vue b/app/assets/javascripts/boards/components/board_column.vue
index 69abf886ad7..bcf5b12b209 100644
--- a/app/assets/javascripts/boards/components/board_column.vue
+++ b/app/assets/javascripts/boards/components/board_column.vue
@@ -79,7 +79,7 @@ export default {
'is-collapsed': list.collapsed,
'board-type-assignee': list.listType === 'assignee',
}"
- :data-id="list.id"
+ :data-list-id="list.id"
class="board gl-display-inline-block gl-h-full gl-px-3 gl-vertical-align-top gl-white-space-normal is-expandable"
data-qa-selector="board_list"
>
diff --git a/app/assets/javascripts/boards/components/board_content.vue b/app/assets/javascripts/boards/components/board_content.vue
index 53b071aaed1..cbebb9de070 100644
--- a/app/assets/javascripts/boards/components/board_content.vue
+++ b/app/assets/javascripts/boards/components/board_content.vue
@@ -76,19 +76,6 @@ export default {
const el = this.canDragColumns ? this.$refs.list.$el : this.$refs.list;
el.scrollTo({ left: el.scrollWidth, behavior: 'smooth' });
},
- handleDragOnEnd(params) {
- const { item, newIndex, oldIndex, to } = params;
-
- const listId = item.dataset.id;
- const replacedListId = to.children[newIndex].dataset.id;
-
- this.moveList({
- listId,
- replacedListId,
- newIndex,
- adjustmentValue: newIndex < oldIndex ? 1 : -1,
- });
- },
},
};
</script>
@@ -104,7 +91,7 @@ export default {
ref="list"
v-bind="draggableOptions"
class="boards-list gl-w-full gl-py-5 gl-px-3 gl-white-space-nowrap"
- @end="handleDragOnEnd"
+ @end="moveList"
>
<component
:is="boardColumnComponent"
diff --git a/app/assets/javascripts/boards/graphql/board_lists.query.graphql b/app/assets/javascripts/boards/graphql/board_lists.query.graphql
index eb922f162f8..734867c77e9 100644
--- a/app/assets/javascripts/boards/graphql/board_lists.query.graphql
+++ b/app/assets/javascripts/boards/graphql/board_lists.query.graphql
@@ -9,6 +9,7 @@ query ListIssues(
) {
group(fullPath: $fullPath) @include(if: $isGroup) {
board(id: $boardId) {
+ hideBacklogList
lists(issueFilters: $filters) {
nodes {
...BoardListFragment
@@ -18,6 +19,7 @@ query ListIssues(
}
project(fullPath: $fullPath) @include(if: $isProject) {
board(id: $boardId) {
+ hideBacklogList
lists(issueFilters: $filters) {
nodes {
...BoardListFragment
diff --git a/app/assets/javascripts/boards/stores/actions.js b/app/assets/javascripts/boards/stores/actions.js
index 0f1b72146c9..0d6a62e53a9 100644
--- a/app/assets/javascripts/boards/stores/actions.js
+++ b/app/assets/javascripts/boards/stores/actions.js
@@ -1,4 +1,5 @@
import * as Sentry from '@sentry/browser';
+import { sortBy } from 'lodash';
import {
BoardType,
ListType,
@@ -216,33 +217,48 @@ export default {
},
moveList: (
- { state, commit, dispatch },
- { listId, replacedListId, newIndex, adjustmentValue },
+ { state: { boardLists }, commit, dispatch },
+ {
+ item: {
+ dataset: { listId: movedListId },
+ },
+ newIndex,
+ to: { children },
+ },
) => {
- if (listId === replacedListId) {
+ const displacedListId = children[newIndex].dataset.listId;
+ if (movedListId === displacedListId) {
return;
}
- const { boardLists } = state;
- const backupList = { ...boardLists };
- const movedList = boardLists[listId];
-
- const newPosition = newIndex - 1;
- const listAtNewIndex = boardLists[replacedListId];
+ const listIds = sortBy(
+ Object.keys(boardLists).filter(
+ (listId) =>
+ listId !== movedListId &&
+ boardLists[listId].listType !== ListType.backlog &&
+ boardLists[listId].listType !== ListType.closed,
+ ),
+ (i) => boardLists[i].position,
+ );
- movedList.position = newPosition;
- listAtNewIndex.position += adjustmentValue;
- commit(types.MOVE_LIST, {
- movedList,
- listAtNewIndex,
- });
+ const targetPosition = boardLists[displacedListId].position;
+ // When the dragged list moves left, displaced list should shift right.
+ const shiftOffset = Number(boardLists[movedListId].position < targetPosition);
+ const displacedListIndex = listIds.findIndex((listId) => listId === displacedListId);
- dispatch('updateList', { listId, position: newPosition, backupList });
+ commit(
+ types.MOVE_LISTS,
+ listIds
+ .slice(0, displacedListIndex + shiftOffset)
+ .concat([movedListId], listIds.slice(displacedListIndex + shiftOffset))
+ .map((listId, index) => ({ listId, position: index })),
+ );
+ dispatch('updateList', { listId: movedListId, position: targetPosition });
},
updateList: (
- { commit, state: { issuableType, boardItemsByListId = {} }, dispatch },
- { listId, position, collapsed, backupList },
+ { state: { issuableType, boardItemsByListId = {} }, dispatch },
+ { listId, position, collapsed },
) => {
gqlClient
.mutate({
@@ -255,8 +271,7 @@ export default {
})
.then(({ data }) => {
if (data?.updateBoardList?.errors.length) {
- commit(types.UPDATE_LIST_FAILURE, backupList);
- return;
+ throw new Error();
}
// Only fetch when board items havent been fetched on a collapsed list
@@ -265,10 +280,19 @@ export default {
}
})
.catch(() => {
- commit(types.UPDATE_LIST_FAILURE, backupList);
+ dispatch('handleUpdateListFailure');
});
},
+ handleUpdateListFailure: ({ dispatch, commit }) => {
+ dispatch('fetchLists');
+
+ commit(
+ types.SET_ERROR,
+ s__('Boards|An error occurred while updating the board list. Please try again.'),
+ );
+ },
+
toggleListCollapsed: ({ commit }, { listId, collapsed }) => {
commit(types.TOGGLE_LIST_COLLAPSED, { listId, collapsed });
},
diff --git a/app/assets/javascripts/boards/stores/mutation_types.js b/app/assets/javascripts/boards/stores/mutation_types.js
index 38c54bc8c5d..0a6ee59955c 100644
--- a/app/assets/javascripts/boards/stores/mutation_types.js
+++ b/app/assets/javascripts/boards/stores/mutation_types.js
@@ -10,8 +10,7 @@ export const RECEIVE_BOARD_LISTS_SUCCESS = 'RECEIVE_BOARD_LISTS_SUCCESS';
export const RECEIVE_BOARD_LISTS_FAILURE = 'RECEIVE_BOARD_LISTS_FAILURE';
export const SHOW_PROMOTION_LIST = 'SHOW_PROMOTION_LIST';
export const RECEIVE_ADD_LIST_SUCCESS = 'RECEIVE_ADD_LIST_SUCCESS';
-export const MOVE_LIST = 'MOVE_LIST';
-export const UPDATE_LIST_FAILURE = 'UPDATE_LIST_FAILURE';
+export const MOVE_LISTS = 'MOVE_LISTS';
export const TOGGLE_LIST_COLLAPSED = 'TOGGLE_LIST_COLLAPSED';
export const REMOVE_LIST = 'REMOVE_LIST';
export const REMOVE_LIST_FAILURE = 'REMOVE_LIST_FAILURE';
diff --git a/app/assets/javascripts/boards/stores/mutations.js b/app/assets/javascripts/boards/stores/mutations.js
index a32a100fa11..ca795dfb10c 100644
--- a/app/assets/javascripts/boards/stores/mutations.js
+++ b/app/assets/javascripts/boards/stores/mutations.js
@@ -1,4 +1,4 @@
-import { pull, union } from 'lodash';
+import { cloneDeep, pull, union } from 'lodash';
import Vue from 'vue';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { s__ } from '~/locale';
@@ -103,15 +103,12 @@ export default {
Vue.set(state.boardLists, list.id, list);
},
- [mutationTypes.MOVE_LIST]: (state, { movedList, listAtNewIndex }) => {
- const { boardLists } = state;
- Vue.set(boardLists, movedList.id, movedList);
- Vue.set(boardLists, listAtNewIndex.id, listAtNewIndex);
- },
-
- [mutationTypes.UPDATE_LIST_FAILURE]: (state, backupList) => {
- state.error = s__('Boards|An error occurred while updating the list. Please try again.');
- Vue.set(state, 'boardLists', backupList);
+ [mutationTypes.MOVE_LISTS]: (state, movedLists) => {
+ const updatedBoardList = movedLists.reduce((acc, { listId, position }) => {
+ acc[listId].position = position;
+ return acc;
+ }, cloneDeep(state.boardLists));
+ Vue.set(state, 'boardLists', updatedBoardList);
},
[mutationTypes.TOGGLE_LIST_COLLAPSED]: (state, { listId, collapsed }) => {
diff --git a/app/assets/stylesheets/components/whats_new.scss b/app/assets/stylesheets/components/whats_new.scss
index 7af97505749..87593f53136 100644
--- a/app/assets/stylesheets/components/whats_new.scss
+++ b/app/assets/stylesheets/components/whats_new.scss
@@ -39,6 +39,14 @@
margin-top: calc(#{$performance-bar-height} + #{$header-height});
}
+.with-system-header .whats-new-drawer {
+ margin-top: $system-header-height + $header-height;
+}
+
+.with-performance-bar.with-system-header .whats-new-drawer {
+ margin-top: $performance-bar-height + $system-header-height + $header-height;
+}
+
.gl-badge.whats-new-item-badge {
background-color: $purple-light;
color: $purple;
diff --git a/db/post_migrate/20210709024048_finalize_push_event_payloads_bigint_conversion_2.rb b/db/post_migrate/20210709024048_finalize_push_event_payloads_bigint_conversion_2.rb
new file mode 100644
index 00000000000..717bdb2a887
--- /dev/null
+++ b/db/post_migrate/20210709024048_finalize_push_event_payloads_bigint_conversion_2.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+class FinalizePushEventPayloadsBigintConversion2 < ActiveRecord::Migration[6.1]
+ include Gitlab::Database::MigrationHelpers
+
+ disable_ddl_transaction!
+
+ TABLE_NAME = 'push_event_payloads'
+ INDEX_NAME = 'index_push_event_payloads_on_event_id_convert_to_bigint'
+
+ def up
+ ensure_batched_background_migration_is_finished(
+ job_class_name: 'CopyColumnUsingBackgroundMigrationJob',
+ table_name: TABLE_NAME,
+ column_name: 'event_id',
+ job_arguments: [["event_id"], ["event_id_convert_to_bigint"]]
+ )
+
+ swap_columns
+ end
+
+ def down
+ swap_columns
+ end
+
+ private
+
+ def swap_columns
+ add_concurrent_index TABLE_NAME, :event_id_convert_to_bigint, unique: true, name: INDEX_NAME
+
+ # Add a foreign key on `event_id_convert_to_bigint` before we swap the columns and drop the old FK (fk_36c74129da)
+ add_concurrent_foreign_key TABLE_NAME, :events, column: :event_id_convert_to_bigint, on_delete: :cascade
+
+ with_lock_retries(raise_on_exhaustion: true) do
+ # We'll need ACCESS EXCLUSIVE lock on the related tables,
+ # lets make sure it can be acquired from the start
+ execute "LOCK TABLE #{TABLE_NAME}, events IN ACCESS EXCLUSIVE MODE"
+
+ # Swap column names
+ temp_name = 'event_id_tmp'
+ execute "ALTER TABLE #{quote_table_name(TABLE_NAME)} RENAME COLUMN #{quote_column_name(:event_id)} TO #{quote_column_name(temp_name)}"
+ execute "ALTER TABLE #{quote_table_name(TABLE_NAME)} RENAME COLUMN #{quote_column_name(:event_id_convert_to_bigint)} TO #{quote_column_name(:event_id)}"
+ execute "ALTER TABLE #{quote_table_name(TABLE_NAME)} RENAME COLUMN #{quote_column_name(temp_name)} TO #{quote_column_name(:event_id_convert_to_bigint)}"
+
+ # We need to update the trigger function in order to make PostgreSQL to
+ # regenerate the execution plan for it. This is to avoid type mismatch errors like
+ # "type of parameter 15 (bigint) does not match that when preparing the plan (integer)"
+ function_name = Gitlab::Database::UnidirectionalCopyTrigger.on_table(TABLE_NAME).name(:event_id, :event_id_convert_to_bigint)
+ execute "ALTER FUNCTION #{quote_table_name(function_name)} RESET ALL"
+
+ # Swap defaults
+ change_column_default TABLE_NAME, :event_id, nil
+ change_column_default TABLE_NAME, :event_id_convert_to_bigint, 0
+
+ # Swap PK constraint
+ execute "ALTER TABLE #{TABLE_NAME} DROP CONSTRAINT push_event_payloads_pkey"
+ rename_index TABLE_NAME, INDEX_NAME, 'push_event_payloads_pkey'
+ execute "ALTER TABLE #{TABLE_NAME} ADD CONSTRAINT push_event_payloads_pkey PRIMARY KEY USING INDEX push_event_payloads_pkey"
+
+ # Drop original FK on the old int4 `event_id` (fk_36c74129da)
+ remove_foreign_key TABLE_NAME, name: concurrent_foreign_key_name(TABLE_NAME, :event_id)
+ # We swapped the columns but the FK for event_id is still using the old name for the event_id_convert_to_bigint column
+ # So we have to also swap the FK name now that we dropped the other one with the same
+ rename_constraint(
+ TABLE_NAME,
+ concurrent_foreign_key_name(TABLE_NAME, :event_id_convert_to_bigint),
+ concurrent_foreign_key_name(TABLE_NAME, :event_id)
+ )
+ end
+ end
+end
diff --git a/db/schema_migrations/20210709024048 b/db/schema_migrations/20210709024048
new file mode 100644
index 00000000000..52e089cd5a7
--- /dev/null
+++ b/db/schema_migrations/20210709024048
@@ -0,0 +1 @@
+d35079b6d6ed38ce8f212a09e684988f7499d456d28f70b6178914b1b17eee5b \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index a9b56afdfd0..dc7ab5fc539 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -17503,7 +17503,7 @@ ALTER SEQUENCE protected_tags_id_seq OWNED BY protected_tags.id;
CREATE TABLE push_event_payloads (
commit_count bigint NOT NULL,
- event_id integer NOT NULL,
+ event_id_convert_to_bigint integer DEFAULT 0 NOT NULL,
action smallint NOT NULL,
ref_type smallint NOT NULL,
commit_from bytea,
@@ -17511,7 +17511,7 @@ CREATE TABLE push_event_payloads (
ref text,
commit_title character varying(70),
ref_count integer,
- event_id_convert_to_bigint bigint DEFAULT 0 NOT NULL
+ event_id bigint NOT NULL
);
CREATE TABLE push_rules (
diff --git a/doc/api/oauth2.md b/doc/api/oauth2.md
index 1b06e554e5e..f2efa8fb4c0 100644
--- a/doc/api/oauth2.md
+++ b/doc/api/oauth2.md
@@ -83,7 +83,7 @@ Before starting the flow, generate the `STATE`, the `CODE_VERIFIER` and the `COD
which use the characters `A-Z`, `a-z`, `0-9`, `-`, `.`, `_`, and `~`.
- The `CODE_CHALLENGE` is an URL-safe base64-encoded string of the SHA256 hash of the
`CODE_VERIFIER`
- - In Ruby, you can set that up with `Base64.urlsafe_encode64(Digest::SHA256.digest(CODE_VERIFIER))`.
+ - In Ruby, you can set that up with `Base64.urlsafe_encode64(Digest::SHA256.digest(CODE_VERIFIER), padding: false)`.
1. Request authorization code. To do that, you should redirect the user to the
`/oauth/authorize` page with the following query parameters:
diff --git a/doc/integration/jira/issues.md b/doc/integration/jira/issues.md
index f2402b1cd14..06b0afb55bb 100644
--- a/doc/integration/jira/issues.md
+++ b/doc/integration/jira/issues.md
@@ -45,7 +45,7 @@ ENTITY_TITLE
You can [disable comments](#disable-comments-on-jira-issues) on issues.
-### Require associated Jira issue for merge requests to be merged
+### Require associated Jira issue for merge requests to be merged **(ULTIMATE)**
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/280766) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.12 behind a feature flag, disabled by default.
> - [Deployed behind a feature flag](../../user/feature_flags.md), disabled by default.
diff --git a/lib/gitlab/kubernetes/default_namespace.rb b/lib/gitlab/kubernetes/default_namespace.rb
index c95362b024b..c22c2fe394d 100644
--- a/lib/gitlab/kubernetes/default_namespace.rb
+++ b/lib/gitlab/kubernetes/default_namespace.rb
@@ -36,14 +36,17 @@ module Gitlab
end
end
- def default_project_namespace(slug)
- namespace_slug = "#{project.path}-#{project.id}".downcase
-
- if cluster.namespace_per_environment?
- namespace_slug += "-#{slug}"
- end
+ def default_project_namespace(environment_slug)
+ maybe_environment_suffix = cluster.namespace_per_environment? ? "-#{environment_slug}" : ''
+ suffix = "-#{project.id}#{maybe_environment_suffix}"
+ namespace = project_path_slug(63 - suffix.length) + suffix
+ Gitlab::NamespaceSanitizer.sanitize(namespace)
+ end
- Gitlab::NamespaceSanitizer.sanitize(namespace_slug)
+ def project_path_slug(max_length)
+ Gitlab::NamespaceSanitizer
+ .sanitize(project.path.downcase)
+ .first(max_length)
end
##
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index c22d4b60109..0ae5acccea0 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -5371,7 +5371,7 @@ msgstr ""
msgid "Boards|An error occurred while removing the list. Please try again."
msgstr ""
-msgid "Boards|An error occurred while updating the list. Please try again."
+msgid "Boards|An error occurred while updating the board list. Please try again."
msgstr ""
msgid "Boards|Blocked by %{blockedByCount} %{issuableType}"
diff --git a/qa/qa/page/project/registry/show.rb b/qa/qa/page/project/registry/show.rb
index dffdb9eebba..03c547fc8b5 100644
--- a/qa/qa/page/project/registry/show.rb
+++ b/qa/qa/page/project/registry/show.rb
@@ -31,7 +31,7 @@ module QA
def click_delete
click_element(:tag_delete_button)
- find_button('Confirm').click
+ find_button('Delete').click
end
end
end
diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb
index 4b52bb953ed..84d25fe7dbd 100644
--- a/spec/features/boards/boards_spec.rb
+++ b/spec/features/boards/boards_spec.rb
@@ -70,13 +70,6 @@ RSpec.describe 'Project issue boards', :js do
stub_feature_flags(board_new_list: false)
visit_project_board_path_without_query_limit(project, board)
-
- wait_for_requests
-
- expect(page).to have_selector('.board', count: 4)
- expect(find('.board:nth-child(2)')).to have_selector('.board-card')
- expect(find('.board:nth-child(3)')).to have_selector('.board-card')
- expect(find('.board:nth-child(4)')).to have_selector('.board-card')
end
it 'shows description tooltip on list title', :quarantine do
@@ -221,18 +214,35 @@ RSpec.describe 'Project issue boards', :js do
it 'changes position of list' do
drag(list_from_index: 2, list_to_index: 1, selector: '.board-header')
- wait_for_board_cards(2, 2)
- wait_for_board_cards(3, 8)
- wait_for_board_cards(4, 1)
-
- expect(find('.board:nth-child(2)')).to have_content(development.title)
- expect(find('.board:nth-child(3)')).to have_content(planning.title)
+ expect(find('.board:nth-child(2) [data-testid="board-list-header"]')).to have_content(development.title)
+ expect(find('.board:nth-child(3) [data-testid="board-list-header"]')).to have_content(planning.title)
# Make sure list positions are preserved after a reload
visit_project_board_path_without_query_limit(project, board)
- expect(find('.board:nth-child(2)')).to have_content(development.title)
- expect(find('.board:nth-child(3)')).to have_content(planning.title)
+ expect(find('.board:nth-child(2) [data-testid="board-list-header"]')).to have_content(development.title)
+ expect(find('.board:nth-child(3) [data-testid="board-list-header"]')).to have_content(planning.title)
+ end
+
+ context 'without backlog and closed lists' do
+ let_it_be(:board) { create(:board, project: project, hide_backlog_list: true, hide_closed_list: true) }
+ let_it_be(:list1) { create(:list, board: board, label: planning, position: 0) }
+ let_it_be(:list2) { create(:list, board: board, label: development, position: 1) }
+
+ it 'changes position of list' do
+ visit_project_board_path_without_query_limit(project, board)
+
+ drag(list_from_index: 0, list_to_index: 1, selector: '.board-header')
+
+ expect(find('.board:nth-child(1) [data-testid="board-list-header"]')).to have_content(development.title)
+ expect(find('.board:nth-child(2) [data-testid="board-list-header"]')).to have_content(planning.title)
+
+ # Make sure list positions are preserved after a reload
+ visit_project_board_path_without_query_limit(project, board)
+
+ expect(find('.board:nth-child(1) [data-testid="board-list-header"]')).to have_content(development.title)
+ expect(find('.board:nth-child(2) [data-testid="board-list-header"]')).to have_content(planning.title)
+ end
end
it 'dragging does not duplicate list' do
@@ -682,6 +692,8 @@ RSpec.describe 'Project issue boards', :js do
def visit_project_board_path_without_query_limit(project, board)
inspect_requests(inject_headers: { 'X-GITLAB-DISABLE-SQL-QUERY-LIMIT' => 'https://gitlab.com/gitlab-org/gitlab/-/issues/323426' }) do
visit project_board_path(project, board)
+
+ wait_for_requests
end
end
end
diff --git a/spec/frontend/boards/stores/actions_spec.js b/spec/frontend/boards/stores/actions_spec.js
index 5e16e389ddc..37817eecebc 100644
--- a/spec/frontend/boards/stores/actions_spec.js
+++ b/spec/frontend/boards/stores/actions_spec.js
@@ -1,4 +1,5 @@
import * as Sentry from '@sentry/browser';
+import { cloneDeep } from 'lodash';
import {
inactiveId,
ISSUABLE,
@@ -419,75 +420,94 @@ describe('fetchLabels', () => {
});
describe('moveList', () => {
- it('should commit MOVE_LIST mutation and dispatch updateList action', (done) => {
- const initialBoardListsState = {
- 'gid://gitlab/List/1': mockLists[0],
- 'gid://gitlab/List/2': mockLists[1],
- };
+ const backlogListId = 'gid://1';
+ const closedListId = 'gid://5';
- const state = {
- fullPath: 'gitlab-org',
- fullBoardId: 'gid://gitlab/Board/1',
- boardType: 'group',
- disabled: false,
- boardLists: initialBoardListsState,
- };
+ const boardLists1 = {
+ 'gid://3': { listType: '', position: 0 },
+ 'gid://4': { listType: '', position: 1 },
+ 'gid://5': { listType: '', position: 2 },
+ };
- testAction(
- actions.moveList,
- {
- listId: 'gid://gitlab/List/1',
- replacedListId: 'gid://gitlab/List/2',
- newIndex: 1,
- adjustmentValue: 1,
- },
- state,
- [
- {
- type: types.MOVE_LIST,
- payload: { movedList: mockLists[0], listAtNewIndex: mockLists[1] },
- },
- ],
- [
- {
- type: 'updateList',
- payload: {
- listId: 'gid://gitlab/List/1',
- position: 0,
- backupList: initialBoardListsState,
+ const boardLists2 = {
+ [backlogListId]: { listType: ListType.backlog, position: -Infinity },
+ [closedListId]: { listType: ListType.closed, position: Infinity },
+ ...cloneDeep(boardLists1),
+ };
+
+ const movableListsOrder = ['gid://3', 'gid://4', 'gid://5'];
+ const allListsOrder = [backlogListId, ...movableListsOrder, closedListId];
+
+ describe.each`
+ draggableFrom | draggableTo | boardLists | boardListsOrder | expectedMovableListsOrder
+ ${0} | ${2} | ${boardLists1} | ${movableListsOrder} | ${['gid://4', 'gid://5', 'gid://3']}
+ ${2} | ${0} | ${boardLists1} | ${movableListsOrder} | ${['gid://5', 'gid://3', 'gid://4']}
+ ${0} | ${1} | ${boardLists1} | ${movableListsOrder} | ${['gid://4', 'gid://3', 'gid://5']}
+ ${1} | ${2} | ${boardLists1} | ${movableListsOrder} | ${['gid://3', 'gid://5', 'gid://4']}
+ ${2} | ${1} | ${boardLists1} | ${movableListsOrder} | ${['gid://3', 'gid://5', 'gid://4']}
+ ${1} | ${3} | ${boardLists2} | ${allListsOrder} | ${['gid://4', 'gid://5', 'gid://3']}
+ ${3} | ${1} | ${boardLists2} | ${allListsOrder} | ${['gid://5', 'gid://3', 'gid://4']}
+ ${1} | ${2} | ${boardLists2} | ${allListsOrder} | ${['gid://4', 'gid://3', 'gid://5']}
+ ${2} | ${3} | ${boardLists2} | ${allListsOrder} | ${['gid://3', 'gid://5', 'gid://4']}
+ ${3} | ${2} | ${boardLists2} | ${allListsOrder} | ${['gid://3', 'gid://5', 'gid://4']}
+ `(
+ 'when moving a list from position $draggableFrom to $draggableTo with lists $boardListsOrder',
+ ({ draggableFrom, draggableTo, boardLists, boardListsOrder, expectedMovableListsOrder }) => {
+ const movedListId = boardListsOrder[draggableFrom];
+ const displacedListId = boardListsOrder[draggableTo];
+ const buildDraggablePayload = () => {
+ return {
+ item: { dataset: { listId: boardListsOrder[draggableFrom] } },
+ newIndex: draggableTo,
+ to: {
+ children: boardListsOrder.map((listId) => ({ dataset: { listId } })),
},
- },
- ],
- done,
- );
- });
+ };
+ };
- it('should not commit MOVE_LIST or dispatch updateList if listId and replacedListId are the same', () => {
- const initialBoardListsState = {
- 'gid://gitlab/List/1': mockLists[0],
- 'gid://gitlab/List/2': mockLists[1],
- };
+ it('should commit MOVE_LIST mutations and dispatch updateList action with correct payloads', () => {
+ return testAction({
+ action: actions.moveList,
+ payload: buildDraggablePayload(),
+ state: { boardLists },
+ expectedMutations: [
+ {
+ type: types.MOVE_LISTS,
+ payload: expectedMovableListsOrder.map((listId, i) => ({ listId, position: i })),
+ },
+ ],
+ expectedActions: [
+ {
+ type: 'updateList',
+ payload: {
+ listId: movedListId,
+ position: movableListsOrder.findIndex((i) => i === displacedListId),
+ },
+ },
+ ],
+ });
+ });
+ },
+ );
- const state = {
- fullPath: 'gitlab-org',
- fullBoardId: 'gid://gitlab/Board/1',
- boardType: 'group',
- disabled: false,
- boardLists: initialBoardListsState,
- };
+ describe('when moving from and to the same position', () => {
+ it('should not commit MOVE_LIST and should not dispatch updateList', () => {
+ const listId = 'gid://1000';
- testAction(
- actions.moveList,
- {
- listId: 'gid://gitlab/List/1',
- replacedListId: 'gid://gitlab/List/1',
- newIndex: 1,
- adjustmentValue: 1,
- },
- state,
- [],
- [],
- );
+ return testAction({
+ action: actions.moveList,
+ payload: {
+ item: { dataset: { listId } },
+ newIndex: 0,
+ to: {
+ children: [{ dataset: { listId } }],
+ },
+ },
+ state: { boardLists: { [listId]: { position: 0 } } },
+ expectedMutations: [],
+ expectedActions: [],
+ });
+ });
});
});
@@ -549,7 +569,7 @@ describe('updateList', () => {
});
});
- it('should commit UPDATE_LIST_FAILURE mutation when API returns an error', (done) => {
+ it('should dispatch handleUpdateListFailure when API returns an error', () => {
jest.spyOn(gqlClient, 'mutate').mockResolvedValue({
data: {
updateBoardList: {
@@ -559,17 +579,31 @@ describe('updateList', () => {
},
});
- testAction(
+ return testAction(
actions.updateList,
{ listId: 'gid://gitlab/List/1', position: 1 },
createState(),
- [{ type: types.UPDATE_LIST_FAILURE }],
[],
- done,
+ [{ type: 'handleUpdateListFailure' }],
);
});
});
+describe('handleUpdateListFailure', () => {
+ it('should dispatch fetchLists action and commit SET_ERROR mutation', async () => {
+ await testAction({
+ action: actions.handleUpdateListFailure,
+ expectedMutations: [
+ {
+ type: types.SET_ERROR,
+ payload: 'An error occurred while updating the board list. Please try again.',
+ },
+ ],
+ expectedActions: [{ type: 'fetchLists' }],
+ });
+ });
+});
+
describe('toggleListCollapsed', () => {
it('should commit TOGGLE_LIST_COLLAPSED mutation', async () => {
const payload = { listId: 'gid://gitlab/List/1', collapsed: true };
diff --git a/spec/frontend/boards/stores/mutations_spec.js b/spec/frontend/boards/stores/mutations_spec.js
index 37f0969a39a..a2ba1e9eb5e 100644
--- a/spec/frontend/boards/stores/mutations_spec.js
+++ b/spec/frontend/boards/stores/mutations_spec.js
@@ -165,40 +165,26 @@ describe('Board Store Mutations', () => {
});
});
- describe('MOVE_LIST', () => {
- it('updates boardLists state with reordered lists', () => {
+ describe('MOVE_LISTS', () => {
+ it('updates the positions of board lists', () => {
state = {
...state,
boardLists: initialBoardListsState,
};
- mutations.MOVE_LIST(state, {
- movedList: mockLists[0],
- listAtNewIndex: mockLists[1],
- });
-
- expect(state.boardLists).toEqual({
- 'gid://gitlab/List/2': mockLists[1],
- 'gid://gitlab/List/1': mockLists[0],
- });
- });
- });
-
- describe('UPDATE_LIST_FAILURE', () => {
- it('updates boardLists state with previous order and sets error message', () => {
- state = {
- ...state,
- boardLists: {
- 'gid://gitlab/List/2': mockLists[1],
- 'gid://gitlab/List/1': mockLists[0],
+ mutations.MOVE_LISTS(state, [
+ {
+ listId: mockLists[0].id,
+ position: 1,
},
- error: undefined,
- };
-
- mutations.UPDATE_LIST_FAILURE(state, initialBoardListsState);
+ {
+ listId: mockLists[1].id,
+ position: 0,
+ },
+ ]);
- expect(state.boardLists).toEqual(initialBoardListsState);
- expect(state.error).toEqual('An error occurred while updating the list. Please try again.');
+ expect(state.boardLists[mockLists[0].id].position).toBe(1);
+ expect(state.boardLists[mockLists[1].id].position).toBe(0);
});
});
diff --git a/spec/lib/gitlab/kubernetes/default_namespace_spec.rb b/spec/lib/gitlab/kubernetes/default_namespace_spec.rb
index 976fe4a0a87..b6816a18baa 100644
--- a/spec/lib/gitlab/kubernetes/default_namespace_spec.rb
+++ b/spec/lib/gitlab/kubernetes/default_namespace_spec.rb
@@ -32,6 +32,14 @@ RSpec.describe Gitlab::Kubernetes::DefaultNamespace do
subject { generator.from_environment_slug(environment.slug) }
+ shared_examples_for 'handles very long project paths' do
+ before do
+ allow(project).to receive(:path).and_return 'x' * 100
+ end
+
+ it { is_expected.to satisfy { |s| s.length <= 63 } }
+ end
+
context 'namespace per environment is enabled' do
context 'platform namespace is specified' do
let(:platform_namespace) { 'platform-namespace' }
@@ -47,15 +55,12 @@ RSpec.describe Gitlab::Kubernetes::DefaultNamespace do
context 'platform namespace is blank' do
let(:platform_namespace) { nil }
- let(:mock_namespace) { 'mock-namespace' }
- it 'constructs a namespace from the project and environment' do
- expect(Gitlab::NamespaceSanitizer).to receive(:sanitize)
- .with("#{project.path}-#{project.id}-#{environment.slug}".downcase)
- .and_return(mock_namespace)
-
- expect(subject).to eq mock_namespace
+ it 'constructs a namespace from the project and environment slug' do
+ expect(subject).to eq "path-with-capitals-#{project.id}-#{environment.slug}"
end
+
+ it_behaves_like 'handles very long project paths'
end
end
@@ -70,15 +75,12 @@ RSpec.describe Gitlab::Kubernetes::DefaultNamespace do
context 'platform namespace is blank' do
let(:platform_namespace) { nil }
- let(:mock_namespace) { 'mock-namespace' }
- it 'constructs a namespace from the project and environment' do
- expect(Gitlab::NamespaceSanitizer).to receive(:sanitize)
- .with("#{project.path}-#{project.id}".downcase)
- .and_return(mock_namespace)
-
- expect(subject).to eq mock_namespace
+ it 'constructs a namespace from just the project' do
+ expect(subject).to eq "path-with-capitals-#{project.id}"
end
+
+ it_behaves_like 'handles very long project paths'
end
end
end