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:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-11-30 18:15:02 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-11-30 18:15:02 +0300
commitd0713b807555cbd970ce712d5c0812daee371e2b (patch)
treec871900c005673027c7226415edb7c895a8f6d77
parent3bba41a8c5dfcca0d086eaef10ef36a705dd4f7a (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab/ci/rails.gitlab-ci.yml10
-rw-r--r--.gitlab/ci/rails/shared.gitlab-ci.yml20
-rw-r--r--.gitlab/ci/rules.gitlab-ci.yml3
-rw-r--r--.rubocop_todo/gitlab/namespaced_class.yml1
-rw-r--r--.rubocop_todo/rspec/feature_category.yml1
-rw-r--r--.rubocop_todo/rspec/named_subject.yml1
-rw-r--r--app/assets/javascripts/boards/components/board_app.vue27
-rw-r--r--app/assets/javascripts/boards/components/board_card_inner.vue7
-rw-r--r--app/assets/javascripts/boards/components/board_card_move_to_position.vue23
-rw-r--r--app/assets/javascripts/boards/components/board_column.vue47
-rw-r--r--app/assets/javascripts/boards/components/board_content.vue43
-rw-r--r--app/assets/javascripts/boards/components/board_filtered_search.vue11
-rw-r--r--app/assets/javascripts/boards/components/board_form.vue26
-rw-r--r--app/assets/javascripts/boards/components/board_list.vue3
-rw-r--r--app/assets/javascripts/boards/components/board_list_header.vue92
-rw-r--r--app/assets/javascripts/boards/components/board_new_issue.vue28
-rw-r--r--app/assets/javascripts/boards/components/board_top_bar.vue4
-rw-r--r--app/assets/javascripts/boards/components/sidebar/board_sidebar_title.vue35
-rw-r--r--app/assets/javascripts/boards/index.js2
-rw-r--r--app/assets/javascripts/ci/pipeline_editor/components/header/pipeline_editor_mini_graph.vue5
-rw-r--r--app/assets/javascripts/feature_flags/components/feature_flags.vue3
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/list.vue9
-rw-r--r--app/assets/javascripts/tracking/tracking.js9
-rw-r--r--app/assets/javascripts/usage_quotas/storage/components/project_storage_app.vue9
-rw-r--r--app/assets/javascripts/usage_quotas/storage/queries/cost_factored_project_storage.query.graphql23
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue114
-rw-r--r--app/assets/javascripts/work_items/graphql/group_work_items.query.graphql1
-rw-r--r--app/assets/javascripts/work_items/graphql/project_work_items.query.graphql2
-rw-r--r--app/assets/stylesheets/page_bundles/ide.scss9
-rw-r--r--app/assets/stylesheets/themes/dark_mode_overrides.scss72
-rw-r--r--app/assets/stylesheets/themes/theme_blue.scss8
-rw-r--r--app/assets/stylesheets/themes/theme_gray.scss8
-rw-r--r--app/assets/stylesheets/themes/theme_green.scss8
-rw-r--r--app/assets/stylesheets/themes/theme_helper.scss265
-rw-r--r--app/assets/stylesheets/themes/theme_indigo.scss8
-rw-r--r--app/assets/stylesheets/themes/theme_light_blue.scss8
-rw-r--r--app/assets/stylesheets/themes/theme_light_gray.scss104
-rw-r--r--app/assets/stylesheets/themes/theme_light_green.scss8
-rw-r--r--app/assets/stylesheets/themes/theme_light_indigo.scss8
-rw-r--r--app/assets/stylesheets/themes/theme_light_red.scss8
-rw-r--r--app/assets/stylesheets/themes/theme_red.scss8
-rw-r--r--app/controllers/external_redirect/external_redirect_controller.rb1
-rw-r--r--app/controllers/ide_controller.rb2
-rw-r--r--app/controllers/web_ide/remote_ide_controller.rb2
-rw-r--r--app/models/event.rb1
-rw-r--r--app/models/group.rb2
-rw-r--r--app/models/user.rb3
-rw-r--r--app/models/user_interacted_project.rb39
-rw-r--r--app/views/ide/_show.html.haml8
-rw-r--r--app/views/layouts/fullscreen.html.haml6
-rw-r--r--config/feature_flags/development/ci_guard_for_catalog_resource_scope.yml2
-rw-r--r--config/gitlab_loose_foreign_keys.yml4
-rw-r--r--danger/qa_selector/Dangerfile2
-rw-r--r--db/migrate/20231124112409_add_instance_level_ai_beta_features_enabled_to_app_settings.rb9
-rw-r--r--db/post_migrate/20231128174345_remove_users_project_authorizations_user_id_fk.rb21
-rw-r--r--db/schema_migrations/202311241124091
-rw-r--r--db/schema_migrations/202311281743451
-rw-r--r--db/structure.sql4
-rw-r--r--doc/architecture/blueprints/cells/index.md38
-rw-r--r--doc/architecture/blueprints/cells/routing-service.md96
-rw-r--r--doc/ci/pipelines/merge_request_pipelines.md2
-rw-r--r--doc/development/ai_features/duo_chat.md5
-rw-r--r--doc/development/ai_features/index.md10
-rw-r--r--doc/development/database/understanding_explain_plans.md4
-rw-r--r--doc/development/development_seed_files.md1
-rw-r--r--lib/gitlab/graphql/loaders/full_path_model_loader.rb1
-rw-r--r--locale/gitlab.pot3
-rwxr-xr-xscripts/decomposition/generate-loose-foreign-key4
-rw-r--r--scripts/review_apps/base-config.yaml2
-rw-r--r--spec/features/admin/admin_abuse_reports_spec.rb18
-rw-r--r--spec/features/admin/admin_runners_spec.rb4
-rw-r--r--spec/features/dashboard/issues_filter_spec.rb3
-rw-r--r--spec/features/dashboard/merge_requests_spec.rb8
-rw-r--r--spec/features/groups/issues_spec.rb4
-rw-r--r--spec/features/issuables/sorting_list_spec.rb4
-rw-r--r--spec/features/issues/user_sorts_issues_spec.rb4
-rw-r--r--spec/features/merge_requests/user_sorts_merge_requests_spec.rb6
-rw-r--r--spec/features/projects/work_items/work_item_children_spec.rb27
-rw-r--r--spec/features/user_sorts_things_spec.rb5
-rw-r--r--spec/frontend/boards/board_card_inner_spec.js32
-rw-r--r--spec/frontend/boards/components/board_app_spec.js87
-rw-r--r--spec/frontend/boards/components/board_card_move_to_position_spec.js69
-rw-r--r--spec/frontend/boards/components/board_column_spec.js52
-rw-r--r--spec/frontend/boards/components/board_content_spec.js139
-rw-r--r--spec/frontend/boards/components/board_filtered_search_spec.js101
-rw-r--r--spec/frontend/boards/components/board_form_spec.js74
-rw-r--r--spec/frontend/boards/components/board_list_header_spec.js137
-rw-r--r--spec/frontend/boards/components/board_new_issue_spec.js117
-rw-r--r--spec/frontend/boards/components/board_top_bar_spec.js83
-rw-r--r--spec/frontend/boards/components/sidebar/board_sidebar_title_spec.js93
-rw-r--r--spec/frontend/ci/pipeline_editor/pipeline_editor_home_spec.js23
-rw-r--r--spec/frontend/ci/runner/components/runner_filtered_search_bar_spec.js27
-rw-r--r--spec/frontend/ide/components/repo_commit_section_spec.js7
-rw-r--r--spec/frontend/integrations/edit/components/jira_trigger_fields_spec.js18
-rw-r--r--spec/frontend/integrations/edit/components/trigger_field_spec.js17
-rw-r--r--spec/frontend/integrations/edit/components/trigger_fields_spec.js17
-rw-r--r--spec/frontend/usage_quotas/storage/components/project_storage_app_spec.js32
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_bar_root_spec.js193
-rw-r--r--spec/frontend/work_items/mock_data.js11
-rw-r--r--spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb1
-rw-r--r--spec/models/event_spec.rb9
-rw-r--r--spec/models/group_spec.rb9
-rw-r--r--spec/models/member_spec.rb4
-rw-r--r--spec/models/ml/candidate_spec.rb4
-rw-r--r--spec/models/namespace_spec.rb4
-rw-r--r--spec/models/notification_setting_spec.rb4
-rw-r--r--spec/models/project_authorization_spec.rb7
-rw-r--r--spec/models/route_spec.rb4
-rw-r--r--spec/models/user_interacted_project_spec.rb52
-rw-r--r--spec/requests/ide_controller_spec.rb36
-rw-r--r--spec/support/helpers/features/sorting_helpers.rb8
-rw-r--r--spec/support/rspec_order_todo.yml1
-rw-r--r--spec/views/layouts/fullscreen.html.haml_spec.rb20
113 files changed, 969 insertions, 1961 deletions
diff --git a/.gitlab/ci/rails.gitlab-ci.yml b/.gitlab/ci/rails.gitlab-ci.yml
index c96628f72a2..937bfc7e93d 100644
--- a/.gitlab/ci/rails.gitlab-ci.yml
+++ b/.gitlab/ci/rails.gitlab-ci.yml
@@ -774,11 +774,19 @@ rspec-ee unit gitlab-duo-chat-zeroshot pg14:
rspec-ee unit gitlab-duo-chat-qa-fast pg14:
extends:
- .rspec-ee-base-gitlab-duo
- - .rails:rules:ee-gitlab-duo-chat-qa-fast
+ - .rails:rules:ee-gitlab-duo-chat-always
script:
- !reference [.base-script, script]
- rspec_paralellized_job "--tag fast_chat_qa_evaluation"
+rspec-ee unit gitlab-duo pg14:
+ extends:
+ - .rspec-ee-base-gitlab-duo
+ - .rails:rules:ee-gitlab-duo-chat-always
+ script:
+ - !reference [.base-script, script]
+ - rspec_paralellized_job "--tag gitlab_duo"
+
rspec-ee unit gitlab-duo-chat-qa pg14:
variables:
QA_EVAL_REPORT_FILENAME: "qa_evaluation_report.md"
diff --git a/.gitlab/ci/rails/shared.gitlab-ci.yml b/.gitlab/ci/rails/shared.gitlab-ci.yml
index 90289faef0d..77b0e336c9a 100644
--- a/.gitlab/ci/rails/shared.gitlab-ci.yml
+++ b/.gitlab/ci/rails/shared.gitlab-ci.yml
@@ -85,13 +85,27 @@ include:
- bundle exec gem list gitlab_quality-test_tooling
- |
if [ "$CREATE_RAILS_TEST_FAILURE_ISSUES" == "true" ]; then
- bundle exec relate-failure-issue --input-files "rspec/rspec-*.json" --system-log-files "log" --project "gitlab-org/gitlab" --token "${TEST_FAILURES_PROJECT_TOKEN}" --related-issues-file "rspec/${CI_JOB_ID}-failed-test-issues.json";
+ bundle exec relate-failure-issue \
+ --token "${TEST_FAILURES_PROJECT_TOKEN}" \
+ --project "gitlab-org/gitlab" \
+ --input-files "rspec/rspec-*.json" \
+ --exclude-labels-for-search "QA,rspec:slow test" \
+ --system-log-files "log" \
+ --related-issues-file "rspec/${CI_JOB_ID}-failed-test-issues.json";
fi
if [ "$CREATE_RAILS_SLOW_TEST_ISSUES" == "true" ]; then
- bundle exec slow-test-issues --input-files "rspec/rspec-*.json" --project "gitlab-org/gitlab" --token "${TEST_FAILURES_PROJECT_TOKEN}" --related-issues-file "rspec/${CI_JOB_ID}-slow-test-issues.json";
+ bundle exec slow-test-issues \
+ --token "${TEST_FAILURES_PROJECT_TOKEN}" \
+ --project "gitlab-org/gitlab" \
+ --input-files "rspec/rspec-*.json" \
+ --related-issues-file "rspec/${CI_JOB_ID}-slow-test-issues.json";
fi
if [ "$ADD_SLOW_TEST_NOTE_TO_MERGE_REQUEST" == "true" ]; then
- bundle exec slow-test-merge-request-report-note --input-files "rspec/rspec-*.json" --project "gitlab-org/gitlab" --merge_request_iid "$CI_MERGE_REQUEST_IID" --token "${TEST_SLOW_NOTE_PROJECT_TOKEN}";
+ bundle exec slow-test-merge-request-report-note \
+ --token "${TEST_SLOW_NOTE_PROJECT_TOKEN}" \
+ --project "gitlab-org/gitlab" \
+ --input-files "rspec/rspec-*.json" \
+ --merge_request_iid "$CI_MERGE_REQUEST_IID";
fi
- echo -e "\e[0Ksection_end:`date +%s`:report_results_section\r\e[0K"
- tooling/bin/push_job_metrics || true
diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml
index 22e0531dd27..9e9f24d37bc 100644
--- a/.gitlab/ci/rules.gitlab-ci.yml
+++ b/.gitlab/ci/rules.gitlab-ci.yml
@@ -372,6 +372,7 @@
.ai-patterns: &ai-patterns
- "{,ee/,jh/}lib/gitlab/llm/**/*"
- "{,ee/,jh/}{,spec/}lib/gitlab/llm/**/*"
+ - "{,ee/,jh/}lib/gitlab/duo/**/*"
# DB patterns + .ci-patterns
.db-patterns: &db-patterns
@@ -2166,7 +2167,7 @@
when: manual
allow_failure: true
-.rails:rules:ee-gitlab-duo-chat-qa-fast:
+.rails:rules:ee-gitlab-duo-chat-always:
rules:
- !reference [".rails:rules:ee-gitlab-duo-chat-base", rules]
- <<: *if-merge-request
diff --git a/.rubocop_todo/gitlab/namespaced_class.yml b/.rubocop_todo/gitlab/namespaced_class.yml
index ea2e52b028a..19f31b94a6a 100644
--- a/.rubocop_todo/gitlab/namespaced_class.yml
+++ b/.rubocop_todo/gitlab/namespaced_class.yml
@@ -326,7 +326,6 @@ Gitlab/NamespacedClass:
- 'app/models/user_custom_attribute.rb'
- 'app/models/user_detail.rb'
- 'app/models/user_highest_role.rb'
- - 'app/models/user_interacted_project.rb'
- 'app/models/user_mention.rb'
- 'app/models/user_preference.rb'
- 'app/models/user_status.rb'
diff --git a/.rubocop_todo/rspec/feature_category.yml b/.rubocop_todo/rspec/feature_category.yml
index afc5e9d55c7..5cf13baf9e5 100644
--- a/.rubocop_todo/rspec/feature_category.yml
+++ b/.rubocop_todo/rspec/feature_category.yml
@@ -4754,7 +4754,6 @@ RSpec/FeatureCategory:
- 'spec/models/user_custom_attribute_spec.rb'
- 'spec/models/user_detail_spec.rb'
- 'spec/models/user_highest_role_spec.rb'
- - 'spec/models/user_interacted_project_spec.rb'
- 'spec/models/user_mentions/commit_user_mention_spec.rb'
- 'spec/models/user_mentions/issue_user_mention_spec.rb'
- 'spec/models/user_mentions/merge_request_user_mention_spec.rb'
diff --git a/.rubocop_todo/rspec/named_subject.yml b/.rubocop_todo/rspec/named_subject.yml
index 16784226de8..1802675827d 100644
--- a/.rubocop_todo/rspec/named_subject.yml
+++ b/.rubocop_todo/rspec/named_subject.yml
@@ -2858,7 +2858,6 @@ RSpec/NamedSubject:
- 'spec/models/uploads/fog_spec.rb'
- 'spec/models/uploads/local_spec.rb'
- 'spec/models/user_custom_attribute_spec.rb'
- - 'spec/models/user_interacted_project_spec.rb'
- 'spec/models/user_spec.rb'
- 'spec/models/user_status_spec.rb'
- 'spec/models/users/credit_card_validation_spec.rb'
diff --git a/app/assets/javascripts/boards/components/board_app.vue b/app/assets/javascripts/boards/components/board_app.vue
index 4d915ff341a..2c8aa1cbe21 100644
--- a/app/assets/javascripts/boards/components/board_app.vue
+++ b/app/assets/javascripts/boards/components/board_app.vue
@@ -1,6 +1,4 @@
<script>
-// eslint-disable-next-line no-restricted-imports
-import { mapGetters } from 'vuex';
import { omit } from 'lodash';
import { refreshCurrentPage, queryToObject } from '~/lib/utils/url_utility';
import { s__ } from '~/locale';
@@ -33,11 +31,10 @@ export default {
'isGroupBoard',
'issuableType',
'boardType',
- 'isApolloBoard',
],
data() {
return {
- boardListsApollo: {},
+ boardLists: {},
activeListId: '',
boardId: this.initialBoardId,
filterParams: { ...this.initialFilterParams },
@@ -59,20 +56,14 @@ export default {
this.setActiveId('');
}
},
- skip() {
- return !this.isApolloBoard;
- },
},
- boardListsApollo: {
+ boardLists: {
query() {
return listsQuery[this.issuableType].query;
},
variables() {
return this.listQueryVariables;
},
- skip() {
- return !this.isApolloBoard;
- },
update(data) {
const { lists } = data[this.boardType].board;
return formatBoardLists(lists);
@@ -91,7 +82,6 @@ export default {
},
computed: {
- ...mapGetters(['isSidebarOpen']),
listQueryVariables() {
return {
...(this.isIssueBoard && {
@@ -107,13 +97,10 @@ export default {
return (gon?.licensed_features?.swimlanes && this.isShowingEpicsSwimlanes) ?? false;
},
isAnySidebarOpen() {
- if (this.isApolloBoard) {
- return this.activeBoardItem?.id || this.activeListId;
- }
- return this.isSidebarOpen;
+ return this.activeBoardItem?.id || this.activeListId;
},
activeList() {
- return this.activeListId ? this.boardListsApollo[this.activeListId] : undefined;
+ return this.activeListId ? this.boardLists[this.activeListId] : undefined;
},
formattedFilterParams() {
return filterVariables({
@@ -134,7 +121,7 @@ export default {
},
methods: {
refetchLists() {
- this.$apollo.queries.boardListsApollo.refetch();
+ this.$apollo.queries.boardLists.refetch();
},
setActiveId(id) {
this.activeListId = id;
@@ -167,14 +154,14 @@ export default {
:add-column-form-visible="addColumnFormVisible"
:is-swimlanes-on="isSwimlanesOn"
:filter-params="formattedFilterParams"
- :board-lists-apollo="boardListsApollo"
+ :board-lists="boardLists"
:apollo-error="error"
:list-query-variables="listQueryVariables"
@setActiveList="setActiveId"
@setAddColumnFormVisibility="addColumnFormVisible = $event"
/>
<board-settings-sidebar
- v-if="!isApolloBoard || activeList"
+ v-if="activeList"
:list="activeList"
:list-id="activeListId"
:board-id="boardId"
diff --git a/app/assets/javascripts/boards/components/board_card_inner.vue b/app/assets/javascripts/boards/components/board_card_inner.vue
index 2c9ff3105bf..06a8acc0c04 100644
--- a/app/assets/javascripts/boards/components/board_card_inner.vue
+++ b/app/assets/javascripts/boards/components/board_card_inner.vue
@@ -8,8 +8,6 @@ import {
GlSprintf,
} from '@gitlab/ui';
import { sortBy } from 'lodash';
-// eslint-disable-next-line no-restricted-imports
-import { mapActions } from 'vuex';
import boardCardInner from 'ee_else_ce/boards/mixins/board_card_inner';
import { isScopedLabel } from '~/lib/utils/common_utils';
import { updateHistory } from '~/lib/utils/url_utility';
@@ -51,7 +49,6 @@ export default {
'isEpicBoard',
'issuableType',
'isGroupBoard',
- 'isApolloBoard',
],
props: {
item: {
@@ -187,7 +184,6 @@ export default {
},
},
methods: {
- ...mapActions(['performSearch']),
setError,
isIndexLessThanlimit(index) {
return index < this.limitBeforeCounter;
@@ -225,9 +221,6 @@ export default {
updateHistory({
url: `${filterPath}${filter}`,
});
- if (!this.isApolloBoard) {
- this.performSearch();
- }
eventHub.$emit('updateTokens');
}
},
diff --git a/app/assets/javascripts/boards/components/board_card_move_to_position.vue b/app/assets/javascripts/boards/components/board_card_move_to_position.vue
index 8034819732a..9173503c888 100644
--- a/app/assets/javascripts/boards/components/board_card_move_to_position.vue
+++ b/app/assets/javascripts/boards/components/board_card_move_to_position.vue
@@ -1,7 +1,5 @@
<script>
import { GlDisclosureDropdown } from '@gitlab/ui';
-// eslint-disable-next-line no-restricted-imports
-import { mapActions, mapState } from 'vuex';
import Tracking from '~/tracking';
import {
BOARD_CARD_MOVE_TO_POSITIONS_OPTIONS,
@@ -15,7 +13,6 @@ export default {
GlDisclosureDropdown,
},
mixins: [Tracking.mixin()],
- inject: ['isApolloBoard'],
props: {
item: {
type: Object,
@@ -37,7 +34,6 @@ export default {
},
},
computed: {
- ...mapState(['pageInfoByListId']),
tracking() {
return {
category: 'boards:list',
@@ -45,9 +41,6 @@ export default {
property: `type_card`,
};
},
- listHasNextPage() {
- return this.pageInfoByListId[this.list.id]?.hasNextPage;
- },
itemIdentifier() {
return `${this.item.id}-${this.item.iid}-${this.index}`;
},
@@ -59,7 +52,6 @@ export default {
},
},
methods: {
- ...mapActions(['moveItem']),
moveToStart() {
this.track('click_toggle_button', {
label: 'move_to_start',
@@ -85,20 +77,7 @@ export default {
});
},
moveToPosition({ positionInList }) {
- if (this.isApolloBoard) {
- this.$emit('moveToPosition', positionInList);
- } else {
- this.moveItem({
- itemId: this.item.id,
- itemIid: this.item.iid,
- itemPath: this.item.referencePath,
- fromListId: this.list.id,
- toListId: this.list.id,
- positionInList,
- atIndex: this.index,
- allItemsLoadedInList: !this.listHasNextPage,
- });
- }
+ this.$emit('moveToPosition', positionInList);
},
selectMoveAction({ text }) {
if (text === BOARD_CARD_MOVE_TO_POSITIONS_START_OPTION) {
diff --git a/app/assets/javascripts/boards/components/board_column.vue b/app/assets/javascripts/boards/components/board_column.vue
index 67a4c5eba45..0ba8b958428 100644
--- a/app/assets/javascripts/boards/components/board_column.vue
+++ b/app/assets/javascripts/boards/components/board_column.vue
@@ -1,6 +1,4 @@
<script>
-// eslint-disable-next-line no-restricted-imports
-import { mapGetters, mapActions, mapState } from 'vuex';
import BoardListHeader from 'ee_else_ce/boards/components/board_list_header.vue';
import { isListDraggable } from '../boards_util';
import BoardList from './board_list.vue';
@@ -10,7 +8,6 @@ export default {
BoardListHeader,
BoardList,
},
- inject: ['isApolloBoard'],
props: {
list: {
type: Object,
@@ -25,48 +22,21 @@ export default {
type: Object,
required: true,
},
- highlightedListsApollo: {
+ highlightedLists: {
type: Array,
required: false,
default: () => [],
},
},
computed: {
- ...mapState(['filterParams', 'highlightedLists']),
- ...mapGetters(['getBoardItemsByList']),
- highlightedListsToUse() {
- return this.isApolloBoard ? this.highlightedListsApollo : this.highlightedLists;
- },
highlighted() {
- return this.highlightedListsToUse.includes(this.list.id);
- },
- listItems() {
- return this.isApolloBoard ? [] : this.getBoardItemsByList(this.list.id);
+ return this.highlightedLists.includes(this.list.id);
},
isListDraggable() {
return isListDraggable(this.list);
},
- filtersToUse() {
- return this.isApolloBoard ? this.filters : this.filterParams;
- },
},
watch: {
- filterParams: {
- handler() {
- if (!this.isApolloBoard && this.list.id && !this.list.collapsed) {
- this.fetchItemsForList({ listId: this.list.id });
- }
- },
- deep: true,
- immediate: true,
- },
- 'list.id': {
- handler(id) {
- if (!this.isApolloBoard && id) {
- this.fetchItemsForList({ listId: this.list.id });
- }
- },
- },
highlighted: {
handler(highlighted) {
if (highlighted) {
@@ -78,9 +48,6 @@ export default {
immediate: true,
},
},
- methods: {
- ...mapActions(['fetchItemsForList']),
- },
};
</script>
@@ -101,17 +68,11 @@ export default {
>
<board-list-header
:list="list"
- :filter-params="filtersToUse"
+ :filter-params="filters"
:board-id="boardId"
@setActiveList="$emit('setActiveList', $event)"
/>
- <board-list
- ref="board-list"
- :board-id="boardId"
- :board-items="listItems"
- :list="list"
- :filter-params="filtersToUse"
- />
+ <board-list ref="board-list" :board-id="boardId" :list="list" :filter-params="filters" />
</div>
</div>
</template>
diff --git a/app/assets/javascripts/boards/components/board_content.vue b/app/assets/javascripts/boards/components/board_content.vue
index a6ff1653c17..9560f0e5fef 100644
--- a/app/assets/javascripts/boards/components/board_content.vue
+++ b/app/assets/javascripts/boards/components/board_content.vue
@@ -3,8 +3,6 @@ import { GlAlert } from '@gitlab/ui';
import { sortBy } from 'lodash';
import produce from 'immer';
import Draggable from 'vuedraggable';
-// eslint-disable-next-line no-restricted-imports
-import { mapState, mapActions } from 'vuex';
import BoardAddNewColumn from 'ee_else_ce/boards/components/board_add_new_column.vue';
import { s__ } from '~/locale';
import { defaultSortableOptions } from '~/sortable/constants';
@@ -29,15 +27,7 @@ export default {
EpicsSwimlanes: () => import('ee_component/boards/components/epics_swimlanes.vue'),
GlAlert,
},
- inject: [
- 'boardType',
- 'canAdminList',
- 'isIssueBoard',
- 'isEpicBoard',
- 'disabled',
- 'issuableType',
- 'isApolloBoard',
- ],
+ inject: ['boardType', 'canAdminList', 'isIssueBoard', 'isEpicBoard', 'disabled', 'issuableType'],
props: {
boardId: {
type: String,
@@ -51,7 +41,7 @@ export default {
type: Boolean,
required: true,
},
- boardListsApollo: {
+ boardLists: {
type: Object,
required: false,
default: () => {},
@@ -77,12 +67,11 @@ export default {
};
},
computed: {
- ...mapState(['boardLists', 'error']),
boardListsById() {
- return this.isApolloBoard ? this.boardListsApollo : this.boardLists;
+ return this.boardLists;
},
boardListsToUse() {
- const lists = this.isApolloBoard ? this.boardListsApollo : this.boardLists;
+ const lists = this.boardLists;
return sortBy([...Object.values(lists)], 'position');
},
canDragColumns() {
@@ -109,11 +98,10 @@ export default {
return this.canDragColumns ? options : {};
},
errorToDisplay() {
- return this.apolloError || this.error || null;
+ return this.apolloError || null;
},
},
methods: {
- ...mapActions(['moveList', 'unsetError']),
afterFormEnters() {
const el = this.canDragColumns ? this.$refs.list.$el : this.$refs.list;
el.scrollTo({ left: el.scrollWidth, behavior: 'smooth' });
@@ -126,11 +114,7 @@ export default {
}, flashAnimationDuration);
},
dismissError() {
- if (this.isApolloBoard) {
- setError({ message: null, captureError: false });
- } else {
- this.unsetError();
- }
+ setError({ message: null, captureError: false });
},
async updateListPosition({
item: {
@@ -139,17 +123,6 @@ export default {
newIndex,
to: { children },
}) {
- if (!this.isApolloBoard) {
- this.moveList({
- item: {
- dataset: { listId: movedListId, draggableItemType },
- },
- newIndex,
- to: { children },
- });
- return;
- }
-
if (draggableItemType !== DraggableItemTypes.list) {
return;
}
@@ -199,7 +172,7 @@ export default {
__typename: 'UpdateBoardListPayload',
errors: [],
list: {
- ...this.boardListsApollo[movedListId],
+ ...this.boardLists[movedListId],
position: targetPosition,
},
},
@@ -240,7 +213,7 @@ export default {
:board-id="boardId"
:list="list"
:filters="filterParams"
- :highlighted-lists-apollo="highlightedLists"
+ :highlighted-lists="highlightedLists"
:data-draggable-item-type="$options.draggableItemTypes.list"
:class="{ 'gl-display-none! gl-sm-display-inline-block!': addColumnFormVisible }"
@setActiveList="$emit('setActiveList', $event)"
diff --git a/app/assets/javascripts/boards/components/board_filtered_search.vue b/app/assets/javascripts/boards/components/board_filtered_search.vue
index 9443154999b..faaef226c21 100644
--- a/app/assets/javascripts/boards/components/board_filtered_search.vue
+++ b/app/assets/javascripts/boards/components/board_filtered_search.vue
@@ -1,7 +1,5 @@
<script>
import { pickBy, isEmpty, mapValues } from 'lodash';
-// eslint-disable-next-line no-restricted-imports
-import { mapActions } from 'vuex';
import { getIdFromGraphQLId, isGid } from '~/graphql_shared/utils';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { updateHistory, setUrlParams, queryToObject } from '~/lib/utils/url_utility';
@@ -31,7 +29,7 @@ export default {
search: __('Search'),
},
components: { FilteredSearch },
- inject: ['initialFilterParams', 'isApolloBoard'],
+ inject: ['initialFilterParams'],
props: {
isSwimlanesOn: {
type: Boolean,
@@ -353,7 +351,6 @@ export default {
eventHub.$off('updateTokens', this.updateTokens);
},
methods: {
- ...mapActions(['performSearch']),
formattedFilterParams() {
const rawFilterParams = queryToObject(window.location.search, { gatherArrays: true });
const filtersCopy = convertObjectPropsToCamelCase(rawFilterParams, {});
@@ -374,11 +371,7 @@ export default {
replace: true,
});
- if (this.isApolloBoard) {
- this.$emit('setFilters', this.formattedFilterParams());
- } else {
- this.performSearch();
- }
+ this.$emit('setFilters', this.formattedFilterParams());
},
getFilterParams(filters = []) {
const notFilters = filters.filter((item) => item.value.operator === '!=');
diff --git a/app/assets/javascripts/boards/components/board_form.vue b/app/assets/javascripts/boards/components/board_form.vue
index a3d55ac8306..5f4917ea487 100644
--- a/app/assets/javascripts/boards/components/board_form.vue
+++ b/app/assets/javascripts/boards/components/board_form.vue
@@ -1,9 +1,6 @@
<script>
import { GlModal, GlAlert } from '@gitlab/ui';
-// eslint-disable-next-line no-restricted-imports
-import { mapActions } from 'vuex';
-import { getIdFromGraphQLId } from '~/graphql_shared/utils';
-import { visitUrl, updateHistory, getParameterByName } from '~/lib/utils/url_utility';
+import { visitUrl } from '~/lib/utils/url_utility';
import { __, s__ } from '~/locale';
import eventHub from '~/boards/eventhub';
import { formType } from '../constants';
@@ -61,9 +58,6 @@ export default {
isProjectBoard: {
default: false,
},
- isApolloBoard: {
- default: false,
- },
},
props: {
canAdminBoard: {
@@ -184,7 +178,6 @@ export default {
}
},
methods: {
- ...mapActions(['setBoard']),
setError,
isFocusMode() {
return Boolean(document.querySelector('.content-wrapper > .js-focus-mode-board.is-focused'));
@@ -227,23 +220,12 @@ export default {
} else {
try {
const board = await this.createOrUpdateBoard();
- if (this.isApolloBoard) {
- if (this.board.id) {
- eventHub.$emit('updateBoard', board);
- } else {
- this.$emit('addBoard', board);
- }
+ if (this.board.id) {
+ eventHub.$emit('updateBoard', board);
} else {
- this.setBoard(board);
+ this.$emit('addBoard', board);
}
this.cancel();
-
- if (!this.isApolloBoard) {
- const param = getParameterByName('group_by')
- ? `?group_by=${getParameterByName('group_by')}`
- : '';
- updateHistory({ url: `${this.boardBaseUrl}/${getIdFromGraphQLId(board.id)}${param}` });
- }
} catch (error) {
setError({ error, message: this.$options.i18n.saveErrorMessage });
} finally {
diff --git a/app/assets/javascripts/boards/components/board_list.vue b/app/assets/javascripts/boards/components/board_list.vue
index ca10cbbad5e..1b515d695cb 100644
--- a/app/assets/javascripts/boards/components/board_list.vue
+++ b/app/assets/javascripts/boards/components/board_list.vue
@@ -70,7 +70,8 @@ export default {
},
boardItems: {
type: Array,
- required: true,
+ required: false, // This is temporary while we remove :apollo_board FF
+ default: () => [],
},
filterParams: {
type: Object,
diff --git a/app/assets/javascripts/boards/components/board_list_header.vue b/app/assets/javascripts/boards/components/board_list_header.vue
index bedb3a75a70..f50c510fcf6 100644
--- a/app/assets/javascripts/boards/components/board_list_header.vue
+++ b/app/assets/javascripts/boards/components/board_list_header.vue
@@ -8,14 +8,11 @@ import {
GlSprintf,
GlTooltipDirective,
} from '@gitlab/ui';
-// eslint-disable-next-line no-restricted-imports
-import { mapActions, mapState } from 'vuex';
import { isListDraggable } from '~/boards/boards_util';
import { isScopedLabel, parseBoolean } from '~/lib/utils/common_utils';
import { fetchPolicies } from '~/lib/graphql';
import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
import { n__, s__ } from '~/locale';
-import sidebarEventHub from '~/sidebar/event_hub';
import Tracking from '~/tracking';
import { TYPE_ISSUE } from '~/issues/constants';
import { formatDate } from '~/lib/utils/datetime_utility';
@@ -23,8 +20,6 @@ import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import setActiveBoardItemMutation from 'ee_else_ce/boards/graphql/client/set_active_board_item.mutation.graphql';
import AccessorUtilities from '~/lib/utils/accessor';
import {
- inactiveId,
- LIST,
ListType,
toggleFormEventPrefix,
updateListQueries,
@@ -81,9 +76,6 @@ export default {
issuableType: {
default: TYPE_ISSUE,
},
- isApolloBoard: {
- default: false,
- },
},
props: {
list: {
@@ -106,7 +98,6 @@ export default {
},
},
computed: {
- ...mapState(['activeId']),
isLoggedIn() {
return Boolean(this.currentUserId);
},
@@ -238,21 +229,12 @@ export default {
}
},
methods: {
- ...mapActions(['updateList', 'setActiveId', 'toggleListCollapsed']),
openSidebarSettings() {
- if (this.activeId === inactiveId) {
- sidebarEventHub.$emit('sidebar.closeAll');
- }
-
- if (this.isApolloBoard) {
- this.$apollo.mutate({
- mutation: setActiveBoardItemMutation,
- variables: { boardItem: null },
- });
- this.$emit('setActiveList', this.list.id);
- } else {
- this.setActiveId({ id: this.list.id, sidebarType: LIST });
- }
+ this.$apollo.mutate({
+ mutation: setActiveBoardItemMutation,
+ variables: { boardItem: null },
+ });
+ this.$emit('setActiveList', this.list.id);
this.track('click_button', { label: 'list_settings' });
},
@@ -297,33 +279,29 @@ export default {
}
},
async updateListFunction(collapsed) {
- if (this.isApolloBoard) {
- try {
- await this.$apollo.mutate({
- mutation: updateListQueries[this.issuableType].mutation,
- variables: {
- listId: this.list.id,
- collapsed,
- },
- optimisticResponse: {
- updateBoardList: {
- __typename: 'UpdateBoardListPayload',
- errors: [],
- list: {
- ...this.list,
- collapsed,
- },
+ try {
+ await this.$apollo.mutate({
+ mutation: updateListQueries[this.issuableType].mutation,
+ variables: {
+ listId: this.list.id,
+ collapsed,
+ },
+ optimisticResponse: {
+ updateBoardList: {
+ __typename: 'UpdateBoardListPayload',
+ errors: [],
+ list: {
+ ...this.list,
+ collapsed,
},
},
- });
- } catch (error) {
- setError({
- error,
- message: s__('Boards|An error occurred while updating the list. Please try again.'),
- });
- }
- } else {
- this.updateList({ listId: this.list.id, collapsed });
+ },
+ });
+ } catch (error) {
+ setError({
+ error,
+ message: s__('Boards|An error occurred while updating the list. Please try again.'),
+ });
}
},
/**
@@ -337,17 +315,13 @@ export default {
return `${start} - ${due}`;
},
updateLocalCollapsedStatus(collapsed) {
- if (this.isApolloBoard) {
- this.$apollo.mutate({
- mutation: toggleCollapsedMutations[this.issuableType].mutation,
- variables: {
- list: this.list,
- collapsed,
- },
- });
- } else {
- this.toggleListCollapsed({ listId: this.list.id, collapsed });
- }
+ this.$apollo.mutate({
+ mutation: toggleCollapsedMutations[this.issuableType].mutation,
+ variables: {
+ list: this.list,
+ collapsed,
+ },
+ });
},
},
};
diff --git a/app/assets/javascripts/boards/components/board_new_issue.vue b/app/assets/javascripts/boards/components/board_new_issue.vue
index d78b60e91a8..ea22bb08f2a 100644
--- a/app/assets/javascripts/boards/components/board_new_issue.vue
+++ b/app/assets/javascripts/boards/components/board_new_issue.vue
@@ -1,6 +1,4 @@
<script>
-// eslint-disable-next-line no-restricted-imports
-import { mapActions, mapGetters } from 'vuex';
import { s__ } from '~/locale';
import { getMilestone, formatIssueInput, getBoardQuery } from 'ee_else_ce/boards/boards_util';
import BoardNewIssueMixin from 'ee_else_ce/boards/mixins/board_new_issue';
@@ -22,7 +20,7 @@ export default {
ProjectSelect,
},
mixins: [BoardNewIssueMixin],
- inject: ['boardType', 'groupId', 'fullPath', 'isGroupBoard', 'isEpicBoard', 'isApolloBoard'],
+ inject: ['boardType', 'groupId', 'fullPath', 'isGroupBoard', 'isEpicBoard'],
props: {
list: {
type: Object,
@@ -50,9 +48,6 @@ export default {
boardId: this.boardId,
};
},
- skip() {
- return !this.isApolloBoard;
- },
update(data) {
const { board } = data.workspace;
return {
@@ -69,7 +64,6 @@ export default {
},
},
computed: {
- ...mapGetters(['getBoardItemsByList']),
formEventPrefix() {
return toggleFormEventPrefix.issue;
},
@@ -81,37 +75,19 @@ export default {
},
},
methods: {
- ...mapActions(['addListNewIssue']),
submit({ title }) {
const labels = this.list.label ? [this.list.label] : [];
const assignees = this.list.assignee ? [this.list.assignee] : [];
const milestone = getMilestone(this.list);
- if (this.isApolloBoard) {
- return this.addNewIssueToList({
- issueInput: {
- title,
- labelIds: labels?.map((l) => l.id),
- assigneeIds: assignees?.map((a) => a?.id),
- milestoneId: milestone?.id,
- projectPath: this.projectPath,
- },
- });
- }
-
- const firstItemId = this.getBoardItemsByList(this.list.id)[0]?.id;
- return this.addListNewIssue({
- list: this.list,
+ return this.addNewIssueToList({
issueInput: {
title,
labelIds: labels?.map((l) => l.id),
assigneeIds: assignees?.map((a) => a?.id),
milestoneId: milestone?.id,
projectPath: this.projectPath,
- moveAfterId: firstItemId,
},
- }).then(() => {
- this.cancel();
});
},
addNewIssueToList({ issueInput }) {
diff --git a/app/assets/javascripts/boards/components/board_top_bar.vue b/app/assets/javascripts/boards/components/board_top_bar.vue
index 31664c28831..14e67c921d8 100644
--- a/app/assets/javascripts/boards/components/board_top_bar.vue
+++ b/app/assets/javascripts/boards/components/board_top_bar.vue
@@ -31,7 +31,6 @@ export default {
'fullPath',
'boardType',
'isEpicBoard',
- 'isApolloBoard',
],
props: {
boardId: {
@@ -63,9 +62,6 @@ export default {
boardId: this.boardId,
};
},
- skip() {
- return !this.isApolloBoard;
- },
update(data) {
const { board } = data.workspace;
return {
diff --git a/app/assets/javascripts/boards/components/sidebar/board_sidebar_title.vue b/app/assets/javascripts/boards/components/sidebar/board_sidebar_title.vue
index a2c4b42b6c5..f86bab40c93 100644
--- a/app/assets/javascripts/boards/components/sidebar/board_sidebar_title.vue
+++ b/app/assets/javascripts/boards/components/sidebar/board_sidebar_title.vue
@@ -1,7 +1,5 @@
<script>
import { GlAlert, GlButton, GlForm, GlFormGroup, GlFormInput, GlLink } from '@gitlab/ui';
-// eslint-disable-next-line no-restricted-imports
-import { mapGetters, mapActions } from 'vuex';
import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue';
import { joinPaths } from '~/lib/utils/url_utility';
import { __ } from '~/locale';
@@ -22,7 +20,7 @@ export default {
directives: {
autofocusonshow,
},
- inject: ['fullPath', 'issuableType', 'isEpicBoard', 'isApolloBoard'],
+ inject: ['fullPath', 'issuableType', 'isEpicBoard'],
props: {
activeItem: {
type: Object,
@@ -37,15 +35,11 @@ export default {
};
},
computed: {
- ...mapGetters(['activeBoardItem']),
- item() {
- return this.isApolloBoard ? this.activeItem : this.activeBoardItem;
- },
pendingChangesStorageKey() {
- return this.getPendingChangesKey(this.item);
+ return this.getPendingChangesKey(this.activeItem);
},
projectPath() {
- const referencePath = this.item.referencePath || '';
+ const referencePath = this.activeItem.referencePath || '';
return referencePath.slice(0, referencePath.indexOf('#'));
},
validationState() {
@@ -53,7 +47,7 @@ export default {
},
},
watch: {
- item: {
+ activeItem: {
handler(updatedItem, formerItem) {
if (formerItem?.title !== this.title) {
localStorage.setItem(this.getPendingChangesKey(formerItem), this.title);
@@ -66,7 +60,6 @@ export default {
},
},
methods: {
- ...mapActions(['setActiveItemTitle']),
getPendingChangesKey(item) {
if (!item) {
return '';
@@ -92,16 +85,12 @@ export default {
}
},
cancel() {
- this.title = this.item.title;
+ this.title = this.activeItem.title;
this.$refs.sidebarItem.collapse();
this.showChangesAlert = false;
localStorage.removeItem(this.pendingChangesStorageKey);
},
async setActiveBoardItemTitle() {
- if (!this.isApolloBoard) {
- await this.setActiveItemTitle({ title: this.title, projectPath: this.projectPath });
- return;
- }
const { fullPath, issuableType, isEpicBoard, title } = this;
const workspacePath = isEpicBoard
? { groupPath: fullPath }
@@ -111,7 +100,7 @@ export default {
variables: {
input: {
...workspacePath,
- iid: String(this.item.iid),
+ iid: String(this.activeItem.iid),
title,
},
},
@@ -120,7 +109,7 @@ export default {
async setTitle() {
this.$refs.sidebarItem.collapse();
- if (!this.title || this.title === this.item.title) {
+ if (!this.title || this.title === this.activeItem.title) {
return;
}
@@ -130,14 +119,14 @@ export default {
localStorage.removeItem(this.pendingChangesStorageKey);
this.showChangesAlert = false;
} catch (e) {
- this.title = this.item.title;
+ this.title = this.activeItem.title;
setError({ error: e, message: this.$options.i18n.updateTitleError });
} finally {
this.loading = false;
}
},
handleOffClick() {
- if (this.title !== this.item.title) {
+ if (this.title !== this.activeItem.title) {
this.showChangesAlert = true;
localStorage.setItem(this.pendingChangesStorageKey, this.title);
} else {
@@ -166,13 +155,13 @@ export default {
>
<template #title>
<span data-testid="item-title">
- <gl-link class="gl-reset-color gl-hover-text-blue-800" :href="item.webUrl">
- {{ item.title }}
+ <gl-link class="gl-reset-color gl-hover-text-blue-800" :href="activeItem.webUrl">
+ {{ activeItem.title }}
</gl-link>
</span>
</template>
<template #collapsed>
- <span class="gl-text-gray-800">{{ item.referencePath }}</span>
+ <span class="gl-text-gray-800">{{ activeItem.referencePath }}</span>
</template>
<gl-alert v-if="showChangesAlert" variant="warning" class="gl-mb-5" :dismissible="false">
{{ $options.i18n.reviewYourChanges }}
diff --git a/app/assets/javascripts/boards/index.js b/app/assets/javascripts/boards/index.js
index 9d7b7a38c6d..72b8aef31a4 100644
--- a/app/assets/javascripts/boards/index.js
+++ b/app/assets/javascripts/boards/index.js
@@ -24,7 +24,7 @@ const apolloProvider = new VueApollo({
function mountBoardApp(el) {
const { boardId, groupId, fullPath, rootPath } = el.dataset;
- const isApolloBoard = window.gon?.features?.apolloBoards;
+ const isApolloBoard = true;
const rawFilterParams = queryToObject(window.location.search, { gatherArrays: true });
diff --git a/app/assets/javascripts/ci/pipeline_editor/components/header/pipeline_editor_mini_graph.vue b/app/assets/javascripts/ci/pipeline_editor/components/header/pipeline_editor_mini_graph.vue
index f00098105d3..f76243e81b9 100644
--- a/app/assets/javascripts/ci/pipeline_editor/components/header/pipeline_editor_mini_graph.vue
+++ b/app/assets/javascripts/ci/pipeline_editor/components/header/pipeline_editor_mini_graph.vue
@@ -19,6 +19,11 @@ export default {
required: true,
},
},
+ data() {
+ return {
+ linkedPipelines: null,
+ };
+ },
apollo: {
linkedPipelines: {
query: getLinkedPipelinesQuery,
diff --git a/app/assets/javascripts/feature_flags/components/feature_flags.vue b/app/assets/javascripts/feature_flags/components/feature_flags.vue
index e0a5e92564e..0cc136e79a5 100644
--- a/app/assets/javascripts/feature_flags/components/feature_flags.vue
+++ b/app/assets/javascripts/feature_flags/components/feature_flags.vue
@@ -50,12 +50,13 @@ export default {
'instanceId',
'isRotating',
'hasRotateError',
+ 'rotateEndpoint',
]),
topAreaBaseClasses() {
return ['gl-display-flex', 'gl-flex-direction-column'];
},
canUserRotateToken() {
- return this.rotateInstanceIdPath !== '';
+ return this.rotateEndpoint !== '';
},
shouldRenderPagination() {
return (
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/list.vue b/app/assets/javascripts/ide/components/commit_sidebar/list.vue
index 76d3acb8e1f..d7bf42ff559 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/list.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/list.vue
@@ -3,7 +3,7 @@
import { GlButton, GlModal, GlTooltipDirective } from '@gitlab/ui';
// eslint-disable-next-line no-restricted-imports
import { mapActions } from 'vuex';
-import { __, sprintf } from '~/locale';
+import { __ } from '~/locale';
import ListItem from './list_item.vue';
export default {
@@ -55,11 +55,6 @@ export default {
},
},
computed: {
- titleText() {
- if (!this.title) return __('Changes');
-
- return sprintf(__('%{title} changes'), { title: this.title });
- },
filesLength() {
return this.fileList.length;
},
@@ -84,7 +79,7 @@ export default {
<div class="ide-commit-list-container">
<header class="multi-file-commit-panel-header gl-display-flex gl-mb-0">
<div class="gl-display-flex gl-align-items-center flex-fill">
- <strong> {{ titleText }} </strong>
+ <strong> {{ __('Changes') }} </strong>
<div class="gl-display-flex gl-ml-auto">
<gl-button
v-if="!stagedList"
diff --git a/app/assets/javascripts/tracking/tracking.js b/app/assets/javascripts/tracking/tracking.js
index 923aea433f1..f4c8781ae20 100644
--- a/app/assets/javascripts/tracking/tracking.js
+++ b/app/assets/javascripts/tracking/tracking.js
@@ -13,12 +13,17 @@ const Tracking = Object.assign(Tracker, {
return {
computed: {
trackingCategory() {
- const localCategory = this.tracking ? this.tracking.category : null;
+ // TODO: refactor to remove potentially undefined property
+ // https://gitlab.com/gitlab-org/gitlab/-/issues/432995
+ const localCategory = 'tracking' in this ? this.tracking.category : null;
return localCategory || opts.category;
},
trackingOptions() {
const options = addExperimentContext(opts);
- return { ...options, ...this.tracking };
+ // TODO: refactor to remove potentially undefined property
+ // https://gitlab.com/gitlab-org/gitlab/-/issues/432995
+ const tracking = 'tracking' in this ? this.tracking : {};
+ return { ...options, ...tracking };
},
},
methods: {
diff --git a/app/assets/javascripts/usage_quotas/storage/components/project_storage_app.vue b/app/assets/javascripts/usage_quotas/storage/components/project_storage_app.vue
index 8075e640720..cc4219c2ca9 100644
--- a/app/assets/javascripts/usage_quotas/storage/components/project_storage_app.vue
+++ b/app/assets/javascripts/usage_quotas/storage/components/project_storage_app.vue
@@ -4,6 +4,8 @@ import { sprintf } from '~/locale';
import { updateRepositorySize } from '~/api/projects_api';
import { numberToHumanSize } from '~/lib/utils/number_utils';
import SectionedPercentageBar from '~/usage_quotas/components/sectioned_percentage_bar.vue';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import getCostFactoredProjectStorageStatistics from 'ee_else_ce/usage_quotas/storage/queries/cost_factored_project_storage.query.graphql';
import getProjectStorageStatistics from 'ee_else_ce/usage_quotas/storage/queries/project_storage.query.graphql';
import {
ERROR_MESSAGE,
@@ -32,10 +34,15 @@ export default {
ProjectStorageDetail,
SectionedPercentageBar,
},
+ mixins: [glFeatureFlagsMixin()],
inject: ['projectPath'],
apollo: {
project: {
- query: getProjectStorageStatistics,
+ query() {
+ return this.glFeatures?.displayCostFactoredStorageSizeOnProjectPages
+ ? getCostFactoredProjectStorageStatistics
+ : getProjectStorageStatistics;
+ },
variables() {
return {
fullPath: this.projectPath,
diff --git a/app/assets/javascripts/usage_quotas/storage/queries/cost_factored_project_storage.query.graphql b/app/assets/javascripts/usage_quotas/storage/queries/cost_factored_project_storage.query.graphql
new file mode 100644
index 00000000000..4438ad4cc3d
--- /dev/null
+++ b/app/assets/javascripts/usage_quotas/storage/queries/cost_factored_project_storage.query.graphql
@@ -0,0 +1,23 @@
+query getCostFactoredProjectStorageStatistics($fullPath: ID!) {
+ project(fullPath: $fullPath) {
+ id
+ statisticsDetailsPaths {
+ containerRegistry
+ buildArtifacts
+ packages
+ repository
+ snippets
+ wiki
+ }
+ statistics {
+ containerRegistrySize
+ buildArtifactsSize
+ lfsObjectsSize
+ packagesSize
+ repositorySize
+ snippetsSize
+ storageSize
+ wikiSize
+ }
+ }
+}
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue
index d39e4d2ee42..364ba10e888 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue
@@ -1,13 +1,5 @@
<script>
-import {
- GlFilteredSearch,
- GlButtonGroup,
- GlButton,
- GlDropdown,
- GlDropdownItem,
- GlFormCheckbox,
- GlTooltipDirective,
-} from '@gitlab/ui';
+import { GlFilteredSearch, GlSorting, GlFormCheckbox, GlTooltipDirective } from '@gitlab/ui';
import RecentSearchesStorageKeys from 'ee_else_ce/filtered_search/recent_searches_storage_keys';
import RecentSearchesService from '~/filtered_search/services/recent_searches_service';
@@ -22,10 +14,7 @@ import { filterEmptySearchTerm, uniqueTokens } from './filtered_search_utils';
export default {
components: {
GlFilteredSearch,
- GlButtonGroup,
- GlButton,
- GlDropdown,
- GlDropdownItem,
+ GlSorting,
GlFormCheckbox,
},
directives: {
@@ -118,8 +107,7 @@ export default {
recentSearchesPromise: null,
recentSearches: [],
filterValue: this.initialFilterValue,
- selectedSortOption: this.sortOptions[0],
- selectedSortDirection: SORT_DIRECTION.descending,
+ ...this.getInitialSort(),
};
},
computed: {
@@ -141,15 +129,14 @@ export default {
{},
);
},
- sortDirectionIcon() {
- return this.selectedSortDirection === SORT_DIRECTION.ascending
- ? 'sort-lowest'
- : 'sort-highest';
+ transformedSortOptions() {
+ return this.sortOptions.map(({ id: value, title: text }) => ({ value, text }));
},
- sortDirectionTooltip() {
- return this.selectedSortDirection === SORT_DIRECTION.ascending
- ? __('Sort direction: Ascending')
- : __('Sort direction: Descending');
+ selectedSortDirection() {
+ return this.sortDirectionAscending ? SORT_DIRECTION.ascending : SORT_DIRECTION.descending;
+ },
+ selectedSortOption() {
+ return this.sortOptions.find((sortOption) => sortOption.id === this.sortById);
},
/**
* This prop fixes a behaviour affecting GlFilteredSearch
@@ -184,14 +171,13 @@ export default {
this.filterValue = newValue;
}
},
- initialSortBy(newValue) {
- if (this.syncFilterAndSort) {
- this.updateSelectedSortValues(newValue);
+ initialSortBy(newInitialSortBy) {
+ if (this.syncFilterAndSort && newInitialSortBy) {
+ this.updateSelectedSortValues();
}
},
},
created() {
- this.updateSelectedSortValues(this.initialSortBy);
if (this.recentSearchesStorageKey) this.setupRecentSearch();
},
methods: {
@@ -273,15 +259,12 @@ export default {
return filter;
});
},
- handleSortOptionClick(sortBy) {
- this.selectedSortOption = sortBy;
- this.$emit('onSort', sortBy.sortDirection[this.selectedSortDirection]);
+ handleSortByChange(sortById) {
+ this.sortById = sortById;
+ this.$emit('onSort', this.selectedSortOption.sortDirection[this.selectedSortDirection]);
},
- handleSortDirectionClick() {
- this.selectedSortDirection =
- this.selectedSortDirection === SORT_DIRECTION.ascending
- ? SORT_DIRECTION.descending
- : SORT_DIRECTION.ascending;
+ handleSortDirectionChange(isAscending) {
+ this.sortDirectionAscending = isAscending;
this.$emit('onSort', this.selectedSortOption.sortDirection[this.selectedSortDirection]);
},
handleHistoryItemSelected(filters) {
@@ -328,18 +311,30 @@ export default {
const cleared = true;
this.$emit('onFilter', [], cleared);
},
- updateSelectedSortValues(sort) {
- if (!sort) {
- return;
+ updateSelectedSortValues() {
+ Object.assign(this, this.getInitialSort());
+ },
+ getInitialSort() {
+ for (const sortOption of this.sortOptions) {
+ if (sortOption.sortDirection.ascending === this.initialSortBy) {
+ return {
+ sortById: sortOption.id,
+ sortDirectionAscending: true,
+ };
+ }
+
+ if (sortOption.sortDirection.descending === this.initialSortBy) {
+ return {
+ sortById: sortOption.id,
+ sortDirectionAscending: false,
+ };
+ }
}
- this.selectedSortOption = this.sortOptions.find(
- (sortBy) =>
- sortBy.sortDirection.ascending === sort || sortBy.sortDirection.descending === sort,
- );
- this.selectedSortDirection = Object.keys(this.selectedSortOption?.sortDirection || {}).find(
- (key) => this.selectedSortOption.sortDirection[key] === sort,
- );
+ return {
+ sortById: this.sortOptions[0]?.id,
+ sortDirectionAscending: false,
+ };
},
},
};
@@ -390,25 +385,14 @@ export default {
</template>
</template>
</gl-filtered-search>
- <gl-button-group v-if="selectedSortOption" class="sort-dropdown-container d-flex">
- <gl-dropdown :text="selectedSortOption.title" :right="true" class="w-100">
- <gl-dropdown-item
- v-for="sortBy in sortOptions"
- :key="sortBy.id"
- is-check-item
- :is-checked="sortBy.id === selectedSortOption.id"
- @click="handleSortOptionClick(sortBy)"
- >{{ sortBy.title }}</gl-dropdown-item
- >
- </gl-dropdown>
- <gl-button
- v-gl-tooltip
- :title="sortDirectionTooltip"
- :aria-label="sortDirectionTooltip"
- :icon="sortDirectionIcon"
- class="flex-shrink-1"
- @click="handleSortDirectionClick"
- />
- </gl-button-group>
+ <gl-sorting
+ v-if="selectedSortOption"
+ :sort-options="transformedSortOptions"
+ :sort-by="sortById"
+ :is-ascending="sortDirectionAscending"
+ class="sort-dropdown-container"
+ @sortByChange="handleSortByChange"
+ @sortDirectionChange="handleSortDirectionChange"
+ />
</div>
</template>
diff --git a/app/assets/javascripts/work_items/graphql/group_work_items.query.graphql b/app/assets/javascripts/work_items/graphql/group_work_items.query.graphql
index 5332e21a0cb..bfee0452acd 100644
--- a/app/assets/javascripts/work_items/graphql/group_work_items.query.graphql
+++ b/app/assets/javascripts/work_items/graphql/group_work_items.query.graphql
@@ -11,6 +11,7 @@ query groupWorkItems(
id
iid
title
+ confidential
}
}
}
diff --git a/app/assets/javascripts/work_items/graphql/project_work_items.query.graphql b/app/assets/javascripts/work_items/graphql/project_work_items.query.graphql
index 3aeaaa1116a..7efd67467e5 100644
--- a/app/assets/javascripts/work_items/graphql/project_work_items.query.graphql
+++ b/app/assets/javascripts/work_items/graphql/project_work_items.query.graphql
@@ -13,6 +13,7 @@ query projectWorkItems(
id
iid
title
+ confidential
}
}
workItemsByIid: workItems(iid: $iid, types: $types) @include(if: $isNumber) {
@@ -20,6 +21,7 @@ query projectWorkItems(
id
iid
title
+ confidential
}
}
}
diff --git a/app/assets/stylesheets/page_bundles/ide.scss b/app/assets/stylesheets/page_bundles/ide.scss
index 45270ab3d30..f7b979c2dac 100644
--- a/app/assets/stylesheets/page_bundles/ide.scss
+++ b/app/assets/stylesheets/page_bundles/ide.scss
@@ -9,6 +9,9 @@
@import './ide_themes/solarized-dark';
@import './ide_themes/monokai';
+// This whole file is for the legacy Web IDE
+// See: https://gitlab.com/groups/gitlab-org/-/epics/7683
+
$search-list-icon-width: 18px;
$ide-activity-bar-width: 60px;
$ide-context-header-padding: 10px;
@@ -18,6 +21,10 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
$ide-commit-row-height: 32px;
$ide-commit-header-height: 48px;
+.web-ide-loader {
+ padding-top: 1rem;
+}
+
.project-refs-form,
.project-refs-target-form {
display: inline-block;
@@ -517,7 +524,6 @@ $ide-commit-header-height: 48px;
.ide-empty-state {
display: flex;
- height: 100vh;
align-items: center;
justify-content: center;
background-color: var(--ide-empty-state-background, transparent);
@@ -526,6 +532,7 @@ $ide-commit-header-height: 48px;
.ide {
overflow: hidden;
flex: 1;
+ height: calc(100vh - var(--top-bar-height))
}
.ide-commit-list-container {
diff --git a/app/assets/stylesheets/themes/dark_mode_overrides.scss b/app/assets/stylesheets/themes/dark_mode_overrides.scss
index be2c791644a..708d173ed3e 100644
--- a/app/assets/stylesheets/themes/dark_mode_overrides.scss
+++ b/app/assets/stylesheets/themes/dark_mode_overrides.scss
@@ -124,13 +124,6 @@
border-color: $gray-800;
}
-.nav-sidebar,
-.toggle-sidebar-button,
-.close-nav-button {
- background-color: darken($gray-50, 4%);
- border-right: 1px solid $gray-50;
-}
-
.gl-avatar:not(.gl-avatar-identicon),
.avatar-container,
.avatar {
@@ -142,83 +135,18 @@
box-shadow: inset 0 0 0 1px rgba($gray-950, $gl-avatar-border-opacity);
}
-.nav-sidebar {
- .sidebar-sub-level-items.fly-out-list {
- box-shadow: none;
- border: 1px solid $border-color;
- }
-}
-
aside.right-sidebar:not(.right-sidebar-merge-requests) {
background-color: $gray-10;
border-left-color: $gray-50;
}
:root.gl-dark {
- @include gitlab-theme($gray-900, $gray-400, $gray-500, $gray-900, $white);
-
.terms {
.logo-text {
fill: var(--black);
}
}
-
- .navbar.navbar-gitlab {
- background-color: var(--gray-50);
- box-shadow: 0 1px 0 0 var(--gray-100);
-
- .navbar-sub-nav,
- .navbar-nav {
- li {
- > a:hover,
- > a:focus,
- > button:hover,
- > button:focus {
- color: var(--gl-text-color);
- background-color: var(--gray-200);
- }
- }
-
- li.active,
- li.dropdown.show {
- > a,
- > button {
- color: var(--gl-text-color);
- background-color: var(--gray-200);
- }
- }
- }
-
- .header-search-form {
- background-color: var(--gray-100) !important;
- box-shadow: inset 0 0 0 1px var(--border-color) !important;
-
- &:active,
- &:hover {
- background-color: var(--gray-100) !important;
- box-shadow: inset 0 0 0 1px var(--blue-200) !important;
- }
- }
-
- .search {
- form {
- background-color: var(--gray-100);
- box-shadow: inset 0 0 0 1px var(--border-color);
-
- &:active,
- &:hover {
- background-color: var(--gray-100);
- box-shadow: inset 0 0 0 1px var(--blue-200);
- }
-
- .search-input {
- color: var(--gl-text-color);
- }
- }
- }
- }
-
.md :not(pre.code) > code {
background-color: $gray-200;
}
diff --git a/app/assets/stylesheets/themes/theme_blue.scss b/app/assets/stylesheets/themes/theme_blue.scss
index 749120a0ecb..adf097ddcd7 100644
--- a/app/assets/stylesheets/themes/theme_blue.scss
+++ b/app/assets/stylesheets/themes/theme_blue.scss
@@ -2,14 +2,6 @@
:root {
&.ui-blue {
- @include gitlab-theme(
- $theme-blue-200,
- $theme-blue-500,
- $theme-blue-700,
- $theme-blue-900,
- $white
- );
-
.page-with-super-sidebar {
@include gitlab-theme-super-sidebar(
$theme-blue-50,
diff --git a/app/assets/stylesheets/themes/theme_gray.scss b/app/assets/stylesheets/themes/theme_gray.scss
index 70611e692cd..9a24142f286 100644
--- a/app/assets/stylesheets/themes/theme_gray.scss
+++ b/app/assets/stylesheets/themes/theme_gray.scss
@@ -2,14 +2,6 @@
:root {
&.ui-gray {
- @include gitlab-theme(
- $gray-200,
- $gray-300,
- $gray-500,
- $gray-900,
- $white
- );
-
.page-with-super-sidebar {
@include gitlab-theme-super-sidebar(
$gray-50,
diff --git a/app/assets/stylesheets/themes/theme_green.scss b/app/assets/stylesheets/themes/theme_green.scss
index ae969873692..e6ce699a701 100644
--- a/app/assets/stylesheets/themes/theme_green.scss
+++ b/app/assets/stylesheets/themes/theme_green.scss
@@ -2,14 +2,6 @@
:root {
&.ui-green {
- @include gitlab-theme(
- $theme-green-200,
- $theme-green-500,
- $theme-green-700,
- $theme-green-900,
- $white
- );
-
.page-with-super-sidebar {
@include gitlab-theme-super-sidebar(
$theme-green-50,
diff --git a/app/assets/stylesheets/themes/theme_helper.scss b/app/assets/stylesheets/themes/theme_helper.scss
index 6839d236cdc..58c78c73fda 100644
--- a/app/assets/stylesheets/themes/theme_helper.scss
+++ b/app/assets/stylesheets/themes/theme_helper.scss
@@ -2,271 +2,6 @@
/**
* Styles the GitLab application with a specific color theme
*/
-@mixin gitlab-theme(
- $search-and-nav-links,
- $accent,
- $border-and-box-shadow,
- $navbar-theme-color,
- $navbar-theme-contrast-color
-) {
- // Set custom properties
-
- --gl-theme-accent: #{$accent};
-
- $search-and-nav-links-a20: rgba($search-and-nav-links, 0.2);
- $search-and-nav-links-a30: rgba($search-and-nav-links, 0.3);
- $search-and-nav-links-a40: rgba($search-and-nav-links, 0.4);
- $search-and-nav-links-a80: rgba($search-and-nav-links, 0.8);
-
- // Header
-
- .navbar-gitlab:not(.super-sidebar-logged-out) {
- background-color: $navbar-theme-color;
-
- .navbar-collapse {
- color: $search-and-nav-links;
- }
-
- .container-fluid {
- .navbar-toggler {
- border-left: 1px solid lighten($border-and-box-shadow, 10%);
- color: $search-and-nav-links;
- }
- }
-
- .navbar-sub-nav,
- .navbar-nav {
- > li {
- > a,
- > button {
- &:hover,
- &:focus {
- background-color: $search-and-nav-links-a20;
- }
- }
-
- &.active,
- &.dropdown.show {
- > a,
- > button {
- color: $navbar-theme-color;
- background-color: $navbar-theme-contrast-color;
- }
- }
-
- &.line-separator {
- border-left: 1px solid $search-and-nav-links-a20;
- }
- }
- }
-
- .navbar-sub-nav {
- color: $search-and-nav-links;
- }
-
- .nav {
- > li {
- color: $search-and-nav-links;
-
- &.header-search {
- color: $gray-900;
- }
-
- > a {
- .notification-dot {
- border: 2px solid $navbar-theme-color;
- }
-
- &.header-help-dropdown-toggle {
- .notification-dot {
- background-color: $search-and-nav-links;
- }
- }
-
- &.header-user-dropdown-toggle {
- .header-user-avatar {
- border-color: $search-and-nav-links;
- }
- }
-
- &:hover,
- &:focus {
- @include media-breakpoint-up(sm) {
- background-color: $search-and-nav-links-a20;
- }
-
- svg {
- fill: currentColor;
- }
-
- .notification-dot {
- will-change: border-color, background-color;
- border-color: adjust-color($navbar-theme-color, $red: 33, $green: 33, $blue: 33);
- }
-
- &.header-help-dropdown-toggle .notification-dot {
- background-color: $white;
- }
- }
- }
-
- &.active > a,
- &.dropdown.show > a {
- color: $navbar-theme-color;
- background-color: $navbar-theme-contrast-color;
-
- &:hover {
- svg {
- fill: $navbar-theme-color;
- }
- }
-
- .notification-dot {
- border-color: $white;
- }
-
- &.header-help-dropdown-toggle {
- .notification-dot {
- background-color: $navbar-theme-color;
- }
- }
- }
-
- .impersonated-user,
- .impersonated-user:hover {
- svg {
- fill: $navbar-theme-color;
- }
- }
- }
- }
- }
-
- .navbar .title {
- > a {
- &:hover,
- &:focus {
- background-color: $search-and-nav-links-a20;
- }
- }
- }
-
- .header-search-form {
- background-color: $search-and-nav-links-a20 !important;
- border-radius: $border-radius-default;
-
- &:hover {
- background-color: $search-and-nav-links-a30 !important;
- }
-
- &.is-focused {
- input {
- background-color: $white;
- color: $gl-text-color !important;
- box-shadow: inset 0 0 0 1px $gray-900;
-
- &:focus {
- box-shadow: inset 0 0 0 1px $gray-900, 0 0 0 1px $white, 0 0 0 3px $blue-400;
- }
-
- &::placeholder {
- color: $gray-400;
- }
- }
- }
-
- svg.gl-search-box-by-type-search-icon {
- color: $search-and-nav-links-a80;
- }
-
- input {
- background-color: transparent;
- color: $search-and-nav-links-a80;
- box-shadow: inset 0 0 0 1px $search-and-nav-links-a40;
-
- &::placeholder {
- color: $search-and-nav-links-a80;
- }
-
- &:focus,
- &:active {
- &::placeholder {
- color: $gray-400;
- }
- }
- }
-
- .keyboard-shortcut-helper {
- color: $search-and-nav-links;
- background-color: $search-and-nav-links-a20;
- }
- }
-
- .search {
- form {
- background-color: $search-and-nav-links-a20;
-
- &:hover {
- background-color: $search-and-nav-links-a30;
- }
- }
-
- .search-input::placeholder {
- color: $search-and-nav-links-a80;
- }
-
- .search-input-wrap {
- .search-icon,
- .clear-icon {
- fill: $search-and-nav-links-a80;
- }
- }
-
- &.search-active {
- form {
- background-color: $white;
- }
-
- .search-input-wrap {
- .search-icon {
- fill: $search-and-nav-links-a80;
- }
- }
- }
- }
-
- // Sidebar
- .nav-sidebar li.active > a {
- color: $gray-900;
- }
-
- .nav-sidebar {
- .fly-out-top-item {
- a,
- a:hover,
- &.active a,
- .fly-out-top-item-container {
- background-color: var(--gray-100, $gray-50);
- color: var(--gray-900, $gray-900);
- }
- }
- }
-
- .branch-header-title {
- color: $border-and-box-shadow;
- }
-
- .ide-sidebar-link {
- &.active {
- color: $border-and-box-shadow;
-
- &.is-right {
- box-shadow: inset -3px 0 $border-and-box-shadow;
- }
- }
- }
-}
-
@mixin gitlab-theme-super-sidebar(
$theme-color-lightest,
$theme-color-light,
diff --git a/app/assets/stylesheets/themes/theme_indigo.scss b/app/assets/stylesheets/themes/theme_indigo.scss
index d82fb7b748c..b9e4404b573 100644
--- a/app/assets/stylesheets/themes/theme_indigo.scss
+++ b/app/assets/stylesheets/themes/theme_indigo.scss
@@ -2,14 +2,6 @@
:root {
&.ui-indigo {
- @include gitlab-theme(
- $theme-indigo-200,
- $theme-indigo-500,
- $theme-indigo-700,
- $theme-indigo-900,
- $white
- );
-
.page-with-super-sidebar {
@include gitlab-theme-super-sidebar(
$theme-indigo-50,
diff --git a/app/assets/stylesheets/themes/theme_light_blue.scss b/app/assets/stylesheets/themes/theme_light_blue.scss
index 430960f563f..9c3c16df217 100644
--- a/app/assets/stylesheets/themes/theme_light_blue.scss
+++ b/app/assets/stylesheets/themes/theme_light_blue.scss
@@ -2,14 +2,6 @@
:root {
&.ui-light-blue {
- @include gitlab-theme(
- $theme-light-blue-200,
- $theme-light-blue-500,
- $theme-light-blue-500,
- $theme-light-blue-700,
- $white
- );
-
.page-with-super-sidebar {
@include gitlab-theme-super-sidebar(
$theme-light-blue-50,
diff --git a/app/assets/stylesheets/themes/theme_light_gray.scss b/app/assets/stylesheets/themes/theme_light_gray.scss
index f63da3f22f1..5cb9bee37b0 100644
--- a/app/assets/stylesheets/themes/theme_light_gray.scss
+++ b/app/assets/stylesheets/themes/theme_light_gray.scss
@@ -1,102 +1,2 @@
-@import './theme_helper';
-
-:root {
- &.ui-light-gray {
- @include gitlab-theme(
- $gray-500,
- $gray-700,
- $gray-500,
- $gray-50,
- $gray-500
- );
-
- .navbar-gitlab:not(.super-sidebar-logged-out) {
- background-color: $gray-50;
- box-shadow: 0 1px 0 0 $border-color;
-
- .logo-text {
- fill: #171321;
- }
-
- .navbar-sub-nav,
- .navbar-nav {
- > li {
- > a:hover,
- > a:focus,
- > button:hover {
- color: $gray-900;
- }
-
- &.active > a,
- &.active > a:hover,
- &.active > button {
- color: $white;
- }
-
- > a,
- > button {
- &:active,
- &:focus {
- @include gl-focus;
- }
- }
- }
- }
-
- .container-fluid {
- .navbar-toggler,
- .navbar-toggler:hover {
- color: $gray-500;
- border-left: 1px solid $gray-100;
- }
- }
- }
-
- .header-search-form {
- background-color: $white !important;
- box-shadow: inset 0 0 0 1px $border-color !important;
- border-radius: $border-radius-default;
-
- &:hover {
- background-color: $white !important;
- box-shadow: inset 0 0 0 1px $blue-200 !important;
- }
- }
-
- .search {
- form {
- background-color: $white;
- box-shadow: inset 0 0 0 1px $border-color;
-
- &:hover {
- background-color: $white;
- box-shadow: inset 0 0 0 1px $blue-200;
- }
- }
-
- .search-input-wrap {
- .search-icon {
- fill: $gray-100;
- }
-
- .search-input {
- color: $gl-text-color;
- }
- }
- }
-
- .nav-sidebar li.active {
- > a {
- color: $gray-900;
- }
-
- svg {
- fill: $gray-900;
- }
- }
-
- .sidebar-top-level-items > li.active .badge.badge-pill {
- color: $gray-900;
- }
- }
-}
+// "Light gray" is the default unthemed state of the sidebar.
+// Nothing to do here.
diff --git a/app/assets/stylesheets/themes/theme_light_green.scss b/app/assets/stylesheets/themes/theme_light_green.scss
index 05adc56c36a..5865d6ad498 100644
--- a/app/assets/stylesheets/themes/theme_light_green.scss
+++ b/app/assets/stylesheets/themes/theme_light_green.scss
@@ -2,14 +2,6 @@
:root {
&.ui-light-green {
- @include gitlab-theme(
- $theme-green-200,
- $theme-green-500,
- $theme-green-500,
- $theme-green-700,
- $white
- );
-
.page-with-super-sidebar {
@include gitlab-theme-super-sidebar(
$theme-green-50,
diff --git a/app/assets/stylesheets/themes/theme_light_indigo.scss b/app/assets/stylesheets/themes/theme_light_indigo.scss
index 89e8fbc8a60..cfe1368980a 100644
--- a/app/assets/stylesheets/themes/theme_light_indigo.scss
+++ b/app/assets/stylesheets/themes/theme_light_indigo.scss
@@ -2,14 +2,6 @@
:root {
&.ui-light-indigo {
- @include gitlab-theme(
- $theme-indigo-200,
- $theme-indigo-500,
- $theme-indigo-500,
- $theme-indigo-700,
- $white
- );
-
.page-with-super-sidebar {
@include gitlab-theme-super-sidebar(
$theme-indigo-50,
diff --git a/app/assets/stylesheets/themes/theme_light_red.scss b/app/assets/stylesheets/themes/theme_light_red.scss
index c4952b8e155..a0b13e9832e 100644
--- a/app/assets/stylesheets/themes/theme_light_red.scss
+++ b/app/assets/stylesheets/themes/theme_light_red.scss
@@ -2,14 +2,6 @@
:root {
&.ui-light-red {
- @include gitlab-theme(
- $theme-light-red-200,
- $theme-light-red-500,
- $theme-light-red-500,
- $theme-light-red-700,
- $white
- );
-
.page-with-super-sidebar {
@include gitlab-theme-super-sidebar(
$theme-light-red-50,
diff --git a/app/assets/stylesheets/themes/theme_red.scss b/app/assets/stylesheets/themes/theme_red.scss
index 536963e12ef..75d26ed84e5 100644
--- a/app/assets/stylesheets/themes/theme_red.scss
+++ b/app/assets/stylesheets/themes/theme_red.scss
@@ -2,14 +2,6 @@
:root {
&.ui-red {
- @include gitlab-theme(
- $theme-red-200,
- $theme-red-500,
- $theme-red-700,
- $theme-red-900,
- $white
- );
-
.page-with-super-sidebar {
@include gitlab-theme-super-sidebar(
$theme-red-50,
diff --git a/app/controllers/external_redirect/external_redirect_controller.rb b/app/controllers/external_redirect/external_redirect_controller.rb
index 532196157b7..c90f55e2a57 100644
--- a/app/controllers/external_redirect/external_redirect_controller.rb
+++ b/app/controllers/external_redirect/external_redirect_controller.rb
@@ -11,7 +11,6 @@ module ExternalRedirect
redirect_to url_param
else
render layout: 'fullscreen', locals: {
- minimal: true,
url: url_param
}
end
diff --git a/app/controllers/ide_controller.rb b/app/controllers/ide_controller.rb
index 4cc943ac252..d9566121dcd 100644
--- a/app/controllers/ide_controller.rb
+++ b/app/controllers/ide_controller.rb
@@ -24,7 +24,7 @@ class IdeController < ApplicationController
@fork_info = fork_info(project, params[:branch])
end
- render layout: 'fullscreen', locals: { minimal: helpers.use_new_web_ide? }
+ render layout: helpers.use_new_web_ide? ? 'fullscreen' : 'application'
end
private
diff --git a/app/controllers/web_ide/remote_ide_controller.rb b/app/controllers/web_ide/remote_ide_controller.rb
index 90652a1b6e2..8392e7a190c 100644
--- a/app/controllers/web_ide/remote_ide_controller.rb
+++ b/app/controllers/web_ide/remote_ide_controller.rb
@@ -17,7 +17,7 @@ module WebIde
def index
return render_404 unless Feature.enabled?(:vscode_web_ide, current_user)
- render layout: 'fullscreen', locals: { minimal: true, data: root_element_data }
+ render layout: 'fullscreen', locals: { data: root_element_data }
end
private
diff --git a/app/models/event.rb b/app/models/event.rb
index fc9cdb940c3..7a09e9d2da4 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -79,7 +79,6 @@ class Event < ApplicationRecord
# Callbacks
after_create :reset_project_activity
after_create :set_last_repository_updated_at, if: :push_action?
- after_create ->(event) { UserInteractedProject.track(event) }
# Scopes
scope :recent, -> { reorder(id: :desc) }
diff --git a/app/models/group.rb b/app/models/group.rb
index 215d6624fd3..e1c88306917 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -406,7 +406,7 @@ class Group < Namespace
end
def visibility_level_allowed_by_projects?(level = self.visibility_level)
- !projects.without_deleted.where('visibility_level > ?', level).exists?
+ !projects.not_aimed_for_deletion.where('visibility_level > ?', level).exists?
end
def visibility_level_allowed_by_sub_groups?(level = self.visibility_level)
diff --git a/app/models/user.rb b/app/models/user.rb
index b9dbba27a94..925fd295611 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -220,9 +220,6 @@ class User < MainClusterwide::ApplicationRecord
has_many :project_authorizations, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
has_many :authorized_projects, through: :project_authorizations, source: :project
- has_many :user_interacted_projects
- has_many :project_interactions, through: :user_interacted_projects, source: :project, class_name: 'Project'
-
has_many :snippets, dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent
has_many :notes, dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent
has_many :issues, dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent
diff --git a/app/models/user_interacted_project.rb b/app/models/user_interacted_project.rb
deleted file mode 100644
index 73bca362960..00000000000
--- a/app/models/user_interacted_project.rb
+++ /dev/null
@@ -1,39 +0,0 @@
-# frozen_string_literal: true
-
-class UserInteractedProject < ApplicationRecord
- extend SuppressCompositePrimaryKeyWarning
-
- belongs_to :user
- belongs_to :project
-
- validates :project_id, presence: true
- validates :user_id, presence: true
-
- CACHE_EXPIRY_TIME = 1.day
-
- class << self
- def track(event)
- # For events without a project, we simply don't care.
- # An example of this is the creation of a snippet (which
- # is not related to any project).
- return unless event.project_id
-
- attributes = {
- project_id: event.project_id,
- user_id: event.author_id
- }
-
- cached_exists?(**attributes) do
- where(attributes).exists? || UserInteractedProject.insert_all([attributes], unique_by: %w[project_id user_id])
- true
- end
- end
-
- private
-
- def cached_exists?(project_id:, user_id:, &block)
- cache_key = "user_interacted_projects:#{project_id}:#{user_id}"
- Rails.cache.fetch(cache_key, expires_in: CACHE_EXPIRY_TIME, &block)
- end
- end
-end
diff --git a/app/views/ide/_show.html.haml b/app/views/ide/_show.html.haml
index 4b16c0199ba..7a14c270ae6 100644
--- a/app/views/ide/_show.html.haml
+++ b/app/views/ide/_show.html.haml
@@ -1,7 +1,15 @@
- page_title _("IDE"), @project.full_name
- add_page_specific_style 'page_bundles/web_ide_loader'
+// The block below is for the Web IDE
+// See: https://gitlab.com/groups/gitlab-org/-/epics/7683
- unless use_new_web_ide?
+ - @breadcrumb_title = _("IDE")
+ - @breadcrumb_link = '#'
+ - @no_container = true
+ - @content_wrapper_class = 'pb-0'
+ - add_to_breadcrumbs(s_('Navigation|Your work'), root_path)
+ - nav 'your_work' # Couldn't get the `project` nav to work easily
- add_page_specific_style 'page_bundles/build'
- add_page_specific_style 'page_bundles/ide'
diff --git a/app/views/layouts/fullscreen.html.haml b/app/views/layouts/fullscreen.html.haml
index f168c742085..5063cc66833 100644
--- a/app/views/layouts/fullscreen.html.haml
+++ b/app/views/layouts/fullscreen.html.haml
@@ -1,13 +1,9 @@
-- minimal = local_assigns.fetch(:minimal, false)
!!! 5
%html{ class: [user_application_theme, page_class], lang: I18n.locale }
= render "layouts/head"
%body{ class: "#{user_tab_width} #{@body_class} fullscreen-layout", data: { page: body_data_page } }
= render 'peek/bar'
= header_message
- - unless minimal
- = render partial: "layouts/header/default", locals: { project: @project, group: @group }
- .mobile-overlay
.hide-when-top-nav-responsive-open.gl--flex-full.gl-h-full{ class: nav ? ["layout-page", page_with_sidebar_class, "gl-mt-0!"]: '' }
- if defined?(nav) && nav
= render "layouts/nav/sidebar/#{nav}"
@@ -19,6 +15,4 @@
= render "layouts/flash", flash_container_no_margin: true
.content-wrapper{ id: "content-body", class: "d-flex flex-column align-items-stretch gl-p-0" }
= yield
- - unless minimal
- = render "layouts/nav/top_nav_responsive", class: "gl-flex-grow-1 gl-overflow-y-auto"
= footer_message
diff --git a/config/feature_flags/development/ci_guard_for_catalog_resource_scope.yml b/config/feature_flags/development/ci_guard_for_catalog_resource_scope.yml
index 5974c9b3985..e6e81690783 100644
--- a/config/feature_flags/development/ci_guard_for_catalog_resource_scope.yml
+++ b/config/feature_flags/development/ci_guard_for_catalog_resource_scope.yml
@@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/432219
milestone: '16.7'
type: development
group: group::pipeline authoring
-default_enabled: false
+default_enabled: true
diff --git a/config/gitlab_loose_foreign_keys.yml b/config/gitlab_loose_foreign_keys.yml
index 8aa22a95fd8..9bbf65e4887 100644
--- a/config/gitlab_loose_foreign_keys.yml
+++ b/config/gitlab_loose_foreign_keys.yml
@@ -302,6 +302,10 @@ pages_deployments:
- table: p_ci_builds
column: ci_build_id
on_delete: async_nullify
+project_authorizations:
+ - table: users
+ column: user_id
+ on_delete: async_delete
projects:
- table: organizations
column: organization_id
diff --git a/danger/qa_selector/Dangerfile b/danger/qa_selector/Dangerfile
index 7df8ccb9933..98417e79782 100644
--- a/danger/qa_selector/Dangerfile
+++ b/danger/qa_selector/Dangerfile
@@ -31,7 +31,7 @@ return if lines_with_testids.empty? && deprecated_qa_class.empty?
if lines_with_testids.any?
markdown(<<~MARKDOWN)
- ### Deprecated `testid` selectors
+ ### `testid` selectors
The following changed lines in this MR contain `testid` selectors:
diff --git a/db/migrate/20231124112409_add_instance_level_ai_beta_features_enabled_to_app_settings.rb b/db/migrate/20231124112409_add_instance_level_ai_beta_features_enabled_to_app_settings.rb
new file mode 100644
index 00000000000..3ff4dd381e0
--- /dev/null
+++ b/db/migrate/20231124112409_add_instance_level_ai_beta_features_enabled_to_app_settings.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class AddInstanceLevelAiBetaFeaturesEnabledToAppSettings < Gitlab::Database::Migration[2.2]
+ milestone '16.7'
+
+ def change
+ add_column :application_settings, :instance_level_ai_beta_features_enabled, :boolean, null: false, default: false
+ end
+end
diff --git a/db/post_migrate/20231128174345_remove_users_project_authorizations_user_id_fk.rb b/db/post_migrate/20231128174345_remove_users_project_authorizations_user_id_fk.rb
new file mode 100644
index 00000000000..faffce473a7
--- /dev/null
+++ b/db/post_migrate/20231128174345_remove_users_project_authorizations_user_id_fk.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+class RemoveUsersProjectAuthorizationsUserIdFk < Gitlab::Database::Migration[2.2]
+ milestone '16.7'
+ disable_ddl_transaction!
+
+ FOREIGN_KEY_NAME = "fk_rails_11e7aa3ed9"
+
+ def up
+ with_lock_retries do
+ remove_foreign_key_if_exists(:project_authorizations, :users,
+ name: FOREIGN_KEY_NAME, reverse_lock_order: true)
+ end
+ end
+
+ def down
+ add_concurrent_foreign_key(:project_authorizations, :users,
+ name: FOREIGN_KEY_NAME, column: :user_id,
+ target_column: :id, on_delete: :cascade)
+ end
+end
diff --git a/db/schema_migrations/20231124112409 b/db/schema_migrations/20231124112409
new file mode 100644
index 00000000000..c0b7e53e062
--- /dev/null
+++ b/db/schema_migrations/20231124112409
@@ -0,0 +1 @@
+a567da73e9ecdf930ad89c68fba02e8b30aba9e8e460a00e0bf272067ca21409 \ No newline at end of file
diff --git a/db/schema_migrations/20231128174345 b/db/schema_migrations/20231128174345
new file mode 100644
index 00000000000..5b2f77bf013
--- /dev/null
+++ b/db/schema_migrations/20231128174345
@@ -0,0 +1 @@
+187bf045979bb377e9999a260791075cab983eeda34db7ca3851720d6c5f79f9 \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index fb31618cd7b..be9eee3f9af 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -12269,6 +12269,7 @@ CREATE TABLE application_settings (
pre_receive_secret_detection_enabled boolean DEFAULT false NOT NULL,
can_create_organization boolean DEFAULT true NOT NULL,
web_ide_oauth_application_id integer,
+ instance_level_ai_beta_features_enabled boolean DEFAULT false NOT NULL,
CONSTRAINT app_settings_container_reg_cleanup_tags_max_list_size_positive CHECK ((container_registry_cleanup_tags_service_max_list_size >= 0)),
CONSTRAINT app_settings_container_registry_pre_import_tags_rate_positive CHECK ((container_registry_pre_import_tags_rate >= (0)::numeric)),
CONSTRAINT app_settings_dep_proxy_ttl_policies_worker_capacity_positive CHECK ((dependency_proxy_ttl_group_policy_worker_capacity >= 0)),
@@ -38409,9 +38410,6 @@ ALTER TABLE ONLY zoom_meetings
ALTER TABLE ONLY gpg_signatures
ADD CONSTRAINT fk_rails_11ae8cb9a7 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
-ALTER TABLE ONLY project_authorizations
- ADD CONSTRAINT fk_rails_11e7aa3ed9 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
-
ALTER TABLE ONLY pm_affected_packages
ADD CONSTRAINT fk_rails_1279c1b9a1 FOREIGN KEY (pm_advisory_id) REFERENCES pm_advisories(id) ON DELETE CASCADE;
diff --git a/doc/architecture/blueprints/cells/index.md b/doc/architecture/blueprints/cells/index.md
index 6274a440810..d4213706189 100644
--- a/doc/architecture/blueprints/cells/index.md
+++ b/doc/architecture/blueprints/cells/index.md
@@ -182,43 +182,7 @@ This list is not exhaustive of work needed to be done.
### 4. Routing layer
-The routing layer is meant to offer a consistent user experience where all Cells are presented under a single domain (for example, `gitlab.com`), instead of having to navigate to separate domains.
-
-The user will be able to use `https://gitlab.com` to access Cell-enabled GitLab.
-Depending on the URL access, it will be transparently proxied to the correct Cell that can serve this particular information.
-For example:
-
-- All requests going to `https://gitlab.com/users/sign_in` are randomly distributed to all Cells.
-- All requests going to `https://gitlab.com/gitlab-org/gitlab/-/tree/master` are always directed to Cell 5, for example.
-- All requests going to `https://gitlab.com/my-username/my-project` are always directed to Cell 1.
-
-1. **Technology.**
-
- We decide what technology the routing service is written in.
- The choice is dependent on the best performing language, and the expected way and place of deployment of the routing layer.
- If it is required to make the service multi-cloud it might be required to deploy it to the CDN provider.
- Then the service needs to be written using a technology compatible with the CDN provider.
-
-1. **Cell discovery.**
-
- The routing service needs to be able to discover and monitor the health of all Cells.
-
-1. **User can use single domain to interact with many Cells.**
-
- The routing service will intelligently route all requests to Cells based on the resource being
- accessed versus the Cell containing the data.
-
-1. **Router endpoints classification.**
-
- The stateless routing service will fetch and cache information about endpoints from one of the Cells.
- We need to implement a protocol that will allow us to accurately describe the incoming request (its fingerprint), so it can be classified by one of the Cells, and the results of that can be cached.
- We also need to implement a mechanism for negative cache and cache eviction.
-
-1. **GraphQL and other ambiguous endpoints.**
-
- Most endpoints have a unique sharding key: the Organization, which directly or indirectly (via a Group or Project) can be used to classify endpoints.
- Some endpoints are ambiguous in their usage (they don't encode the sharding key), or the sharding key is stored deep in the payload.
- In these cases, we need to decide how to handle endpoints like `/api/graphql`.
+See [Cells: Routing Service](routing-service.md).
### 5. Cell deployment
diff --git a/doc/architecture/blueprints/cells/routing-service.md b/doc/architecture/blueprints/cells/routing-service.md
new file mode 100644
index 00000000000..3355e6d0c01
--- /dev/null
+++ b/doc/architecture/blueprints/cells/routing-service.md
@@ -0,0 +1,96 @@
+---
+stage: core platform
+group: Tenant Scale
+description: 'Cells: Routing Service'
+---
+
+# Cells: Routing Service
+
+This document describes design goals and architecture of Routing Service
+used by Cells. To better understand where the Routing Service fits
+into architecture take a look at [Deployment Architecture](deployment-architecture.md).
+
+## Goals
+
+The routing layer is meant to offer a consistent user experience where all Cells are presented under a single domain (for example, `gitlab.com`), instead of having to navigate to separate domains.
+
+The user will be able to use `https://gitlab.com` to access Cell-enabled GitLab.
+Depending on the URL access, it will be transparently proxied to the correct Cell that can serve this particular information.
+For example:
+
+- All requests going to `https://gitlab.com/users/sign_in` are randomly distributed to all Cells.
+- All requests going to `https://gitlab.com/gitlab-org/gitlab/-/tree/master` are always directed to Cell 5, for example.
+- All requests going to `https://gitlab.com/my-username/my-project` are always directed to Cell 1.
+
+1. **Technology.**
+
+ We decide what technology the routing service is written in.
+ The choice is dependent on the best performing language, and the expected way and place of deployment of the routing layer.
+ If it is required to make the service multi-cloud it might be required to deploy it to the CDN provider.
+ Then the service needs to be written using a technology compatible with the CDN provider.
+
+1. **Cell discovery.**
+
+ The routing service needs to be able to discover and monitor the health of all Cells.
+
+1. **User can use single domain to interact with many Cells.**
+
+ The routing service will intelligently route all requests to Cells based on the resource being
+ accessed versus the Cell containing the data.
+
+1. **Router endpoints classification.**
+
+ The stateless routing service will fetch and cache information about endpoints from one of the Cells.
+ We need to implement a protocol that will allow us to accurately describe the incoming request (its fingerprint), so it can be classified by one of the Cells, and the results of that can be cached.
+ We also need to implement a mechanism for negative cache and cache eviction.
+
+1. **GraphQL and other ambiguous endpoints.**
+
+ Most endpoints have a unique sharding key: the Organization, which directly or indirectly (via a Group or Project) can be used to classify endpoints.
+ Some endpoints are ambiguous in their usage (they don't encode the sharding key), or the sharding key is stored deep in the payload.
+ In these cases, we need to decide how to handle endpoints like `/api/graphql`.
+
+1. **Small.**
+
+ The Routing Service is configuration-driven and rules-driven, and does not implement any business logic.
+ The maximum size of the project source code in initial phase is 1_000 lines without tests.
+ The reason for the hard limit is to make the Routing Service to not have any special logic,
+ and could be rewritten into any technology in a matter of a few days.
+
+## Requirements
+
+| Requirement | Description | Priority |
+|---------------|-------------------------------------------------------------------|----------|
+| Discovery | needs to be able to discover and monitor the health of all Cells. | high |
+| Security | only authorized cells can be routed to | high |
+| Single domain | e.g. GitLab.com | high |
+| Caching | can cache routing information for performance | high |
+| Low latency | small overhead for user requests | high |
+| Path-based | can make routing decision based on path | high |
+| Complexity | the routing service should be configuration-driven and small | high |
+| Stateless | does not need database, Cells provide all routing information | medium |
+| Secrets-based | can make routing decision based on secret (e.g. JWT) | medium |
+| Observability | can use existing observability tooling | low |
+| Self-managed | can be eventually used by [self-managed](goals.md#self-managed) | low |
+| Regional | can route requests to different [regions](goals.md#regions) | low |
+
+## Non-Goals
+
+Not yet defined.
+
+## Proposal
+
+TBD
+
+## Technology
+
+TBD
+
+## Alternatives
+
+TBD
+
+## Links
+
+- [Cells - Routing: Technology](https://gitlab.com/groups/gitlab-org/-/epics/11002)
+- [Classify endpoints](https://gitlab.com/gitlab-org/gitlab/-/issues/430330)
diff --git a/doc/ci/pipelines/merge_request_pipelines.md b/doc/ci/pipelines/merge_request_pipelines.md
index 69714363126..d35fdd1d2c6 100644
--- a/doc/ci/pipelines/merge_request_pipelines.md
+++ b/doc/ci/pipelines/merge_request_pipelines.md
@@ -137,7 +137,7 @@ Pipelines for forks display with the **fork** badge in the parent project:
![Pipeline ran in fork](img/pipeline_fork_v13_7.png)
-### Run pipelines in the parent project **(PREMIUM ALL)**
+### Run pipelines in the parent project
Project members in the parent project can trigger a merge request pipeline
for a merge request submitted from a fork project. This pipeline:
diff --git a/doc/development/ai_features/duo_chat.md b/doc/development/ai_features/duo_chat.md
index b8807432830..e6f38bea205 100644
--- a/doc/development/ai_features/duo_chat.md
+++ b/doc/development/ai_features/duo_chat.md
@@ -125,6 +125,11 @@ The following CI jobs for GitLab project run the rspecs tagged with `real_ai_req
The job is always run and not allowed to fail. Although there's a chance that the QA test still might fail,
it is cheap and fast to run and intended to prevent a regression in the QA test helpers.
+- `rspec-ee unit gitlab-duo pg14`:
+ This job runs tests to ensure that the GitLab Duo features are functional without running into system errors.
+ The job is always run and not allowed to fail.
+ This job does NOT conduct evaluations. The quality of the feature is tested in the other jobs such as QA jobs.
+
### Management of credentials and API keys for CI jobs
All API keys required to run the rspecs should be [masked](../../ci/variables/index.md#mask-a-cicd-variable)
diff --git a/doc/development/ai_features/index.md b/doc/development/ai_features/index.md
index 34da1831770..f550ad0c715 100644
--- a/doc/development/ai_features/index.md
+++ b/doc/development/ai_features/index.md
@@ -50,8 +50,14 @@ All AI features are experimental.
## Test AI features locally
-NOTE:
-Use [this snippet](https://gitlab.com/gitlab-org/gitlab/-/snippets/2554994) for help automating the following section.
+**One-line setup**
+
+```shell
+# Replace the <test-group-name> by the group name you want to enable GitLab Duo features. If the group doesn't exist, it creates a new one.
+RAILS_ENV=development bundle exec rake gitlab:duo:setup['<test-group-name>']
+```
+
+**Manual way**
1. Enable the required general feature flags:
diff --git a/doc/development/database/understanding_explain_plans.md b/doc/development/database/understanding_explain_plans.md
index 00522cb1fee..22c52c04745 100644
--- a/doc/development/database/understanding_explain_plans.md
+++ b/doc/development/database/understanding_explain_plans.md
@@ -571,8 +571,8 @@ what if we slightly change the purpose of it? What if instead of retrieving all
projects with `visibility_level` 0 or 20, we retrieve those that a user
interacted with somehow?
-Fortunately, GitLab has an answer for this, and it's a table called
-`user_interacted_projects`. This table has the following schema:
+Prior to GitLab 16.7, GitLab used a table named `user_interacted_projects` to track user interactions with projects.
+This table had the following schema:
```sql
Table "public.user_interacted_projects"
diff --git a/doc/development/development_seed_files.md b/doc/development/development_seed_files.md
index 18fedb90315..fbbc1cc7efb 100644
--- a/doc/development/development_seed_files.md
+++ b/doc/development/development_seed_files.md
@@ -18,6 +18,7 @@ data for features.
|-------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------|
| DevOps Adoption | `FILTER=devops_adoption bundle exec rake db:seed_fu` | [31_devops_adoption.rb](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/db/fixtures/development/31_devops_adoption.rb) |
| Value Streams Dashboard | `FILTER=cycle_analytics SEED_VSA=1 bundle exec rake db:seed_fu` | [17_cycle_analytics.rb](https://gitlab.com/gitlab-org/gitlab/-/blob/master/db/fixtures/development/17_cycle_analytics.rb) |
+| Value Streams Dashboard overview counts | `FILTER=vsd_overview_counts SEED_VSD_COUNTS=1 bundle exec rake db:seed_fu` | [93_vsd_overview_counts.rb](https://gitlab.com/gitlab-org/gitlab/-/tree/master/ee/db/fixtures/development/93_vsd_overview_counts.rb) |
| Value Stream Analytics | `FILTER=customizable_cycle_analytics SEED_CUSTOMIZABLE_CYCLE_ANALYTICS=1 bundle exec rake db:seed_fu` | [30_customizable_cycle_analytics](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/db/fixtures/development/30_customizable_cycle_analytics.rb) |
| CI/CD analytics | `FILTER=ci_cd_analytics SEED_CI_CD_ANALYTICS=1 bundle exec rake db:seed_fu` | [38_ci_cd_analytics](https://gitlab.com/gitlab-org/gitlab/-/blob/master/db/fixtures/development/38_ci_cd_analytics.rb?ref_type=heads) |
| Contributions Analytics<br><br>Productivity Analytics<br><br>Code review Analytics<br><br>Merge Request Analytics | `FILTER=productivity_analytics SEED_PRODUCTIVITY_ANALYTICS=1 bundle exec rake db:seed_fu` | [90_productivity_analytics](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/db/fixtures/development/90_productivity_analytics.rb) |
diff --git a/lib/gitlab/graphql/loaders/full_path_model_loader.rb b/lib/gitlab/graphql/loaders/full_path_model_loader.rb
index d0ec403432c..7de4956a668 100644
--- a/lib/gitlab/graphql/loaders/full_path_model_loader.rb
+++ b/lib/gitlab/graphql/loaders/full_path_model_loader.rb
@@ -19,7 +19,6 @@ module Gitlab
scope = args[:key]
# this logic cannot be placed in the NamespaceResolver due to N+1
scope = scope.without_project_namespaces if scope == Namespace
- # `with_route` avoids an N+1 calculating full_path
scope = scope.where_full_path_in(full_paths)
scope.each do |model_instance|
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 63fc8ef40a8..6872275f384 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -1273,9 +1273,6 @@ msgstr ""
msgid "%{time} UTC"
msgstr ""
-msgid "%{title} changes"
-msgstr ""
-
msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
msgstr ""
diff --git a/scripts/decomposition/generate-loose-foreign-key b/scripts/decomposition/generate-loose-foreign-key
index 56d19cb3aa1..2caa87741e6 100755
--- a/scripts/decomposition/generate-loose-foreign-key
+++ b/scripts/decomposition/generate-loose-foreign-key
@@ -216,8 +216,8 @@ def add_test_to_specs(definition)
spec_test = <<-EOF.strip_heredoc.indent(2)
context 'with loose foreign key on #{definition.from_table}.#{definition.column}' do
it_behaves_like 'cleanup by a loose foreign key' do
- let!(:parent) { create(:#{definition.to_table.singularize}) }
- let!(:model) { create(:#{definition.from_table.singularize}, #{definition.column.delete_suffix("_id").singularize}: parent) }
+ let_it_be(:parent) { create(:#{definition.to_table.singularize}) }
+ let_it_be(:model) { create(:#{definition.from_table.singularize}, #{definition.column.delete_suffix("_id").singularize}: parent) }
end
end
EOF
diff --git a/scripts/review_apps/base-config.yaml b/scripts/review_apps/base-config.yaml
index 0ba0648cf01..414ec77a186 100644
--- a/scripts/review_apps/base-config.yaml
+++ b/scripts/review_apps/base-config.yaml
@@ -16,8 +16,6 @@ global:
image:
pullPolicy: Always
ingress:
- annotations:
- external-dns.alpha.kubernetes.io/ttl: 10
configureCertmanager: false
tls:
secretName: review-apps-tls
diff --git a/spec/features/admin/admin_abuse_reports_spec.rb b/spec/features/admin/admin_abuse_reports_spec.rb
index 973988560b3..5e98d2ffcf3 100644
--- a/spec/features/admin/admin_abuse_reports_spec.rb
+++ b/spec/features/admin/admin_abuse_reports_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe "Admin::AbuseReports", :js, feature_category: :insider_threat do
+ include Features::SortingHelpers
+
let_it_be(:user) { create(:user) }
let_it_be(:admin) { create(:admin) }
@@ -79,7 +81,7 @@ RSpec.describe "Admin::AbuseReports", :js, feature_category: :insider_threat do
expect(report_rows[1].text).to include(report_text(open_report2))
# updated_at asc
- sort_by 'Updated date'
+ sort_by 'Updated date', from: 'Created date'
expect(report_rows[0].text).to include(report_text(open_report2))
expect(report_rows[1].text).to include(report_text(open_report))
@@ -120,7 +122,7 @@ RSpec.describe "Admin::AbuseReports", :js, feature_category: :insider_threat do
expect(report_rows[1].text).to include(report_text(open_report2))
# created_at desc
- sort_by 'Created date'
+ sort_by 'Created date', from: 'Number of Reports'
expect(report_rows[0].text).to include(report_text(open_report2))
expect(report_rows[1].text).to include(aggregated_report_text(open_report, 2))
@@ -131,7 +133,7 @@ RSpec.describe "Admin::AbuseReports", :js, feature_category: :insider_threat do
expect(report_rows[0].text).to include(aggregated_report_text(open_report, 2))
expect(report_rows[1].text).to include(report_text(open_report2))
- sort_by 'Updated date'
+ sort_by 'Updated date', from: 'Created date'
# updated_at asc
expect(report_rows[0].text).to include(report_text(open_report2))
@@ -193,14 +195,10 @@ RSpec.describe "Admin::AbuseReports", :js, feature_category: :insider_threat do
select_tokens(*tokens, submit: true, input_text: 'Filter reports')
end
- def sort_by(sort)
+ def sort_by(sort, from: 'Number of Reports')
page.within('.vue-filtered-search-bar-container .sort-dropdown-container') do
- page.find('.gl-dropdown-toggle').click
-
- page.within('.dropdown-menu') do
- click_button sort
- wait_for_requests
- end
+ pajamas_sort_by sort, from: from
+ wait_for_requests
end
end
end
diff --git a/spec/features/admin/admin_runners_spec.rb b/spec/features/admin/admin_runners_spec.rb
index 750f5f8d4b9..d4e25d413dd 100644
--- a/spec/features/admin/admin_runners_spec.rb
+++ b/spec/features/admin/admin_runners_spec.rb
@@ -3,6 +3,7 @@
require 'spec_helper'
RSpec.describe "Admin Runners", feature_category: :runner_fleet do
+ include Features::SortingHelpers
include Features::RunnersHelpers
include Spec::Support::Helpers::ModalHelpers
@@ -480,8 +481,7 @@ RSpec.describe "Admin Runners", feature_category: :runner_fleet do
end
end
- click_on 'Created date' # Open "sort by" dropdown
- click_on 'Last contact'
+ pajamas_sort_by 'Last contact', from: 'Created date'
click_on 'Sort direction: Descending'
within_testid('runner-list') do
diff --git a/spec/features/dashboard/issues_filter_spec.rb b/spec/features/dashboard/issues_filter_spec.rb
index ab3aa29a3aa..9bc412d6bf4 100644
--- a/spec/features/dashboard/issues_filter_spec.rb
+++ b/spec/features/dashboard/issues_filter_spec.rb
@@ -111,8 +111,7 @@ RSpec.describe 'Dashboard Issues filtering', :js, feature_category: :team_planni
end
it 'keeps sorting issues after visiting Projects Issues page' do
- click_button 'Created date'
- click_button 'Due date'
+ pajamas_sort_by 'Due date', from: 'Created date'
visit project_issues_path(project)
diff --git a/spec/features/dashboard/merge_requests_spec.rb b/spec/features/dashboard/merge_requests_spec.rb
index 22bd7bed626..ba42b3e099b 100644
--- a/spec/features/dashboard/merge_requests_spec.rb
+++ b/spec/features/dashboard/merge_requests_spec.rb
@@ -211,19 +211,19 @@ RSpec.describe 'Dashboard Merge Requests', :js, feature_category: :code_review_w
end
it 'shows sorted merge requests' do
- pajamas_sort_by(s_('SortOptions|Created date'))
+ pajamas_sort_by(s_('SortOptions|Priority'), from: s_('SortOptions|Created date'))
visit merge_requests_dashboard_path(assignee_username: current_user.username)
- expect(find('.issues-filters')).to have_content('Created date')
+ expect(find('.issues-filters')).to have_content(s_('SortOptions|Priority'))
end
it 'keeps sorting merge requests after visiting Projects MR page' do
- pajamas_sort_by(s_('SortOptions|Created date'))
+ pajamas_sort_by(s_('SortOptions|Priority'), from: s_('SortOptions|Created date'))
visit project_merge_requests_path(project)
- expect(find('.issues-filters')).to have_content('Created date')
+ expect(find('.issues-filters')).to have_content(s_('SortOptions|Priority'))
end
end
diff --git a/spec/features/groups/issues_spec.rb b/spec/features/groups/issues_spec.rb
index 0d7e9df031c..e4f9fe34823 100644
--- a/spec/features/groups/issues_spec.rb
+++ b/spec/features/groups/issues_spec.rb
@@ -3,6 +3,7 @@
require 'spec_helper'
RSpec.describe 'Group issues page', feature_category: :groups_and_projects do
+ include Features::SortingHelpers
include FilteredSearchHelpers
include DragTo
@@ -180,8 +181,7 @@ RSpec.describe 'Group issues page', feature_category: :groups_and_projects do
end
def select_manual_sort
- click_button 'Created date'
- click_button 'Manual'
+ pajamas_sort_by 'Manual', from: 'Created date'
wait_for_requests
end
diff --git a/spec/features/issuables/sorting_list_spec.rb b/spec/features/issuables/sorting_list_spec.rb
index 9045124cc8c..d960081c517 100644
--- a/spec/features/issuables/sorting_list_spec.rb
+++ b/spec/features/issuables/sorting_list_spec.rb
@@ -2,6 +2,7 @@
require 'spec_helper'
RSpec.describe 'Sort Issuable List', feature_category: :team_planning do
+ include Features::SortingHelpers
include ListboxHelpers
let(:project) { create(:project, :public) }
@@ -195,8 +196,7 @@ RSpec.describe 'Sort Issuable List', feature_category: :team_planning do
it 'supports sorting in asc and desc order' do
visit_issues_with_state(project, 'opened')
- click_button('Created date')
- click_on('Updated date')
+ pajamas_sort_by 'Updated date', from: 'Created date'
expect(page).to have_css('.issue:first-child', text: last_updated_issuable.title)
expect(page).to have_css('.issue:last-child', text: first_updated_issuable.title)
diff --git a/spec/features/issues/user_sorts_issues_spec.rb b/spec/features/issues/user_sorts_issues_spec.rb
index 206544b32a4..5c6198785d0 100644
--- a/spec/features/issues/user_sorts_issues_spec.rb
+++ b/spec/features/issues/user_sorts_issues_spec.rb
@@ -3,6 +3,7 @@
require "spec_helper"
RSpec.describe "User sorts issues", feature_category: :team_planning do
+ include Features::SortingHelpers
include SortingHelper
include IssueHelpers
@@ -46,8 +47,7 @@ RSpec.describe "User sorts issues", feature_category: :team_planning do
it 'sorts by popularity', :js do
visit(project_issues_path(project))
- click_button 'Created date'
- click_on 'Popularity'
+ pajamas_sort_by 'Popularity', from: 'Created date'
page.within(".issues-list") do
page.within("li.issue:nth-child(1)") do
diff --git a/spec/features/merge_requests/user_sorts_merge_requests_spec.rb b/spec/features/merge_requests/user_sorts_merge_requests_spec.rb
index 5ccc24ebca1..3c2e1c4b37e 100644
--- a/spec/features/merge_requests/user_sorts_merge_requests_spec.rb
+++ b/spec/features/merge_requests/user_sorts_merge_requests_spec.rb
@@ -23,7 +23,7 @@ RSpec.describe 'User sorts merge requests', :js, feature_category: :code_review_
end
it 'keeps the sort option' do
- pajamas_sort_by(s_('SortOptions|Milestone'))
+ pajamas_sort_by(s_('SortOptions|Milestone'), from: s_('SortOptions|Created date'))
visit(merge_requests_dashboard_path(assignee_username: user.username))
@@ -49,7 +49,7 @@ RSpec.describe 'User sorts merge requests', :js, feature_category: :code_review_
it 'separates remember sorting with issues', :js do
create(:issue, project: project)
- pajamas_sort_by(s_('SortOptions|Milestone'))
+ pajamas_sort_by(s_('SortOptions|Milestone'), from: s_('SortOptions|Created date'))
visit(project_issues_path(project))
@@ -66,7 +66,7 @@ RSpec.describe 'User sorts merge requests', :js, feature_category: :code_review_
end
it 'sorts by popularity' do
- pajamas_sort_by(s_('SortOptions|Popularity'))
+ pajamas_sort_by(s_('SortOptions|Popularity'), from: s_('SortOptions|Created date'))
page.within('.mr-list') do
page.within('li.merge-request:nth-child(1)') do
diff --git a/spec/features/projects/work_items/work_item_children_spec.rb b/spec/features/projects/work_items/work_item_children_spec.rb
index 752ea282fbf..0970752157d 100644
--- a/spec/features/projects/work_items/work_item_children_spec.rb
+++ b/spec/features/projects/work_items/work_item_children_spec.rb
@@ -133,6 +133,33 @@ RSpec.describe 'Work item children', :js, feature_category: :team_planning do
expect(find('[data-testid="links-child"]')).to have_content(task.title)
end
end
+
+ context 'with confidential issue' do
+ let_it_be_with_reload(:issue) { create(:issue, :confidential, project: project) }
+ let_it_be(:task) { create(:work_item, :confidential, :task, project: project) }
+
+ it 'adds an existing child task', :aggregate_failures do
+ page.within('[data-testid="work-item-links"]') do
+ click_button 'Add'
+ click_button 'Existing task'
+
+ expect(page).to have_button('Add task', disabled: true)
+ find('[data-testid="work-item-token-select-input"]').set(task.title)
+ wait_for_all_requests
+ click_button task.title
+
+ expect(page).to have_button('Add task', disabled: false)
+
+ send_keys :escape
+
+ click_button('Add task')
+
+ wait_for_all_requests
+
+ expect(find('[data-testid="links-child"]')).to have_content(task.title)
+ end
+ end
+ end
end
context 'in work item metadata' do
diff --git a/spec/features/user_sorts_things_spec.rb b/spec/features/user_sorts_things_spec.rb
index bc377fb1f8f..8b2ee9f1ced 100644
--- a/spec/features/user_sorts_things_spec.rb
+++ b/spec/features/user_sorts_things_spec.rb
@@ -25,8 +25,7 @@ RSpec.describe "User sorts things", :js do
visit(project_issues_path(project))
- click_button s_('SortOptions|Created date')
- click_button sort_option
+ pajamas_sort_by sort_option, from: s_('SortOptions|Created date')
visit(project_path(project))
visit(project_issues_path(project))
@@ -39,7 +38,7 @@ RSpec.describe "User sorts things", :js do
visit(project_merge_requests_path(project))
- pajamas_sort_by(sort_option)
+ pajamas_sort_by sort_option, from: s_('SortOptions|Created date')
visit(assigned_mrs_dashboard_path)
diff --git a/spec/frontend/boards/board_card_inner_spec.js b/spec/frontend/boards/board_card_inner_spec.js
index 6312b6694ed..8f2752b6bd8 100644
--- a/spec/frontend/boards/board_card_inner_spec.js
+++ b/spec/frontend/boards/board_card_inner_spec.js
@@ -2,8 +2,6 @@ import { GlLabel, GlLoadingIcon } from '@gitlab/ui';
import { range } from 'lodash';
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
-// eslint-disable-next-line no-restricted-imports
-import Vuex from 'vuex';
import createMockApollo from 'helpers/mock_apollo_helper';
import setWindowLocation from 'helpers/set_window_location_helper';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
@@ -13,7 +11,6 @@ import BoardCardInner from '~/boards/components/board_card_inner.vue';
import isShowingLabelsQuery from '~/graphql_shared/client/is_showing_labels.query.graphql';
import WorkItemTypeIcon from '~/work_items/components/work_item_type_icon.vue';
import eventHub from '~/boards/eventhub';
-import defaultStore from '~/boards/stores';
import { TYPE_ISSUE } from '~/issues/constants';
import { updateHistory } from '~/lib/utils/url_utility';
import { mockLabelList, mockIssue, mockIssueFullPath, mockIssueDirectNamespace } from './mock_data';
@@ -21,7 +18,6 @@ import { mockLabelList, mockIssue, mockIssueFullPath, mockIssueDirectNamespace }
jest.mock('~/lib/utils/url_utility');
jest.mock('~/boards/eventhub');
-Vue.use(Vuex);
Vue.use(VueApollo);
describe('Board card component', () => {
@@ -43,24 +39,12 @@ describe('Board card component', () => {
let wrapper;
let issue;
let list;
- let store;
const findIssuableBlockedIcon = () => wrapper.findComponent(IssuableBlockedIcon);
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findHiddenIssueIcon = () => wrapper.findByTestId('hidden-icon');
const findWorkItemIcon = () => wrapper.findComponent(WorkItemTypeIcon);
- const performSearchMock = jest.fn();
-
- const createStore = () => {
- store = new Vuex.Store({
- actions: {
- performSearch: performSearchMock,
- },
- state: defaultStore.state,
- });
- };
-
const mockApollo = createMockApollo();
const createWrapper = ({ props = {}, isGroupBoard = true } = {}) => {
@@ -72,7 +56,6 @@ describe('Board card component', () => {
});
wrapper = mountExtended(BoardCardInner, {
- store,
apolloProvider: mockApollo,
propsData: {
list,
@@ -94,7 +77,6 @@ describe('Board card component', () => {
allowSubEpics: false,
issuableType: TYPE_ISSUE,
isGroupBoard,
- isApolloBoard: false,
},
});
};
@@ -108,14 +90,9 @@ describe('Board card component', () => {
weight: 1,
};
- createStore();
createWrapper({ props: { item: issue, list } });
});
- afterEach(() => {
- store = null;
- });
-
it('renders issue title', () => {
expect(wrapper.find('.board-card-title').text()).toContain(issue.title);
});
@@ -159,7 +136,6 @@ describe('Board card component', () => {
});
it('does not render item reference path', () => {
- createStore();
createWrapper({ isGroupBoard: false });
expect(wrapper.find('.board-card-number').text()).not.toContain(mockIssueDirectNamespace);
@@ -460,10 +436,6 @@ describe('Board card component', () => {
expect(updateHistory).toHaveBeenCalledTimes(1);
});
- it('dispatches performSearch vuex action', () => {
- expect(performSearchMock).toHaveBeenCalledTimes(1);
- });
-
it('emits updateTokens event', () => {
expect(eventHub.$emit).toHaveBeenCalledTimes(1);
expect(eventHub.$emit).toHaveBeenCalledWith('updateTokens');
@@ -480,10 +452,6 @@ describe('Board card component', () => {
expect(updateHistory).not.toHaveBeenCalled();
});
- it('does not dispatch performSearch vuex action', () => {
- expect(performSearchMock).not.toHaveBeenCalled();
- });
-
it('does not emit updateTokens event', () => {
expect(eventHub.$emit).not.toHaveBeenCalled();
});
diff --git a/spec/frontend/boards/components/board_app_spec.js b/spec/frontend/boards/components/board_app_spec.js
index b16f9b26f40..157c76b4fff 100644
--- a/spec/frontend/boards/components/board_app_spec.js
+++ b/spec/frontend/boards/components/board_app_spec.js
@@ -1,8 +1,6 @@
import { shallowMount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
-// eslint-disable-next-line no-restricted-imports
-import Vuex from 'vuex';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
@@ -15,34 +13,15 @@ import { rawIssue, boardListsQueryResponse } from '../mock_data';
describe('BoardApp', () => {
let wrapper;
- let store;
let mockApollo;
const errorMessage = 'Failed to fetch lists';
const boardListQueryHandler = jest.fn().mockResolvedValue(boardListsQueryResponse);
const boardListQueryHandlerFailure = jest.fn().mockRejectedValue(new Error(errorMessage));
- Vue.use(Vuex);
Vue.use(VueApollo);
- const createStore = ({ mockGetters = {} } = {}) => {
- store = new Vuex.Store({
- state: {},
- actions: {
- performSearch: jest.fn(),
- },
- getters: {
- isSidebarOpen: () => true,
- ...mockGetters,
- },
- });
- };
-
- const createComponent = ({
- isApolloBoard = false,
- issue = rawIssue,
- handler = boardListQueryHandler,
- } = {}) => {
+ const createComponent = ({ issue = rawIssue, handler = boardListQueryHandler } = {}) => {
mockApollo = createMockApollo([[boardListsQuery, handler]]);
mockApollo.clients.defaultClient.cache.writeQuery({
query: activeBoardItemQuery,
@@ -53,7 +32,6 @@ describe('BoardApp', () => {
wrapper = shallowMount(BoardApp, {
apolloProvider: mockApollo,
- store,
provide: {
fullPath: 'gitlab-org',
initialBoardId: 'gid://gitlab/Board/1',
@@ -62,69 +40,46 @@ describe('BoardApp', () => {
boardType: 'group',
isIssueBoard: true,
isGroupBoard: true,
- isApolloBoard,
},
});
};
- beforeEach(() => {
+ beforeEach(async () => {
cacheUpdates.setError = jest.fn();
- });
- afterEach(() => {
- store = null;
+ createComponent({ isApolloBoard: true });
+ await nextTick();
});
- it("should have 'is-compact' class when sidebar is open", () => {
- createStore();
- createComponent();
+ it('fetches lists', () => {
+ expect(boardListQueryHandler).toHaveBeenCalled();
+ });
+ it('should have is-compact class when a card is selected', () => {
expect(wrapper.classes()).toContain('is-compact');
});
- it("should not have 'is-compact' class when sidebar is closed", () => {
- createStore({ mockGetters: { isSidebarOpen: () => false } });
- createComponent();
+ it('should not have is-compact class when no card is selected', async () => {
+ createComponent({ isApolloBoard: true, issue: {} });
+ await nextTick();
expect(wrapper.classes()).not.toContain('is-compact');
});
- describe('Apollo boards', () => {
- beforeEach(async () => {
- createComponent({ isApolloBoard: true });
- await nextTick();
- });
+ it('refetches lists when updateBoard event is received', async () => {
+ jest.spyOn(eventHub, '$on').mockImplementation(() => {});
- it('fetches lists', () => {
- expect(boardListQueryHandler).toHaveBeenCalled();
- });
+ createComponent({ isApolloBoard: true });
+ await waitForPromises();
- it('should have is-compact class when a card is selected', () => {
- expect(wrapper.classes()).toContain('is-compact');
- });
-
- it('should not have is-compact class when no card is selected', async () => {
- createComponent({ isApolloBoard: true, issue: {} });
- await nextTick();
-
- expect(wrapper.classes()).not.toContain('is-compact');
- });
-
- it('refetches lists when updateBoard event is received', async () => {
- jest.spyOn(eventHub, '$on').mockImplementation(() => {});
-
- createComponent({ isApolloBoard: true });
- await waitForPromises();
-
- expect(eventHub.$on).toHaveBeenCalledWith('updateBoard', wrapper.vm.refetchLists);
- });
+ expect(eventHub.$on).toHaveBeenCalledWith('updateBoard', wrapper.vm.refetchLists);
+ });
- it('sets error on fetch lists failure', async () => {
- createComponent({ isApolloBoard: true, handler: boardListQueryHandlerFailure });
+ it('sets error on fetch lists failure', async () => {
+ createComponent({ isApolloBoard: true, handler: boardListQueryHandlerFailure });
- await waitForPromises();
+ await waitForPromises();
- expect(cacheUpdates.setError).toHaveBeenCalled();
- });
+ expect(cacheUpdates.setError).toHaveBeenCalled();
});
});
diff --git a/spec/frontend/boards/components/board_card_move_to_position_spec.js b/spec/frontend/boards/components/board_card_move_to_position_spec.js
index 20beaf2e9bd..d3c43a4e054 100644
--- a/spec/frontend/boards/components/board_card_move_to_position_spec.js
+++ b/spec/frontend/boards/components/board_card_move_to_position_spec.js
@@ -8,7 +8,7 @@ import {
BOARD_CARD_MOVE_TO_POSITIONS_END_OPTION,
} from '~/boards/constants';
import BoardCardMoveToPosition from '~/boards/components/board_card_move_to_position.vue';
-import { mockList, mockIssue2, mockIssue, mockIssue3, mockIssue4 } from 'jest/boards/mock_data';
+import { mockList, mockIssue2 } from 'jest/boards/mock_data';
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
Vue.use(Vuex);
@@ -28,30 +28,8 @@ describe('Board Card Move to position', () => {
let wrapper;
let trackingSpy;
let store;
- let dispatch;
const itemIndex = 1;
- const createStoreOptions = () => {
- const state = {
- pageInfoByListId: {
- 'gid://gitlab/List/1': {},
- 'gid://gitlab/List/2': { hasNextPage: true },
- },
- };
- const getters = {
- getBoardItemsByList: () => () => [mockIssue, mockIssue2, mockIssue3, mockIssue4],
- };
- const actions = {
- moveItem: jest.fn(),
- };
-
- return {
- state,
- getters,
- actions,
- };
- };
-
const createComponent = (propsData, isApolloBoard = false) => {
wrapper = shallowMount(BoardCardMoveToPosition, {
store,
@@ -73,7 +51,6 @@ describe('Board Card Move to position', () => {
};
beforeEach(() => {
- store = new Vuex.Store(createStoreOptions());
createComponent();
});
@@ -97,50 +74,6 @@ describe('Board Card Move to position', () => {
describe('Dropdown options', () => {
beforeEach(() => {
- createComponent({ index: itemIndex });
- trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
- dispatch = jest.spyOn(store, 'dispatch').mockImplementation(() => {});
- });
-
- afterEach(() => {
- unmockTracking();
- });
-
- it.each`
- dropdownIndex | dropdownItem | trackLabel | positionInList
- ${0} | ${dropdownOptions[0]} | ${'move_to_start'} | ${0}
- ${1} | ${dropdownOptions[1]} | ${'move_to_end'} | ${-1}
- `(
- 'on click of dropdown index $dropdownIndex with label $dropdownLabel should call moveItem action with tracking label $trackLabel',
- async ({ dropdownIndex, dropdownItem, trackLabel, positionInList }) => {
- await findMoveToPositionDropdown().vm.$emit('shown');
-
- expect(findDropdownItemAtIndex(dropdownIndex).text()).toBe(dropdownItem.text);
-
- await findMoveToPositionDropdown().vm.$emit('action', dropdownItem);
-
- expect(trackingSpy).toHaveBeenCalledWith('boards:list', 'click_toggle_button', {
- category: 'boards:list',
- label: trackLabel,
- property: 'type_card',
- });
-
- expect(dispatch).toHaveBeenCalledWith('moveItem', {
- fromListId: mockList.id,
- itemId: mockIssue2.id,
- itemIid: mockIssue2.iid,
- itemPath: mockIssue2.referencePath,
- positionInList,
- toListId: mockList.id,
- allItemsLoadedInList: true,
- atIndex: itemIndex,
- });
- },
- );
- });
-
- describe('Apollo boards', () => {
- beforeEach(() => {
createComponent({ index: itemIndex }, true);
trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
});
diff --git a/spec/frontend/boards/components/board_column_spec.js b/spec/frontend/boards/components/board_column_spec.js
index 5717031be20..61c53c27187 100644
--- a/spec/frontend/boards/components/board_column_spec.js
+++ b/spec/frontend/boards/components/board_column_spec.js
@@ -4,17 +4,15 @@ import { nextTick } from 'vue';
import { listObj } from 'jest/boards/mock_data';
import BoardColumn from '~/boards/components/board_column.vue';
import { ListType } from '~/boards/constants';
-import { createStore } from '~/boards/stores';
describe('Board Column Component', () => {
let wrapper;
- let store;
- const initStore = () => {
- store = createStore();
- };
-
- const createComponent = ({ listType = ListType.backlog, collapsed = false } = {}) => {
+ const createComponent = ({
+ listType = ListType.backlog,
+ collapsed = false,
+ highlightedLists = [],
+ } = {}) => {
const listMock = {
...listObj,
listType,
@@ -27,14 +25,11 @@ describe('Board Column Component', () => {
}
wrapper = shallowMount(BoardColumn, {
- store,
propsData: {
list: listMock,
boardId: 'gid://gitlab/Board/1',
filters: {},
- },
- provide: {
- isApolloBoard: false,
+ highlightedLists,
},
});
};
@@ -43,10 +38,6 @@ describe('Board Column Component', () => {
const isCollapsed = () => wrapper.classes('is-collapsed');
describe('Given different list types', () => {
- beforeEach(() => {
- initStore();
- });
-
it('is expandable when List Type is `backlog`', () => {
createComponent({ listType: ListType.backlog });
@@ -70,40 +61,11 @@ describe('Board Column Component', () => {
describe('highlighting', () => {
it('scrolls to column when highlighted', async () => {
- createComponent();
-
- store.state.highlightedLists.push(listObj.id);
+ createComponent({ highlightedLists: [listObj.id] });
await nextTick();
expect(wrapper.element.scrollIntoView).toHaveBeenCalled();
});
});
-
- describe('on mount', () => {
- beforeEach(() => {
- initStore();
- jest.spyOn(store, 'dispatch').mockImplementation();
- });
-
- describe('when list is collapsed', () => {
- it('does not call fetchItemsForList when', async () => {
- createComponent({ collapsed: true });
-
- await nextTick();
-
- expect(store.dispatch).toHaveBeenCalledTimes(0);
- });
- });
-
- describe('when the list is not collapsed', () => {
- it('calls fetchItemsForList when', async () => {
- createComponent({ collapsed: false });
-
- await nextTick();
-
- expect(store.dispatch).toHaveBeenCalledWith('fetchItemsForList', { listId: 300 });
- });
- });
- });
});
diff --git a/spec/frontend/boards/components/board_content_spec.js b/spec/frontend/boards/components/board_content_spec.js
index 675b79a8b1a..706f84ad319 100644
--- a/spec/frontend/boards/components/board_content_spec.js
+++ b/spec/frontend/boards/components/board_content_spec.js
@@ -3,14 +3,11 @@ import { shallowMount } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import Vue, { nextTick } from 'vue';
import Draggable from 'vuedraggable';
-// eslint-disable-next-line no-restricted-imports
-import Vuex from 'vuex';
import createMockApollo from 'helpers/mock_apollo_helper';
import { stubComponent } from 'helpers/stub_component';
import waitForPromises from 'helpers/wait_for_promises';
import EpicsSwimlanes from 'ee_component/boards/components/epics_swimlanes.vue';
-import getters from 'ee_else_ce/boards/stores/getters';
import * as cacheUpdates from '~/boards/graphql/cache_updates';
import BoardColumn from '~/boards/components/board_column.vue';
import BoardContent from '~/boards/components/board_content.vue';
@@ -27,11 +24,6 @@ import {
} from '../mock_data';
Vue.use(VueApollo);
-Vue.use(Vuex);
-
-const actions = {
- moveList: jest.fn(),
-};
describe('BoardContent', () => {
let wrapper;
@@ -41,26 +33,9 @@ describe('BoardContent', () => {
const errorMessage = 'Failed to update list';
const updateListHandlerFailure = jest.fn().mockRejectedValue(new Error(errorMessage));
- const defaultState = {
- isShowingEpicsSwimlanes: false,
- boardLists: mockListsById,
- error: undefined,
- issuableType: 'issue',
- };
-
- const createStore = (state = defaultState) => {
- return new Vuex.Store({
- actions,
- getters,
- state,
- });
- };
-
const createComponent = ({
- state,
props = {},
canAdminList = true,
- isApolloBoard = false,
issuableType = 'issue',
isIssueBoard = true,
isEpicBoard = false,
@@ -75,17 +50,13 @@ describe('BoardContent', () => {
data: boardListsQueryResponse.data,
});
- const store = createStore({
- ...defaultState,
- ...state,
- });
wrapper = shallowMount(BoardContent, {
apolloProvider: mockApollo,
propsData: {
boardId: 'gid://gitlab/Board/1',
filterParams: {},
isSwimlanesOn: false,
- boardListsApollo: mockListsById,
+ boardLists: mockListsById,
listQueryVariables,
addColumnFormVisible: false,
...props,
@@ -98,9 +69,7 @@ describe('BoardContent', () => {
isEpicBoard,
isGroupBoard: true,
disabled: false,
- isApolloBoard,
},
- store,
stubs: {
BoardContentSidebar: stubComponent(BoardContentSidebar, {
template: '<div></div>',
@@ -114,13 +83,26 @@ describe('BoardContent', () => {
const findDraggable = () => wrapper.findComponent(Draggable);
const findError = () => wrapper.findComponent(GlAlert);
+ const moveList = () => {
+ const movableListsOrder = [mockLists[0].id, mockLists[1].id];
+
+ findDraggable().vm.$emit('end', {
+ item: { dataset: { listId: mockLists[0].id, draggableItemType: DraggableItemTypes.list } },
+ newIndex: 1,
+ to: {
+ children: movableListsOrder.map((listId) => ({ dataset: { listId } })),
+ },
+ });
+ };
+
beforeEach(() => {
cacheUpdates.setError = jest.fn();
});
describe('default', () => {
- beforeEach(() => {
+ beforeEach(async () => {
createComponent();
+ await waitForPromises();
});
it('renders a BoardColumn component per list', () => {
@@ -146,63 +128,6 @@ describe('BoardContent', () => {
it('does not show the "add column" form', () => {
expect(findBoardAddNewColumn().exists()).toBe(false);
});
- });
-
- describe('when issuableType is not issue', () => {
- beforeEach(() => {
- createComponent({ issuableType: 'foo', isIssueBoard: false });
- });
-
- it('does not render BoardContentSidebar', () => {
- expect(wrapper.findComponent(BoardContentSidebar).exists()).toBe(false);
- });
- });
-
- describe('can admin list', () => {
- beforeEach(() => {
- createComponent({ canAdminList: true });
- });
-
- it('renders draggable component', () => {
- expect(findDraggable().exists()).toBe(true);
- });
- });
-
- describe('can not admin list', () => {
- beforeEach(() => {
- createComponent({ canAdminList: false });
- });
-
- it('does not render draggable component', () => {
- expect(findDraggable().exists()).toBe(false);
- });
- });
-
- describe('when Apollo boards FF is on', () => {
- const moveList = () => {
- const movableListsOrder = [mockLists[0].id, mockLists[1].id];
-
- findDraggable().vm.$emit('end', {
- item: { dataset: { listId: mockLists[0].id, draggableItemType: DraggableItemTypes.list } },
- newIndex: 1,
- to: {
- children: movableListsOrder.map((listId) => ({ dataset: { listId } })),
- },
- });
- };
-
- beforeEach(async () => {
- createComponent({ isApolloBoard: true });
- await waitForPromises();
- });
-
- it('renders a BoardColumn component per list', () => {
- expect(wrapper.findAllComponents(BoardColumn)).toHaveLength(mockLists.length);
- });
-
- it('renders BoardContentSidebar', () => {
- expect(wrapper.findComponent(BoardContentSidebar).exists()).toBe(true);
- });
it('reorders lists', async () => {
moveList();
@@ -212,7 +137,7 @@ describe('BoardContent', () => {
});
it('sets error on reorder lists failure', async () => {
- createComponent({ isApolloBoard: true, handler: updateListHandlerFailure });
+ createComponent({ handler: updateListHandlerFailure });
moveList();
await waitForPromises();
@@ -222,7 +147,7 @@ describe('BoardContent', () => {
describe('when error is passed', () => {
beforeEach(async () => {
- createComponent({ isApolloBoard: true, props: { apolloError: 'Error' } });
+ createComponent({ props: { apolloError: 'Error' } });
await waitForPromises();
});
@@ -239,6 +164,36 @@ describe('BoardContent', () => {
});
});
+ describe('when issuableType is not issue', () => {
+ beforeEach(() => {
+ createComponent({ issuableType: 'foo', isIssueBoard: false });
+ });
+
+ it('does not render BoardContentSidebar', () => {
+ expect(wrapper.findComponent(BoardContentSidebar).exists()).toBe(false);
+ });
+ });
+
+ describe('can admin list', () => {
+ beforeEach(() => {
+ createComponent({ canAdminList: true });
+ });
+
+ it('renders draggable component', () => {
+ expect(findDraggable().exists()).toBe(true);
+ });
+ });
+
+ describe('can not admin list', () => {
+ beforeEach(() => {
+ createComponent({ canAdminList: false });
+ });
+
+ it('does not render draggable component', () => {
+ expect(findDraggable().exists()).toBe(false);
+ });
+ });
+
describe('when "add column" form is visible', () => {
beforeEach(() => {
createComponent({ props: { addColumnFormVisible: true } });
diff --git a/spec/frontend/boards/components/board_filtered_search_spec.js b/spec/frontend/boards/components/board_filtered_search_spec.js
index e0d865ada20..5e96b508f37 100644
--- a/spec/frontend/boards/components/board_filtered_search_spec.js
+++ b/spec/frontend/boards/components/board_filtered_search_spec.js
@@ -1,7 +1,4 @@
import { shallowMount } from '@vue/test-utils';
-import Vue from 'vue';
-// eslint-disable-next-line no-restricted-imports
-import Vuex from 'vuex';
import BoardFilteredSearch from '~/boards/components/board_filtered_search.vue';
import { updateHistory } from '~/lib/utils/url_utility';
import {
@@ -20,9 +17,6 @@ import {
import FilteredSearchBarRoot from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
import UserToken from '~/vue_shared/components/filtered_search_bar/tokens/user_token.vue';
import LabelToken from '~/vue_shared/components/filtered_search_bar/tokens/label_token.vue';
-import { createStore } from '~/boards/stores';
-
-Vue.use(Vuex);
jest.mock('~/lib/utils/url_utility', () => ({
updateHistory: jest.fn(),
@@ -32,7 +26,6 @@ jest.mock('~/lib/utils/url_utility', () => ({
describe('BoardFilteredSearch', () => {
let wrapper;
- let store;
const tokens = [
{
icon: 'labels',
@@ -63,15 +56,12 @@ describe('BoardFilteredSearch', () => {
];
const createComponent = ({ initialFilterParams = {}, props = {}, provide = {} } = {}) => {
- store = createStore();
wrapper = shallowMount(BoardFilteredSearch, {
provide: {
initialFilterParams,
fullPath: '',
- isApolloBoard: false,
...provide,
},
- store,
propsData: {
...props,
tokens,
@@ -84,8 +74,6 @@ describe('BoardFilteredSearch', () => {
describe('default', () => {
beforeEach(() => {
createComponent();
-
- jest.spyOn(store, 'dispatch').mockImplementation();
});
it('passes the correct tokens to FilteredSearch', () => {
@@ -93,12 +81,6 @@ describe('BoardFilteredSearch', () => {
});
describe('when onFilter is emitted', () => {
- it('calls performSearch', () => {
- findFilteredSearch().vm.$emit('onFilter', [{ value: { data: '' } }]);
-
- expect(store.dispatch).toHaveBeenCalledWith('performSearch');
- });
-
it('calls historyPushState', () => {
findFilteredSearch().vm.$emit('onFilter', [{ value: { data: 'searchQuery' } }]);
@@ -109,6 +91,18 @@ describe('BoardFilteredSearch', () => {
});
});
});
+
+ it('emits setFilters and updates URL when onFilter is emitted', () => {
+ findFilteredSearch().vm.$emit('onFilter', [{ value: { data: '' } }]);
+
+ expect(updateHistory).toHaveBeenCalledWith({
+ title: '',
+ replace: true,
+ url: 'http://test.host/',
+ });
+
+ expect(wrapper.emitted('setFilters')).toHaveLength(1);
+ });
});
describe('when eeFilters is not empty', () => {
@@ -130,8 +124,6 @@ describe('BoardFilteredSearch', () => {
describe('when searching', () => {
beforeEach(() => {
createComponent();
-
- jest.spyOn(store, 'dispatch').mockImplementation();
});
it('sets the url params to the correct results', () => {
@@ -151,7 +143,6 @@ describe('BoardFilteredSearch', () => {
findFilteredSearch().vm.$emit('onFilter', mockFilters);
- expect(store.dispatch).toHaveBeenCalledWith('performSearch');
expect(updateHistory).toHaveBeenCalledWith({
title: '',
replace: true,
@@ -198,56 +189,42 @@ describe('BoardFilteredSearch', () => {
});
});
- describe('when Apollo boards FF is on', () => {
+ describe('when iteration is passed a wildcard value with a cadence id', () => {
+ const url = (arg) => `http://test.host/?iteration_id=${arg}&iteration_cadence_id=1349`;
+
beforeEach(() => {
- createComponent({ provide: { isApolloBoard: true } });
+ createComponent();
});
- it('emits setFilters and updates URL when onFilter is emitted', () => {
- findFilteredSearch().vm.$emit('onFilter', [{ value: { data: '' } }]);
+ it.each([
+ ['Current&1349', url('Current'), 'Current'],
+ ['Any&1349', url('Any'), 'Any'],
+ ])('sets the url param %s', (iterationParam, expected, wildCardId) => {
+ Object.defineProperty(window, 'location', {
+ writable: true,
+ value: new URL(expected),
+ });
+
+ const mockFilters = [
+ { type: TOKEN_TYPE_ITERATION, value: { data: iterationParam, operator: '=' } },
+ ];
+
+ findFilteredSearch().vm.$emit('onFilter', mockFilters);
expect(updateHistory).toHaveBeenCalledWith({
title: '',
replace: true,
- url: 'http://test.host/',
+ url: expected,
});
- expect(wrapper.emitted('setFilters')).toHaveLength(1);
- });
-
- describe('when iteration is passed a wildcard value with a cadence id', () => {
- const url = (arg) => `http://test.host/?iteration_id=${arg}&iteration_cadence_id=1349`;
-
- it.each([
- ['Current&1349', url('Current'), 'Current'],
- ['Any&1349', url('Any'), 'Any'],
- ])('sets the url param %s', (iterationParam, expected, wildCardId) => {
- Object.defineProperty(window, 'location', {
- writable: true,
- value: new URL(expected),
- });
-
- const mockFilters = [
- { type: TOKEN_TYPE_ITERATION, value: { data: iterationParam, operator: '=' } },
- ];
-
- findFilteredSearch().vm.$emit('onFilter', mockFilters);
-
- expect(updateHistory).toHaveBeenCalledWith({
- title: '',
- replace: true,
- url: expected,
- });
-
- expect(wrapper.emitted('setFilters')).toStrictEqual([
- [
- {
- iterationCadenceId: '1349',
- iterationId: wildCardId,
- },
- ],
- ]);
- });
+ expect(wrapper.emitted('setFilters')).toStrictEqual([
+ [
+ {
+ iterationCadenceId: '1349',
+ iterationId: wildCardId,
+ },
+ ],
+ ]);
});
});
});
diff --git a/spec/frontend/boards/components/board_form_spec.js b/spec/frontend/boards/components/board_form_spec.js
index a0dacf085e2..16947a0512d 100644
--- a/spec/frontend/boards/components/board_form_spec.js
+++ b/spec/frontend/boards/components/board_form_spec.js
@@ -1,7 +1,5 @@
import { GlModal } from '@gitlab/ui';
import Vue from 'vue';
-// eslint-disable-next-line no-restricted-imports
-import Vuex from 'vuex';
import VueApollo from 'vue-apollo';
import setWindowLocation from 'helpers/set_window_location_helper';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
@@ -23,8 +21,6 @@ jest.mock('~/lib/utils/url_utility', () => ({
}));
jest.mock('~/boards/eventhub');
-Vue.use(Vuex);
-
const currentBoard = {
id: 'gid://gitlab/Board/1',
name: 'test',
@@ -55,14 +51,6 @@ describe('BoardForm', () => {
const findDeleteConfirmation = () => wrapper.findByTestId('delete-confirmation-message');
const findInput = () => wrapper.find('#board-new-name');
- const setBoardMock = jest.fn();
-
- const store = new Vuex.Store({
- actions: {
- setBoard: setBoardMock,
- },
- });
-
const defaultHandlers = {
createBoardMutationHandler: jest.fn().mockResolvedValue({
data: {
@@ -107,7 +95,6 @@ describe('BoardForm', () => {
isProjectBoard: false,
...provide,
},
- store,
attachTo: document.body,
});
};
@@ -220,7 +207,7 @@ describe('BoardForm', () => {
});
await waitForPromises();
- expect(setBoardMock).toHaveBeenCalledTimes(1);
+ expect(wrapper.emitted('addBoard')).toHaveLength(1);
});
it('sets error in state if GraphQL mutation fails', async () => {
@@ -239,31 +226,8 @@ describe('BoardForm', () => {
expect(requestHandlers.createBoardMutationHandler).toHaveBeenCalled();
await waitForPromises();
- expect(setBoardMock).not.toHaveBeenCalled();
expect(cacheUpdates.setError).toHaveBeenCalled();
});
-
- describe('when Apollo boards FF is on', () => {
- it('calls a correct GraphQL mutation and emits addBoard event when creating a board', async () => {
- createComponent({
- props: { canAdminBoard: true, currentPage: formType.new },
- provide: { isApolloBoard: true },
- });
-
- fillForm();
-
- await waitForPromises();
-
- expect(requestHandlers.createBoardMutationHandler).toHaveBeenCalledWith({
- input: expect.objectContaining({
- name: 'test',
- }),
- });
-
- await waitForPromises();
- expect(wrapper.emitted('addBoard')).toHaveLength(1);
- });
- });
});
});
@@ -314,8 +278,12 @@ describe('BoardForm', () => {
});
await waitForPromises();
- expect(setBoardMock).toHaveBeenCalledTimes(1);
expect(global.window.location.href).not.toContain('?group_by=epic');
+ expect(eventHub.$emit).toHaveBeenCalledTimes(1);
+ expect(eventHub.$emit).toHaveBeenCalledWith('updateBoard', {
+ id: 'gid://gitlab/Board/321',
+ webPath: 'test-path',
+ });
});
it('calls GraphQL mutation with correct parameters when issues are grouped by epic', async () => {
@@ -335,7 +303,6 @@ describe('BoardForm', () => {
});
await waitForPromises();
- expect(setBoardMock).toHaveBeenCalledTimes(1);
expect(global.window.location.href).toContain('?group_by=epic');
});
@@ -355,36 +322,8 @@ describe('BoardForm', () => {
expect(requestHandlers.updateBoardMutationHandler).toHaveBeenCalled();
await waitForPromises();
- expect(setBoardMock).not.toHaveBeenCalled();
expect(cacheUpdates.setError).toHaveBeenCalled();
});
-
- describe('when Apollo boards FF is on', () => {
- it('calls a correct GraphQL mutation and emits updateBoard event when updating a board', async () => {
- setWindowLocation('https://test/boards/1');
-
- createComponent({
- props: { canAdminBoard: true, currentPage: formType.edit },
- provide: { isApolloBoard: true },
- });
- findInput().trigger('keyup.enter', { metaKey: true });
-
- await waitForPromises();
-
- expect(requestHandlers.updateBoardMutationHandler).toHaveBeenCalledWith({
- input: expect.objectContaining({
- id: currentBoard.id,
- }),
- });
-
- await waitForPromises();
- expect(eventHub.$emit).toHaveBeenCalledTimes(1);
- expect(eventHub.$emit).toHaveBeenCalledWith('updateBoard', {
- id: 'gid://gitlab/Board/321',
- webPath: 'test-path',
- });
- });
- });
});
describe('when deleting a board', () => {
@@ -427,7 +366,6 @@ describe('BoardForm', () => {
destroyBoardMutationHandler: jest.fn().mockRejectedValue('Houston, we have a problem'),
},
});
- jest.spyOn(store, 'dispatch').mockImplementation(() => {});
findModal().vm.$emit('primary');
diff --git a/spec/frontend/boards/components/board_list_header_spec.js b/spec/frontend/boards/components/board_list_header_spec.js
index 76e969f1725..b59ed8b6abb 100644
--- a/spec/frontend/boards/components/board_list_header_spec.js
+++ b/spec/frontend/boards/components/board_list_header_spec.js
@@ -1,8 +1,6 @@
import { GlButtonGroup } from '@gitlab/ui';
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
-// eslint-disable-next-line no-restricted-imports
-import Vuex from 'vuex';
import createMockApollo from 'helpers/mock_apollo_helper';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
@@ -18,15 +16,11 @@ import * as cacheUpdates from '~/boards/graphql/cache_updates';
import listQuery from 'ee_else_ce/boards/graphql/board_lists_deferred.query.graphql';
Vue.use(VueApollo);
-Vue.use(Vuex);
describe('Board List Header Component', () => {
let wrapper;
- let store;
let fakeApollo;
- const updateListSpy = jest.fn();
- const toggleListCollapsedSpy = jest.fn();
const mockClientToggleListCollapsedResolver = jest.fn();
const updateListHandlerSuccess = jest.fn().mockResolvedValue(updateBoardListResponse);
@@ -69,10 +63,6 @@ describe('Board List Header Component', () => {
);
}
- store = new Vuex.Store({
- state: {},
- actions: { updateList: updateListSpy, toggleListCollapsed: toggleListCollapsedSpy },
- });
fakeApollo = createMockApollo(
[
[listQuery, listQueryHandler],
@@ -87,7 +77,6 @@ describe('Board List Header Component', () => {
wrapper = shallowMountExtended(BoardListHeader, {
apolloProvider: fakeApollo,
- store,
propsData: {
list: listMock,
filterParams: {},
@@ -198,26 +187,34 @@ describe('Board List Header Component', () => {
expect(icon.props('icon')).toBe('chevron-lg-right');
});
- it('should dispatch toggleListCollapse when clicking the collapse icon', async () => {
- createComponent();
+ it('set active board item on client when clicking on card', async () => {
+ createComponent({ listType: ListType.label });
+ await nextTick();
findCaret().vm.$emit('click');
-
await nextTick();
- expect(toggleListCollapsedSpy).toHaveBeenCalledTimes(1);
+
+ expect(mockClientToggleListCollapsedResolver).toHaveBeenCalledWith(
+ {},
+ {
+ list: mockLabelList,
+ collapsed: true,
+ },
+ expect.anything(),
+ expect.anything(),
+ );
});
- it("when logged in it calls list update and doesn't set localStorage", async () => {
+ it("when logged in it doesn't set localStorage", async () => {
createComponent({ withLocalStorage: false, currentUserId: 1 });
findCaret().vm.$emit('click');
await nextTick();
- expect(updateListSpy).toHaveBeenCalledTimes(1);
expect(localStorage.getItem(`${wrapper.vm.uniqueKey}.collapsed`)).toBe(null);
});
- it("when logged out it doesn't call list update and sets localStorage", async () => {
+ it('when logged out it sets localStorage', async () => {
createComponent({
currentUserId: null,
});
@@ -225,7 +222,6 @@ describe('Board List Header Component', () => {
findCaret().vm.$emit('click');
await nextTick();
- expect(updateListSpy).not.toHaveBeenCalled();
expect(localStorage.getItem(`${wrapper.vm.uniqueKey}.collapsed`)).toBe(
String(!isCollapsed()),
);
@@ -252,86 +248,67 @@ describe('Board List Header Component', () => {
});
});
- describe('Apollo boards', () => {
- beforeEach(async () => {
- createComponent({ listType: ListType.label, injectedProps: { isApolloBoard: true } });
- await nextTick();
- });
-
- it('set active board item on client when clicking on card', async () => {
- findCaret().vm.$emit('click');
- await nextTick();
-
- expect(mockClientToggleListCollapsedResolver).toHaveBeenCalledWith(
- {},
- {
- list: mockLabelList,
- collapsed: true,
- },
- expect.anything(),
- expect.anything(),
- );
- });
+ beforeEach(async () => {
+ createComponent({ listType: ListType.label });
+ await nextTick();
+ });
- it('does not call update list mutation when user is not logged in', async () => {
- createComponent({ currentUserId: null, injectedProps: { isApolloBoard: true } });
+ it('does not call update list mutation when user is not logged in', async () => {
+ createComponent({ currentUserId: null });
- findCaret().vm.$emit('click');
- await nextTick();
+ findCaret().vm.$emit('click');
+ await nextTick();
- expect(updateListHandlerSuccess).not.toHaveBeenCalled();
- });
+ expect(updateListHandlerSuccess).not.toHaveBeenCalled();
+ });
- it('calls update list mutation when user is logged in', async () => {
- createComponent({ currentUserId: 1, injectedProps: { isApolloBoard: true } });
+ it('calls update list mutation when user is logged in', async () => {
+ createComponent({ currentUserId: 1 });
- findCaret().vm.$emit('click');
- await nextTick();
+ findCaret().vm.$emit('click');
+ await nextTick();
- expect(updateListHandlerSuccess).toHaveBeenCalledWith({
- listId: mockLabelList.id,
- collapsed: true,
- });
+ expect(updateListHandlerSuccess).toHaveBeenCalledWith({
+ listId: mockLabelList.id,
+ collapsed: true,
});
+ });
- describe('when fetch list query fails', () => {
- const errorMessage = 'Failed to fetch list';
- const listQueryHandlerFailure = jest.fn().mockRejectedValue(new Error(errorMessage));
+ describe('when fetch list query fails', () => {
+ const errorMessage = 'Failed to fetch list';
+ const listQueryHandlerFailure = jest.fn().mockRejectedValue(new Error(errorMessage));
- beforeEach(() => {
- createComponent({
- listQueryHandler: listQueryHandlerFailure,
- injectedProps: { isApolloBoard: true },
- });
+ beforeEach(() => {
+ createComponent({
+ listQueryHandler: listQueryHandlerFailure,
});
+ });
- it('sets error', async () => {
- await waitForPromises();
+ it('sets error', async () => {
+ await waitForPromises();
- expect(cacheUpdates.setError).toHaveBeenCalled();
- });
+ expect(cacheUpdates.setError).toHaveBeenCalled();
});
+ });
- describe('when update list mutation fails', () => {
- const errorMessage = 'Failed to update list';
- const updateListHandlerFailure = jest.fn().mockRejectedValue(new Error(errorMessage));
+ describe('when update list mutation fails', () => {
+ const errorMessage = 'Failed to update list';
+ const updateListHandlerFailure = jest.fn().mockRejectedValue(new Error(errorMessage));
- beforeEach(() => {
- createComponent({
- currentUserId: 1,
- updateListHandler: updateListHandlerFailure,
- injectedProps: { isApolloBoard: true },
- });
+ beforeEach(() => {
+ createComponent({
+ currentUserId: 1,
+ updateListHandler: updateListHandlerFailure,
});
+ });
- it('sets error', async () => {
- await waitForPromises();
+ it('sets error', async () => {
+ await waitForPromises();
- findCaret().vm.$emit('click');
- await waitForPromises();
+ findCaret().vm.$emit('click');
+ await waitForPromises();
- expect(cacheUpdates.setError).toHaveBeenCalled();
- });
+ expect(cacheUpdates.setError).toHaveBeenCalled();
});
});
});
diff --git a/spec/frontend/boards/components/board_new_issue_spec.js b/spec/frontend/boards/components/board_new_issue_spec.js
index bf2608d0594..dad0d148449 100644
--- a/spec/frontend/boards/components/board_new_issue_spec.js
+++ b/spec/frontend/boards/components/board_new_issue_spec.js
@@ -1,7 +1,5 @@
import { shallowMount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
-// eslint-disable-next-line no-restricted-imports
-import Vuex from 'vuex';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import BoardNewIssue from '~/boards/components/board_new_issue.vue';
@@ -15,18 +13,12 @@ import { WORKSPACE_GROUP, WORKSPACE_PROJECT } from '~/issues/constants';
import {
mockList,
mockGroupProjects,
- mockIssue,
- mockIssue2,
mockProjectBoardResponse,
mockGroupBoardResponse,
} from '../mock_data';
-Vue.use(Vuex);
Vue.use(VueApollo);
-const addListNewIssuesSpy = jest.fn().mockResolvedValue();
-const mockActions = { addListNewIssue: addListNewIssuesSpy };
-
const projectBoardQueryHandlerSuccess = jest.fn().mockResolvedValue(mockProjectBoardResponse);
const groupBoardQueryHandlerSuccess = jest.fn().mockResolvedValue(mockGroupBoardResponse);
@@ -36,20 +28,12 @@ const mockApollo = createMockApollo([
]);
const createComponent = ({
- state = {},
- actions = mockActions,
- getters = { getBoardItemsByList: () => () => [] },
isGroupBoard = true,
data = { selectedProject: mockGroupProjects[0] },
provide = {},
} = {}) =>
shallowMount(BoardNewIssue, {
apolloProvider: mockApollo,
- store: new Vuex.Store({
- state,
- actions,
- getters,
- }),
propsData: {
list: mockList,
boardId: 'gid://gitlab/Board/1',
@@ -63,7 +47,6 @@ const createComponent = ({
isGroupBoard,
boardType: 'group',
isEpicBoard: false,
- isApolloBoard: false,
...provide,
},
stubs: {
@@ -82,6 +65,32 @@ describe('Issue boards new issue form', () => {
await nextTick();
});
+ it.each`
+ boardType | queryHandler | notCalledHandler
+ ${WORKSPACE_GROUP} | ${groupBoardQueryHandlerSuccess} | ${projectBoardQueryHandlerSuccess}
+ ${WORKSPACE_PROJECT} | ${projectBoardQueryHandlerSuccess} | ${groupBoardQueryHandlerSuccess}
+ `(
+ 'fetches $boardType board and emits addNewIssue event',
+ async ({ boardType, queryHandler, notCalledHandler }) => {
+ wrapper = createComponent({
+ provide: {
+ boardType,
+ isProjectBoard: boardType === WORKSPACE_PROJECT,
+ isGroupBoard: boardType === WORKSPACE_GROUP,
+ },
+ });
+
+ await nextTick();
+ findBoardNewItem().vm.$emit('form-submit', { title: 'Foo' });
+
+ await nextTick();
+
+ expect(queryHandler).toHaveBeenCalled();
+ expect(notCalledHandler).not.toHaveBeenCalled();
+ expect(wrapper.emitted('addNewIssue')[0][0]).toMatchObject({ title: 'Foo' });
+ },
+ );
+
it('renders board-new-item component', () => {
const boardNewItem = findBoardNewItem();
expect(boardNewItem.exists()).toBe(true);
@@ -93,51 +102,6 @@ describe('Issue boards new issue form', () => {
});
});
- it('calls addListNewIssue action when `board-new-item` emits form-submit event', async () => {
- findBoardNewItem().vm.$emit('form-submit', { title: 'Foo' });
-
- await nextTick();
- expect(addListNewIssuesSpy).toHaveBeenCalledWith(expect.any(Object), {
- list: mockList,
- issueInput: {
- title: 'Foo',
- labelIds: [],
- assigneeIds: [],
- milestoneId: undefined,
- projectPath: mockGroupProjects[0].fullPath,
- moveAfterId: undefined,
- },
- });
- });
-
- describe('when list has an existing issues', () => {
- beforeEach(() => {
- wrapper = createComponent({
- getters: {
- getBoardItemsByList: () => () => [mockIssue, mockIssue2],
- },
- isGroupBoard: true,
- });
- });
-
- it('uses the first issue ID as moveAfterId', async () => {
- findBoardNewItem().vm.$emit('form-submit', { title: 'Foo' });
-
- await nextTick();
- expect(addListNewIssuesSpy).toHaveBeenCalledWith(expect.any(Object), {
- list: mockList,
- issueInput: {
- title: 'Foo',
- labelIds: [],
- assigneeIds: [],
- milestoneId: undefined,
- projectPath: mockGroupProjects[0].fullPath,
- moveAfterId: mockIssue.id,
- },
- });
- });
- });
-
it('emits event `toggle-issue-form` with current list Id suffix on eventHub when `board-new-item` emits form-cancel event', async () => {
jest.spyOn(eventHub, '$emit').mockImplementation();
findBoardNewItem().vm.$emit('form-cancel');
@@ -168,33 +132,4 @@ describe('Issue boards new issue form', () => {
expect(projectSelect.exists()).toBe(false);
});
});
-
- describe('Apollo boards', () => {
- it.each`
- boardType | queryHandler | notCalledHandler
- ${WORKSPACE_GROUP} | ${groupBoardQueryHandlerSuccess} | ${projectBoardQueryHandlerSuccess}
- ${WORKSPACE_PROJECT} | ${projectBoardQueryHandlerSuccess} | ${groupBoardQueryHandlerSuccess}
- `(
- 'fetches $boardType board and emits addNewIssue event',
- async ({ boardType, queryHandler, notCalledHandler }) => {
- wrapper = createComponent({
- provide: {
- boardType,
- isProjectBoard: boardType === WORKSPACE_PROJECT,
- isGroupBoard: boardType === WORKSPACE_GROUP,
- isApolloBoard: true,
- },
- });
-
- await nextTick();
- findBoardNewItem().vm.$emit('form-submit', { title: 'Foo' });
-
- await nextTick();
-
- expect(queryHandler).toHaveBeenCalled();
- expect(notCalledHandler).not.toHaveBeenCalled();
- expect(wrapper.emitted('addNewIssue')[0][0]).toMatchObject({ title: 'Foo' });
- },
- );
- });
});
diff --git a/spec/frontend/boards/components/board_top_bar_spec.js b/spec/frontend/boards/components/board_top_bar_spec.js
index 87abe630688..03526600114 100644
--- a/spec/frontend/boards/components/board_top_bar_spec.js
+++ b/spec/frontend/boards/components/board_top_bar_spec.js
@@ -1,8 +1,6 @@
import { shallowMount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
-// eslint-disable-next-line no-restricted-imports
-import Vuex from 'vuex';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
@@ -21,18 +19,11 @@ import projectBoardQuery from '~/boards/graphql/project_board.query.graphql';
import { mockProjectBoardResponse, mockGroupBoardResponse } from '../mock_data';
Vue.use(VueApollo);
-Vue.use(Vuex);
describe('BoardTopBar', () => {
let wrapper;
let mockApollo;
- const createStore = () => {
- return new Vuex.Store({
- state: {},
- });
- };
-
const projectBoardQueryHandlerSuccess = jest.fn().mockResolvedValue(mockProjectBoardResponse);
const groupBoardQueryHandlerSuccess = jest.fn().mockResolvedValue(mockGroupBoardResponse);
const errorMessage = 'Failed to fetch board';
@@ -43,14 +34,12 @@ describe('BoardTopBar', () => {
projectBoardQueryHandler = projectBoardQueryHandlerSuccess,
groupBoardQueryHandler = groupBoardQueryHandlerSuccess,
} = {}) => {
- const store = createStore();
mockApollo = createMockApollo([
[projectBoardQuery, projectBoardQueryHandler],
[groupBoardQuery, groupBoardQueryHandler],
]);
wrapper = shallowMount(BoardTopBar, {
- store,
apolloProvider: mockApollo,
propsData: {
boardId: 'gid://gitlab/Board/1',
@@ -67,7 +56,7 @@ describe('BoardTopBar', () => {
isIssueBoard: true,
isEpicBoard: false,
isGroupBoard: true,
- isApolloBoard: false,
+ // isApolloBoard: false,
...provide,
},
stubs: { IssueBoardFilteredSearch },
@@ -127,45 +116,41 @@ describe('BoardTopBar', () => {
});
});
- describe('Apollo boards', () => {
- it.each`
- boardType | queryHandler | notCalledHandler
- ${WORKSPACE_GROUP} | ${groupBoardQueryHandlerSuccess} | ${projectBoardQueryHandlerSuccess}
- ${WORKSPACE_PROJECT} | ${projectBoardQueryHandlerSuccess} | ${groupBoardQueryHandlerSuccess}
- `('fetches $boardType boards', async ({ boardType, queryHandler, notCalledHandler }) => {
- createComponent({
- provide: {
- boardType,
- isProjectBoard: boardType === WORKSPACE_PROJECT,
- isGroupBoard: boardType === WORKSPACE_GROUP,
- isApolloBoard: true,
- },
- });
-
- await nextTick();
-
- expect(queryHandler).toHaveBeenCalled();
- expect(notCalledHandler).not.toHaveBeenCalled();
+ it.each`
+ boardType | queryHandler | notCalledHandler
+ ${WORKSPACE_GROUP} | ${groupBoardQueryHandlerSuccess} | ${projectBoardQueryHandlerSuccess}
+ ${WORKSPACE_PROJECT} | ${projectBoardQueryHandlerSuccess} | ${groupBoardQueryHandlerSuccess}
+ `('fetches $boardType boards', async ({ boardType, queryHandler, notCalledHandler }) => {
+ createComponent({
+ provide: {
+ boardType,
+ isProjectBoard: boardType === WORKSPACE_PROJECT,
+ isGroupBoard: boardType === WORKSPACE_GROUP,
+ },
});
- it.each`
- boardType
- ${WORKSPACE_GROUP}
- ${WORKSPACE_PROJECT}
- `('sets error when $boardType board query fails', async ({ boardType }) => {
- createComponent({
- provide: {
- boardType,
- isProjectBoard: boardType === WORKSPACE_PROJECT,
- isGroupBoard: boardType === WORKSPACE_GROUP,
- isApolloBoard: true,
- },
- groupBoardQueryHandler: boardQueryHandlerFailure,
- projectBoardQueryHandler: boardQueryHandlerFailure,
- });
-
- await waitForPromises();
- expect(cacheUpdates.setError).toHaveBeenCalled();
+ await nextTick();
+
+ expect(queryHandler).toHaveBeenCalled();
+ expect(notCalledHandler).not.toHaveBeenCalled();
+ });
+
+ it.each`
+ boardType
+ ${WORKSPACE_GROUP}
+ ${WORKSPACE_PROJECT}
+ `('sets error when $boardType board query fails', async ({ boardType }) => {
+ createComponent({
+ provide: {
+ boardType,
+ isProjectBoard: boardType === WORKSPACE_PROJECT,
+ isGroupBoard: boardType === WORKSPACE_GROUP,
+ },
+ groupBoardQueryHandler: boardQueryHandlerFailure,
+ projectBoardQueryHandler: boardQueryHandlerFailure,
});
+
+ await waitForPromises();
+ expect(cacheUpdates.setError).toHaveBeenCalled();
});
});
diff --git a/spec/frontend/boards/components/sidebar/board_sidebar_title_spec.js b/spec/frontend/boards/components/sidebar/board_sidebar_title_spec.js
index f354067e226..77b557e7ccd 100644
--- a/spec/frontend/boards/components/sidebar/board_sidebar_title_spec.js
+++ b/spec/frontend/boards/components/sidebar/board_sidebar_title_spec.js
@@ -6,7 +6,6 @@ import waitForPromises from 'helpers/wait_for_promises';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue';
import BoardSidebarTitle from '~/boards/components/sidebar/board_sidebar_title.vue';
-import { createStore } from '~/boards/stores';
import issueSetTitleMutation from '~/boards/graphql/issue_set_title.mutation.graphql';
import * as cacheUpdates from '~/boards/graphql/cache_updates';
import updateEpicTitleMutation from '~/sidebar/queries/update_epic_title.mutation.graphql';
@@ -32,11 +31,10 @@ const TEST_ISSUE_B = {
describe('BoardSidebarTitle', () => {
let wrapper;
- let store;
- let storeDispatch;
let mockApollo;
const issueSetTitleMutationHandlerSuccess = jest.fn().mockResolvedValue(updateIssueTitleResponse);
+ const issueSetTitleMutationHandlerFailure = jest.fn().mockRejectedValue(new Error('error'));
const updateEpicTitleMutationHandlerSuccess = jest
.fn()
.mockResolvedValue(updateEpicTitleResponse);
@@ -47,28 +45,25 @@ describe('BoardSidebarTitle', () => {
afterEach(() => {
localStorage.clear();
- store = null;
});
- const createWrapper = ({ item = TEST_ISSUE_A, provide = {} } = {}) => {
- store = createStore();
- store.state.boardItems = { [item.id]: { ...item } };
- store.dispatch('setActiveId', { id: item.id });
+ const createWrapper = ({
+ item = TEST_ISSUE_A,
+ provide = {},
+ issueSetTitleMutationHandler = issueSetTitleMutationHandlerSuccess,
+ } = {}) => {
mockApollo = createMockApollo([
- [issueSetTitleMutation, issueSetTitleMutationHandlerSuccess],
+ [issueSetTitleMutation, issueSetTitleMutationHandler],
[updateEpicTitleMutation, updateEpicTitleMutationHandlerSuccess],
]);
- storeDispatch = jest.spyOn(store, 'dispatch');
wrapper = shallowMountExtended(BoardSidebarTitle, {
- store,
apolloProvider: mockApollo,
provide: {
canUpdate: true,
fullPath: 'gitlab-org',
issuableType: 'issue',
isEpicBoard: false,
- isApolloBoard: false,
...provide,
},
propsData: {
@@ -122,13 +117,6 @@ describe('BoardSidebarTitle', () => {
expect(findCollapsed().isVisible()).toBe(true);
});
- it('commits change to the server', () => {
- expect(storeDispatch).toHaveBeenCalledWith('setActiveItemTitle', {
- projectPath: 'h/b',
- title: 'New item title',
- });
- });
-
it('renders correct title', async () => {
createWrapper({ item: { ...TEST_ISSUE_A, title: TEST_TITLE } });
await waitForPromises();
@@ -137,6 +125,31 @@ describe('BoardSidebarTitle', () => {
});
});
+ it.each`
+ issuableType | isEpicBoard | queryHandler | notCalledHandler
+ ${'issue'} | ${false} | ${issueSetTitleMutationHandlerSuccess} | ${updateEpicTitleMutationHandlerSuccess}
+ ${'epic'} | ${true} | ${updateEpicTitleMutationHandlerSuccess} | ${issueSetTitleMutationHandlerSuccess}
+ `(
+ 'updates $issuableType title',
+ async ({ issuableType, isEpicBoard, queryHandler, notCalledHandler }) => {
+ createWrapper({
+ provide: {
+ issuableType,
+ isEpicBoard,
+ },
+ });
+
+ await nextTick();
+
+ findFormInput().vm.$emit('input', TEST_TITLE);
+ findForm().vm.$emit('submit', { preventDefault: () => {} });
+ await nextTick();
+
+ expect(queryHandler).toHaveBeenCalled();
+ expect(notCalledHandler).not.toHaveBeenCalled();
+ },
+ );
+
describe('when submitting and invalid title', () => {
beforeEach(async () => {
createWrapper();
@@ -146,8 +159,8 @@ describe('BoardSidebarTitle', () => {
await nextTick();
});
- it('commits change to the server', () => {
- expect(storeDispatch).not.toHaveBeenCalled();
+ it('does not update title', () => {
+ expect(issueSetTitleMutationHandlerSuccess).not.toHaveBeenCalled();
});
});
@@ -194,7 +207,7 @@ describe('BoardSidebarTitle', () => {
});
it('collapses sidebar and render former title', () => {
- expect(storeDispatch).not.toHaveBeenCalled();
+ expect(issueSetTitleMutationHandlerSuccess).not.toHaveBeenCalled();
expect(findCollapsed().isVisible()).toBe(true);
expect(findTitle().text()).toBe(TEST_ISSUE_B.title);
});
@@ -202,47 +215,23 @@ describe('BoardSidebarTitle', () => {
describe('when the mutation fails', () => {
beforeEach(async () => {
- createWrapper({ item: TEST_ISSUE_B });
+ createWrapper({
+ item: TEST_ISSUE_B,
+ issueSetTitleMutationHandler: issueSetTitleMutationHandlerFailure,
+ });
findFormInput().vm.$emit('input', 'Invalid title');
findForm().vm.$emit('submit', { preventDefault: () => {} });
await nextTick();
});
- it('collapses sidebar and renders former item title', () => {
+ it('collapses sidebar and renders former item title', async () => {
expect(findCollapsed().isVisible()).toBe(true);
expect(findTitle().text()).toContain(TEST_ISSUE_B.title);
+ await waitForPromises();
expect(cacheUpdates.setError).toHaveBeenCalledWith(
expect.objectContaining({ message: 'An error occurred when updating the title' }),
);
});
});
-
- describe('Apollo boards', () => {
- it.each`
- issuableType | isEpicBoard | queryHandler | notCalledHandler
- ${'issue'} | ${false} | ${issueSetTitleMutationHandlerSuccess} | ${updateEpicTitleMutationHandlerSuccess}
- ${'epic'} | ${true} | ${updateEpicTitleMutationHandlerSuccess} | ${issueSetTitleMutationHandlerSuccess}
- `(
- 'updates $issuableType title',
- async ({ issuableType, isEpicBoard, queryHandler, notCalledHandler }) => {
- createWrapper({
- provide: {
- issuableType,
- isEpicBoard,
- isApolloBoard: true,
- },
- });
-
- await nextTick();
-
- findFormInput().vm.$emit('input', TEST_TITLE);
- findForm().vm.$emit('submit', { preventDefault: () => {} });
- await nextTick();
-
- expect(queryHandler).toHaveBeenCalled();
- expect(notCalledHandler).not.toHaveBeenCalled();
- },
- );
- });
});
diff --git a/spec/frontend/ci/pipeline_editor/pipeline_editor_home_spec.js b/spec/frontend/ci/pipeline_editor/pipeline_editor_home_spec.js
index ca5f80f331c..fd0d17ee05b 100644
--- a/spec/frontend/ci/pipeline_editor/pipeline_editor_home_spec.js
+++ b/spec/frontend/ci/pipeline_editor/pipeline_editor_home_spec.js
@@ -1,5 +1,6 @@
import { shallowMount } from '@vue/test-utils';
-import { GlButton, GlModal } from '@gitlab/ui';
+import { GlModal } from '@gitlab/ui';
+import { nextTick } from 'vue';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import setWindowLocation from 'helpers/set_window_location_helper';
import CommitSection from '~/ci/pipeline_editor/components/commit/commit_section.vue';
@@ -60,7 +61,7 @@ describe('Pipeline editor home wrapper', () => {
const findPipelineEditorFileTree = () => wrapper.findComponent(PipelineEditorFileTree);
const findPipelineEditorHeader = () => wrapper.findComponent(PipelineEditorHeader);
const findPipelineEditorTabs = () => wrapper.findComponent(PipelineEditorTabs);
- const findFileTreeBtn = () => wrapper.findByTestId('file-tree-toggle');
+ const findPipelineEditorFileNav = () => wrapper.findComponent(PipelineEditorFileNav);
const clickHelpBtn = async () => {
await findPipelineEditorDrawer().vm.$emit('switch-drawer', EDITOR_APP_DRAWER_HELP);
@@ -279,24 +280,16 @@ describe('Pipeline editor home wrapper', () => {
describe('file tree', () => {
const toggleFileTree = async () => {
- await findFileTreeBtn().vm.$emit('click');
+ findPipelineEditorFileNav().vm.$emit('toggle-file-tree');
+ await nextTick();
};
- describe('button toggle', () => {
+ describe('file navigation', () => {
beforeEach(() => {
- createComponent({
- stubs: {
- GlButton,
- PipelineEditorFileNav,
- },
- });
- });
-
- it('shows button toggle', () => {
- expect(findFileTreeBtn().exists()).toBe(true);
+ createComponent({});
});
- it('toggles the drawer on button click', async () => {
+ it('toggles the drawer on `toggle-file-tree` event', async () => {
await toggleFileTree();
expect(findPipelineEditorFileTree().exists()).toBe(true);
diff --git a/spec/frontend/ci/runner/components/runner_filtered_search_bar_spec.js b/spec/frontend/ci/runner/components/runner_filtered_search_bar_spec.js
index ffc19d66cac..62ab40b2ebb 100644
--- a/spec/frontend/ci/runner/components/runner_filtered_search_bar_spec.js
+++ b/spec/frontend/ci/runner/components/runner_filtered_search_bar_spec.js
@@ -1,4 +1,4 @@
-import { GlFilteredSearch, GlDropdown, GlDropdownItem } from '@gitlab/ui';
+import { GlFilteredSearch, GlSorting } from '@gitlab/ui';
import { nextTick } from 'vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { assertProps } from 'helpers/assert_props';
@@ -32,7 +32,12 @@ describe('RunnerList', () => {
const findFilteredSearch = () => wrapper.findComponent(FilteredSearch);
const findGlFilteredSearch = () => wrapper.findComponent(GlFilteredSearch);
- const findSortOptions = () => wrapper.findAllComponents(GlDropdownItem);
+ const findGlSorting = () => wrapper.findComponent(GlSorting);
+ const getSortOptions = () => findGlSorting().props('sortOptions');
+ const getSelectedSortOption = () => {
+ const sortBy = findGlSorting().props('sortBy');
+ return getSortOptions().find(({ value }) => sortBy === value)?.text;
+ };
const mockOtherSort = CONTACTED_DESC;
const mockFilters = [
@@ -56,8 +61,6 @@ describe('RunnerList', () => {
stubs: {
FilteredSearch,
GlFilteredSearch,
- GlDropdown,
- GlDropdownItem,
},
...options,
});
@@ -74,9 +77,10 @@ describe('RunnerList', () => {
it('sets sorting options', () => {
const SORT_OPTIONS_COUNT = 2;
- expect(findSortOptions()).toHaveLength(SORT_OPTIONS_COUNT);
- expect(findSortOptions().at(0).text()).toBe('Created date');
- expect(findSortOptions().at(1).text()).toBe('Last contact');
+ const sortOptionsProp = getSortOptions();
+ expect(sortOptionsProp).toHaveLength(SORT_OPTIONS_COUNT);
+ expect(sortOptionsProp[0].text).toBe('Created date');
+ expect(sortOptionsProp[1].text).toBe('Last contact');
});
it('sets tokens to the filtered search', () => {
@@ -141,12 +145,7 @@ describe('RunnerList', () => {
});
it('sort option is selected', () => {
- expect(
- findSortOptions()
- .filter((w) => w.props('isChecked'))
- .at(0)
- .text(),
- ).toEqual('Last contact');
+ expect(getSelectedSortOption()).toBe('Last contact');
});
it('when the user sets a filter, the "search" preserves the other filters', async () => {
@@ -181,7 +180,7 @@ describe('RunnerList', () => {
});
it('when the user sets a sorting method, the "search" is emitted with the sort', () => {
- findSortOptions().at(1).vm.$emit('click');
+ findGlSorting().vm.$emit('sortByChange', 2);
expectToHaveLastEmittedInput({
runnerType: null,
diff --git a/spec/frontend/ide/components/repo_commit_section_spec.js b/spec/frontend/ide/components/repo_commit_section_spec.js
index ead609421b7..3dd9ae1285d 100644
--- a/spec/frontend/ide/components/repo_commit_section_spec.js
+++ b/spec/frontend/ide/components/repo_commit_section_spec.js
@@ -71,11 +71,8 @@ describe('RepoCommitSection', () => {
createComponent();
});
- it('renders no changes text', () => {
- expect(wrapper.findComponent(EmptyState).text().trim()).toContain('No changes');
- expect(wrapper.findComponent(EmptyState).find('img').attributes('src')).toBe(
- TEST_NO_CHANGES_SVG,
- );
+ it('renders empty state component', () => {
+ expect(wrapper.findComponent(EmptyState).exists()).toBe(true);
});
});
diff --git a/spec/frontend/integrations/edit/components/jira_trigger_fields_spec.js b/spec/frontend/integrations/edit/components/jira_trigger_fields_spec.js
index a038b63d28c..08f758c1382 100644
--- a/spec/frontend/integrations/edit/components/jira_trigger_fields_spec.js
+++ b/spec/frontend/integrations/edit/components/jira_trigger_fields_spec.js
@@ -1,11 +1,15 @@
import { GlFormCheckbox } from '@gitlab/ui';
-import { nextTick } from 'vue';
+import Vue, { nextTick } from 'vue';
+// eslint-disable-next-line no-restricted-imports
+import Vuex from 'vuex';
import { mountExtended } from 'helpers/vue_test_utils_helper';
-
import JiraTriggerFields from '~/integrations/edit/components/jira_trigger_fields.vue';
+Vue.use(Vuex);
+
describe('JiraTriggerFields', () => {
let wrapper;
+ let store;
const defaultProps = {
initialTriggerCommit: false,
@@ -14,12 +18,16 @@ describe('JiraTriggerFields', () => {
};
const createComponent = (props, isInheriting = false) => {
- wrapper = mountExtended(JiraTriggerFields, {
- propsData: { ...defaultProps, ...props },
- computed: {
+ store = new Vuex.Store({
+ getters: {
isInheriting: () => isInheriting,
},
});
+
+ wrapper = mountExtended(JiraTriggerFields, {
+ propsData: { ...defaultProps, ...props },
+ store,
+ });
};
const findCommentSettings = () => wrapper.findByTestId('comment-settings');
diff --git a/spec/frontend/integrations/edit/components/trigger_field_spec.js b/spec/frontend/integrations/edit/components/trigger_field_spec.js
index b3d6784959f..1dad3b27618 100644
--- a/spec/frontend/integrations/edit/components/trigger_field_spec.js
+++ b/spec/frontend/integrations/edit/components/trigger_field_spec.js
@@ -1,12 +1,17 @@
-import { nextTick } from 'vue';
+import Vue, { nextTick } from 'vue';
+// eslint-disable-next-line no-restricted-imports
+import Vuex from 'vuex';
import { shallowMount } from '@vue/test-utils';
import { GlFormCheckbox, GlFormInput } from '@gitlab/ui';
import TriggerField from '~/integrations/edit/components/trigger_field.vue';
import { integrationTriggerEventTitles } from '~/integrations/constants';
+Vue.use(Vuex);
+
describe('TriggerField', () => {
let wrapper;
+ let store;
const defaultProps = {
event: { name: 'push_events' },
@@ -15,12 +20,16 @@ describe('TriggerField', () => {
const mockField = { name: 'push_channel' };
const createComponent = ({ props = {}, isInheriting = false } = {}) => {
- wrapper = shallowMount(TriggerField, {
- propsData: { ...defaultProps, ...props },
- computed: {
+ store = new Vuex.Store({
+ getters: {
isInheriting: () => isInheriting,
},
});
+
+ wrapper = shallowMount(TriggerField, {
+ propsData: { ...defaultProps, ...props },
+ store,
+ });
};
const findGlFormCheckbox = () => wrapper.findComponent(GlFormCheckbox);
diff --git a/spec/frontend/integrations/edit/components/trigger_fields_spec.js b/spec/frontend/integrations/edit/components/trigger_fields_spec.js
index defa02aefd2..97ac01e2f26 100644
--- a/spec/frontend/integrations/edit/components/trigger_fields_spec.js
+++ b/spec/frontend/integrations/edit/components/trigger_fields_spec.js
@@ -1,23 +1,32 @@
import { GlFormGroup, GlFormCheckbox, GlFormInput } from '@gitlab/ui';
+import Vue from 'vue';
+// eslint-disable-next-line no-restricted-imports
+import Vuex from 'vuex';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import { placeholderForType } from 'jh_else_ce/integrations/constants';
-
import TriggerFields from '~/integrations/edit/components/trigger_fields.vue';
+Vue.use(Vuex);
+
describe('TriggerFields', () => {
let wrapper;
+ let store;
const defaultProps = {
type: 'slack',
};
const createComponent = (props, isInheriting = false) => {
- wrapper = mountExtended(TriggerFields, {
- propsData: { ...defaultProps, ...props },
- computed: {
+ store = new Vuex.Store({
+ getters: {
isInheriting: () => isInheriting,
},
});
+
+ wrapper = mountExtended(TriggerFields, {
+ propsData: { ...defaultProps, ...props },
+ store,
+ });
};
const findTriggerLabel = () => wrapper.findByTestId('trigger-fields-group').find('label');
diff --git a/spec/frontend/usage_quotas/storage/components/project_storage_app_spec.js b/spec/frontend/usage_quotas/storage/components/project_storage_app_spec.js
index b80223cb404..babefe1dd19 100644
--- a/spec/frontend/usage_quotas/storage/components/project_storage_app_spec.js
+++ b/spec/frontend/usage_quotas/storage/components/project_storage_app_spec.js
@@ -16,6 +16,7 @@ import {
NAMESPACE_STORAGE_TYPES,
TOTAL_USAGE_DEFAULT_TEXT,
} from '~/usage_quotas/storage/constants';
+import getCostFactoredProjectStorageStatistics from 'ee_else_ce/usage_quotas/storage/queries/cost_factored_project_storage.query.graphql';
import getProjectStorageStatistics from 'ee_else_ce/usage_quotas/storage/queries/project_storage.query.graphql';
import { numberToHumanSize } from '~/lib/utils/number_utils';
import {
@@ -38,7 +39,10 @@ describe('ProjectStorageApp', () => {
response = jest.fn().mockResolvedValue(mockedValue);
}
- const requestHandlers = [[getProjectStorageStatistics, response]];
+ const requestHandlers = [
+ [getProjectStorageStatistics, response],
+ [getCostFactoredProjectStorageStatistics, response],
+ ];
return createMockApollo(requestHandlers);
};
@@ -187,4 +191,30 @@ describe('ProjectStorageApp', () => {
]);
});
});
+
+ describe('when displayCostFactoredStorageSizeOnProjectPages feature flag is enabled', () => {
+ let mockApollo;
+ beforeEach(async () => {
+ mockApollo = createMockApolloProvider({
+ mockedValue: mockGetProjectStorageStatisticsGraphQLResponse,
+ });
+ createComponent({
+ mockApollo,
+ provide: {
+ glFeatures: {
+ displayCostFactoredStorageSizeOnProjectPages: true,
+ },
+ },
+ });
+ await waitForPromises();
+ });
+
+ it('renders correct total usage', () => {
+ const expectedValue = numberToHumanSize(
+ mockGetProjectStorageStatisticsGraphQLResponse.data.project.statistics.storageSize,
+ 1,
+ );
+ expect(findUsagePercentage().text()).toBe(expectedValue);
+ });
+ });
});
diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_bar_root_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_bar_root_spec.js
index bb612a13209..3a5c7d7729f 100644
--- a/spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_bar_root_spec.js
+++ b/spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_bar_root_spec.js
@@ -1,11 +1,4 @@
-import {
- GlFilteredSearch,
- GlButtonGroup,
- GlButton,
- GlDropdown,
- GlDropdownItem,
- GlFormCheckbox,
-} from '@gitlab/ui';
+import { GlDropdownItem, GlSorting, GlFilteredSearch, GlFormCheckbox } from '@gitlab/ui';
import { shallowMount, mount } from '@vue/test-utils';
import { nextTick } from 'vue';
@@ -13,7 +6,6 @@ import RecentSearchesService from '~/filtered_search/services/recent_searches_se
import RecentSearchesStore from '~/filtered_search/stores/recent_searches_store';
import {
FILTERED_SEARCH_TERM,
- SORT_DIRECTION,
TOKEN_TYPE_AUTHOR,
TOKEN_TYPE_LABEL,
TOKEN_TYPE_MILESTONE,
@@ -48,6 +40,7 @@ const createComponent = ({
recentSearchesStorageKey = 'requirements',
tokens = mockAvailableTokens,
sortOptions,
+ initialSortBy,
initialFilterValue = [],
showCheckbox = false,
checkboxChecked = false,
@@ -61,6 +54,7 @@ const createComponent = ({
recentSearchesStorageKey,
tokens,
sortOptions,
+ initialSortBy,
initialFilterValue,
showCheckbox,
checkboxChecked,
@@ -72,34 +66,38 @@ const createComponent = ({
describe('FilteredSearchBarRoot', () => {
let wrapper;
- const findGlButton = () => wrapper.findComponent(GlButton);
- const findGlDropdown = () => wrapper.findComponent(GlDropdown);
+ const findGlSorting = () => wrapper.findComponent(GlSorting);
const findGlFilteredSearch = () => wrapper.findComponent(GlFilteredSearch);
- beforeEach(() => {
- wrapper = createComponent({ sortOptions: mockSortOptions });
- });
-
describe('data', () => {
- it('initializes `filterValue`, `selectedSortOption` and `selectedSortDirection` data props and displays the sort dropdown', () => {
- expect(wrapper.vm.filterValue).toEqual([]);
- expect(wrapper.vm.selectedSortOption).toBe(mockSortOptions[0]);
- expect(wrapper.vm.selectedSortDirection).toBe(SORT_DIRECTION.descending);
- expect(wrapper.findComponent(GlButtonGroup).exists()).toBe(true);
- expect(wrapper.findComponent(GlButton).exists()).toBe(true);
- expect(wrapper.findComponent(GlDropdown).exists()).toBe(true);
- expect(wrapper.findComponent(GlDropdownItem).exists()).toBe(true);
+ describe('when `sortOptions` are provided', () => {
+ beforeEach(() => {
+ wrapper = createComponent({ sortOptions: mockSortOptions });
+ });
+
+ it('sets a correct initial value for GlFilteredSearch', () => {
+ expect(findGlFilteredSearch().props('value')).toEqual([]);
+ });
+
+ it('emits an event with the selectedSortOption provided by default', async () => {
+ findGlSorting().vm.$emit('sortByChange', mockSortOptions[1].id);
+ await nextTick();
+
+ expect(wrapper.emitted('onSort')[0]).toEqual([mockSortOptions[1].sortDirection.descending]);
+ });
+
+ it('emits an event with the selectedSortDirection provided by default', async () => {
+ findGlSorting().vm.$emit('sortDirectionChange', true);
+ await nextTick();
+
+ expect(wrapper.emitted('onSort')[0]).toEqual([mockSortOptions[0].sortDirection.ascending]);
+ });
});
- it('does not initialize `selectedSortOption` and `selectedSortDirection` when `sortOptions` is not applied and hides the sort dropdown', () => {
- const wrapperNoSort = createComponent();
+ it('does not initialize the sort dropdown when `sortOptions` are not provided', () => {
+ wrapper = createComponent();
- expect(wrapperNoSort.vm.filterValue).toEqual([]);
- expect(wrapperNoSort.vm.selectedSortOption).toBe(undefined);
- expect(wrapperNoSort.findComponent(GlButtonGroup).exists()).toBe(false);
- expect(wrapperNoSort.findComponent(GlButton).exists()).toBe(false);
- expect(wrapperNoSort.findComponent(GlDropdown).exists()).toBe(false);
- expect(wrapperNoSort.findComponent(GlDropdownItem).exists()).toBe(false);
+ expect(findGlSorting().exists()).toBe(false);
});
});
@@ -125,27 +123,27 @@ describe('FilteredSearchBarRoot', () => {
});
describe('sortDirectionIcon', () => {
- it('renders `sort-highest` descending icon by default', () => {
- expect(findGlButton().props('icon')).toBe('sort-highest');
- expect(findGlButton().attributes()).toMatchObject({
- 'aria-label': 'Sort direction: Descending',
- title: 'Sort direction: Descending',
- });
+ beforeEach(() => {
+ wrapper = createComponent({ sortOptions: mockSortOptions });
+ });
+
+ it('passes isAscending=false to GlSorting by default', () => {
+ expect(findGlSorting().props('isAscending')).toBe(false);
});
it('renders `sort-lowest` ascending icon when the sort button is clicked', async () => {
- findGlButton().vm.$emit('click');
+ findGlSorting().vm.$emit('sortDirectionChange', true);
await nextTick();
- expect(findGlButton().props('icon')).toBe('sort-lowest');
- expect(findGlButton().attributes()).toMatchObject({
- 'aria-label': 'Sort direction: Ascending',
- title: 'Sort direction: Ascending',
- });
+ expect(findGlSorting().props('isAscending')).toBe(true);
});
});
describe('filteredRecentSearches', () => {
+ beforeEach(() => {
+ wrapper = createComponent();
+ });
+
it('returns array of recent searches filtering out any string type (unsupported) items', async () => {
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
@@ -227,34 +225,37 @@ describe('FilteredSearchBarRoot', () => {
});
});
- describe('handleSortOptionClick', () => {
- it('emits component event `onSort` with selected sort by value', () => {
- wrapper.vm.handleSortOptionClick(mockSortOptions[1]);
+ describe('handleSortOptionChange', () => {
+ it('emits component event `onSort` with selected sort by value', async () => {
+ wrapper = createComponent({ sortOptions: mockSortOptions });
+
+ findGlSorting().vm.$emit('sortByChange', mockSortOptions[1].id);
+ await nextTick();
expect(wrapper.vm.selectedSortOption).toBe(mockSortOptions[1]);
expect(wrapper.emitted('onSort')[0]).toEqual([mockSortOptions[1].sortDirection.descending]);
});
});
- describe('handleSortDirectionClick', () => {
+ describe('handleSortDirectionChange', () => {
beforeEach(() => {
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({
- selectedSortOption: mockSortOptions[0],
+ wrapper = createComponent({
+ sortOptions: mockSortOptions,
+ initialSortBy: mockSortOptions[0].sortDirection.descending,
});
});
- it('sets `selectedSortDirection` to be opposite of its current value', () => {
- expect(wrapper.vm.selectedSortDirection).toBe(SORT_DIRECTION.descending);
+ it('sets sort direction to be opposite of its current value', async () => {
+ expect(findGlSorting().props('isAscending')).toBe(false);
- wrapper.vm.handleSortDirectionClick();
+ findGlSorting().vm.$emit('sortDirectionChange', true);
+ await nextTick();
- expect(wrapper.vm.selectedSortDirection).toBe(SORT_DIRECTION.ascending);
+ expect(findGlSorting().props('isAscending')).toBe(true);
});
it('emits component event `onSort` with opposite of currently selected sort by value', () => {
- wrapper.vm.handleSortDirectionClick();
+ findGlSorting().vm.$emit('sortDirectionChange', true);
expect(wrapper.emitted('onSort')[0]).toEqual([mockSortOptions[0].sortDirection.ascending]);
});
@@ -288,6 +289,8 @@ describe('FilteredSearchBarRoot', () => {
const mockFilters = [tokenValueAuthor, 'foo'];
beforeEach(async () => {
+ wrapper = createComponent();
+
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({
@@ -358,19 +361,14 @@ describe('FilteredSearchBarRoot', () => {
});
describe('template', () => {
- beforeEach(async () => {
+ it('renders gl-filtered-search component', async () => {
+ wrapper = createComponent();
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
- wrapper.setData({
- selectedSortOption: mockSortOptions[0],
- selectedSortDirection: SORT_DIRECTION.descending,
+ await wrapper.setData({
recentSearches: mockHistoryItems,
});
- await nextTick();
- });
-
- it('renders gl-filtered-search component', () => {
const glFilteredSearchEl = wrapper.findComponent(GlFilteredSearch);
expect(glFilteredSearchEl.props('placeholder')).toBe('Filter requirements');
@@ -454,25 +452,28 @@ describe('FilteredSearchBarRoot', () => {
});
it('renders sort dropdown component', () => {
- expect(wrapper.findComponent(GlButtonGroup).exists()).toBe(true);
- expect(wrapper.findComponent(GlDropdown).exists()).toBe(true);
- expect(wrapper.findComponent(GlDropdown).props('text')).toBe(mockSortOptions[0].title);
- });
-
- it('renders sort dropdown items', () => {
- const dropdownItemsEl = wrapper.findAllComponents(GlDropdownItem);
+ wrapper = createComponent({ sortOptions: mockSortOptions });
- expect(dropdownItemsEl).toHaveLength(mockSortOptions.length);
- expect(dropdownItemsEl.at(0).text()).toBe(mockSortOptions[0].title);
- expect(dropdownItemsEl.at(0).props('isChecked')).toBe(true);
- expect(dropdownItemsEl.at(1).text()).toBe(mockSortOptions[1].title);
+ expect(findGlSorting().exists()).toBe(true);
});
- it('renders sort direction button', () => {
- const sortButtonEl = wrapper.findComponent(GlButton);
-
- expect(sortButtonEl.attributes('title')).toBe('Sort direction: Descending');
- expect(sortButtonEl.props('icon')).toBe('sort-highest');
+ it('renders sort dropdown items', () => {
+ wrapper = createComponent({ sortOptions: mockSortOptions });
+
+ const { sortOptions, sortBy } = findGlSorting().props();
+
+ expect(sortOptions).toEqual([
+ {
+ value: mockSortOptions[0].id,
+ text: mockSortOptions[0].title,
+ },
+ {
+ value: mockSortOptions[1].id,
+ text: mockSortOptions[1].title,
+ },
+ ]);
+
+ expect(sortBy).toBe(mockSortOptions[0].id);
});
});
@@ -483,6 +484,10 @@ describe('FilteredSearchBarRoot', () => {
value: { data: '' },
};
+ beforeEach(() => {
+ wrapper = createComponent({ sortOptions: mockSortOptions });
+ });
+
it('syncs filter value', async () => {
await wrapper.setProps({ initialFilterValue: [tokenValue], syncFilterAndSort: true });
@@ -498,17 +503,33 @@ describe('FilteredSearchBarRoot', () => {
it('syncs sort values', async () => {
await wrapper.setProps({ initialSortBy: 'updated_asc', syncFilterAndSort: true });
- expect(findGlDropdown().props('text')).toBe('Last updated');
- expect(findGlButton().props('icon')).toBe('sort-lowest');
- expect(findGlButton().attributes('aria-label')).toBe('Sort direction: Ascending');
+ expect(findGlSorting().props()).toMatchObject({
+ sortBy: 2,
+ isAscending: true,
+ });
});
it('does not sync sort values when syncFilterAndSort=false', async () => {
await wrapper.setProps({ initialSortBy: 'updated_asc', syncFilterAndSort: false });
- expect(findGlDropdown().props('text')).toBe('Created date');
- expect(findGlButton().props('icon')).toBe('sort-highest');
- expect(findGlButton().attributes('aria-label')).toBe('Sort direction: Descending');
+ expect(findGlSorting().props()).toMatchObject({
+ sortBy: 1,
+ isAscending: false,
+ });
+ });
+
+ it('does not sync sort values when initialSortBy is unset', async () => {
+ // Give initialSort some value which changes the current sort option...
+ await wrapper.setProps({ initialSortBy: 'updated_asc', syncFilterAndSort: true });
+
+ // ... Read the new sort options...
+ const { sortBy, isAscending } = findGlSorting().props();
+
+ // ... Then *unset* initialSortBy...
+ await wrapper.setProps({ initialSortBy: undefined });
+
+ // ... The sort options should not have changed.
+ expect(findGlSorting().props()).toMatchObject({ sortBy, isAscending });
});
});
});
diff --git a/spec/frontend/work_items/mock_data.js b/spec/frontend/work_items/mock_data.js
index 41e8a01de36..8df46403b90 100644
--- a/spec/frontend/work_items/mock_data.js
+++ b/spec/frontend/work_items/mock_data.js
@@ -1600,18 +1600,21 @@ export const availableWorkItemsResponse = {
id: 'gid://gitlab/WorkItem/458',
iid: '2',
title: 'Task 1',
+ confidential: false,
__typename: 'WorkItem',
},
{
id: 'gid://gitlab/WorkItem/459',
iid: '3',
title: 'Task 2',
+ confidential: false,
__typename: 'WorkItem',
},
{
id: 'gid://gitlab/WorkItem/460',
iid: '4',
title: 'Task 3',
+ confidential: false,
__typename: 'WorkItem',
},
],
@@ -1631,18 +1634,21 @@ export const availableObjectivesResponse = {
id: 'gid://gitlab/WorkItem/716',
iid: '122',
title: 'Objective 101',
+ confidential: false,
__typename: 'WorkItem',
},
{
id: 'gid://gitlab/WorkItem/712',
iid: '118',
title: 'Objective 103',
+ confidential: false,
__typename: 'WorkItem',
},
{
id: 'gid://gitlab/WorkItem/711',
iid: '117',
title: 'Objective 102',
+ confidential: false,
__typename: 'WorkItem',
},
],
@@ -1662,6 +1668,7 @@ export const searchedObjectiveResponse = {
id: 'gid://gitlab/WorkItem/716',
iid: '122',
title: 'Objective 101',
+ confidential: false,
__typename: 'WorkItem',
},
],
@@ -1681,6 +1688,7 @@ export const searchWorkItemsTextResponse = {
id: 'gid://gitlab/WorkItem/459',
iid: '3',
title: 'Task 2',
+ confidential: false,
__typename: 'WorkItem',
},
],
@@ -1703,6 +1711,7 @@ export const searchWorkItemsIidResponse = {
id: 'gid://gitlab/WorkItem/460',
iid: '101',
title: 'Task 3',
+ confidential: false,
__typename: 'WorkItem',
},
],
@@ -1722,6 +1731,7 @@ export const searchWorkItemsTextIidResponse = {
id: 'gid://gitlab/WorkItem/459',
iid: '3',
title: 'Task 123',
+ confidential: false,
__typename: 'WorkItem',
},
],
@@ -1732,6 +1742,7 @@ export const searchWorkItemsTextIidResponse = {
id: 'gid://gitlab/WorkItem/460',
iid: '123',
title: 'Task 2',
+ confidential: false,
__typename: 'WorkItem',
},
],
diff --git a/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb b/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb
index c57b8bb5992..85b1337438e 100644
--- a/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb
+++ b/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb
@@ -27,7 +27,6 @@ RSpec.describe 'cross-database foreign keys' do
'namespace_commit_emails.email_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/429804
'namespace_commit_emails.user_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/429804
'path_locks.user_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/429380
- 'project_authorizations.user_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/422044
'protected_branch_push_access_levels.user_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/431054
'protected_branch_merge_access_levels.user_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/431055
'security_orchestration_policy_configurations.bot_user_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/429438
diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb
index 6430fc2ffc8..e142c35a681 100644
--- a/spec/models/event_spec.rb
+++ b/spec/models/event_spec.rb
@@ -68,15 +68,6 @@ RSpec.describe Event, feature_category: :user_profile do
end.not_to change { project.last_repository_updated_at }
end
end
-
- describe 'after_create UserInteractedProject.track' do
- let(:event) { build(:push_event, project: project, author: project.first_owner) }
-
- it 'passes event to UserInteractedProject.track' do
- expect(UserInteractedProject).to receive(:track).with(event)
- event.save!
- end
- end
end
describe 'validations' do
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index 0236ae9dc9e..279d4bc194e 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -391,12 +391,19 @@ RSpec.describe Group, feature_category: :groups_and_projects do
expect(internal_group.errors[:visibility_level]).to include('private is not allowed since this group contains projects with higher visibility.')
end
- it 'is valid if higher visibility project is deleted' do
+ it 'is valid if higher visibility project is currently undergoing deletion' do
internal_project.update_attribute(:pending_delete, true)
internal_group.visibility_level = Gitlab::VisibilityLevel::PRIVATE
expect(internal_group).to be_valid
end
+
+ it 'is valid if higher visibility project is pending deletion via marked_for_deletion_at' do
+ internal_project.update_attribute(:marked_for_deletion_at, Time.current)
+ internal_group.visibility_level = Gitlab::VisibilityLevel::PRIVATE
+
+ expect(internal_group).to be_valid
+ end
end
context 'when group has a higher visibility' do
diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb
index b4941c71d6a..db2ae319bc9 100644
--- a/spec/models/member_spec.rb
+++ b/spec/models/member_spec.rb
@@ -1184,8 +1184,8 @@ RSpec.describe Member, feature_category: :groups_and_projects do
context 'with loose foreign key on members.user_id' do
it_behaves_like 'cleanup by a loose foreign key' do
- let!(:parent) { create(:user) }
- let!(:model) { create(:group_member, user: parent) }
+ let_it_be(:parent) { create(:user) }
+ let_it_be(:model) { create(:group_member, user: parent) }
end
end
end
diff --git a/spec/models/ml/candidate_spec.rb b/spec/models/ml/candidate_spec.rb
index d5b71e2c3f7..503d3514a72 100644
--- a/spec/models/ml/candidate_spec.rb
+++ b/spec/models/ml/candidate_spec.rb
@@ -272,8 +272,8 @@ RSpec.describe Ml::Candidate, factory_default: :keep, feature_category: :mlops d
context 'with loose foreign key on ml_candidates.ci_build_id' do
it_behaves_like 'cleanup by a loose foreign key' do
- let!(:parent) { create(:ci_build) }
- let!(:model) { create(:ml_candidates, ci_build: parent) }
+ let_it_be(:parent) { create(:ci_build) }
+ let_it_be(:model) { create(:ml_candidates, ci_build: parent) }
end
end
end
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index 85569a68252..3054bcacd9c 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -2387,8 +2387,8 @@ RSpec.describe Namespace, feature_category: :groups_and_projects do
context 'with loose foreign key on organization_id' do
it_behaves_like 'cleanup by a loose foreign key' do
- let!(:parent) { create(:organization) }
- let!(:model) { create(:namespace, organization: parent) }
+ let_it_be(:parent) { create(:organization) }
+ let_it_be(:model) { create(:namespace, organization: parent) }
end
end
end
diff --git a/spec/models/notification_setting_spec.rb b/spec/models/notification_setting_spec.rb
index 9bf95051730..1bb639a5907 100644
--- a/spec/models/notification_setting_spec.rb
+++ b/spec/models/notification_setting_spec.rb
@@ -224,8 +224,8 @@ RSpec.describe NotificationSetting do
context 'with loose foreign key on notification_settings.user_id' do
it_behaves_like 'cleanup by a loose foreign key' do
- let!(:parent) { create(:user) }
- let!(:model) { create(:notification_setting, user: parent) }
+ let_it_be(:parent) { create(:user) }
+ let_it_be(:model) { create(:notification_setting, user: parent) }
end
end
end
diff --git a/spec/models/project_authorization_spec.rb b/spec/models/project_authorization_spec.rb
index 98816d7e1bc..64ad25f559d 100644
--- a/spec/models/project_authorization_spec.rb
+++ b/spec/models/project_authorization_spec.rb
@@ -153,4 +153,11 @@ RSpec.describe ProjectAuthorization, feature_category: :groups_and_projects do
expect(user.project_authorizations.pluck(:user_id, :project_id, :access_level)).to match_array(attributes.map(&:values))
end
end
+
+ context 'with loose foreign key on project_authorizations.user_id' do
+ it_behaves_like 'cleanup by a loose foreign key' do
+ let_it_be(:parent) { create(:user) }
+ let_it_be(:model) { create(:project_authorization, user: parent) }
+ end
+ end
end
diff --git a/spec/models/route_spec.rb b/spec/models/route_spec.rb
index aa5fc231e14..7cada013636 100644
--- a/spec/models/route_spec.rb
+++ b/spec/models/route_spec.rb
@@ -311,8 +311,8 @@ RSpec.describe Route do
context 'with loose foreign key on routes.namespace_id' do
it_behaves_like 'cleanup by a loose foreign key' do
- let!(:parent) { create(:namespace) }
- let!(:model) { parent.route }
+ let_it_be(:parent) { create(:namespace) }
+ let_it_be(:model) { parent.route }
end
end
end
diff --git a/spec/models/user_interacted_project_spec.rb b/spec/models/user_interacted_project_spec.rb
deleted file mode 100644
index aa038b06d8d..00000000000
--- a/spec/models/user_interacted_project_spec.rb
+++ /dev/null
@@ -1,52 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe UserInteractedProject do
- let_it_be(:project) { create(:project) }
- let_it_be(:author) { project.creator }
-
- describe '.track' do
- subject { described_class.track(event) }
-
- let(:event) { build(:event, project: project, author: author) }
-
- Event.actions.each_key do |action|
- context "for all actions (event types)" do
- let(:event) { build(:event, project: project, author: author, action: action) }
-
- it 'creates a record' do
- expect { subject }.to change { described_class.count }.from(0).to(1)
- end
- end
- end
-
- it 'sets project accordingly' do
- subject
- expect(described_class.first.project).to eq(event.project)
- end
-
- it 'sets user accordingly' do
- subject
- expect(described_class.first.user).to eq(event.author)
- end
-
- it 'only creates a record once per user/project' do
- expect do
- subject
- described_class.track(event)
- end.to change { described_class.count }.from(0).to(1)
- end
-
- describe 'with an event without a project' do
- let(:event) { build(:event, project: nil) }
-
- it 'ignores the event' do
- expect { subject }.not_to change { described_class.count }
- end
- end
- end
-
- it { is_expected.to validate_presence_of(:project_id) }
- it { is_expected.to validate_presence_of(:user_id) }
-end
diff --git a/spec/requests/ide_controller_spec.rb b/spec/requests/ide_controller_spec.rb
index 4131f1d26ec..fae3d9533c5 100644
--- a/spec/requests/ide_controller_spec.rb
+++ b/spec/requests/ide_controller_spec.rb
@@ -17,8 +17,6 @@ RSpec.describe IdeController, feature_category: :web_ide do
let_it_be(:creator) { project.creator }
let_it_be(:other_user) { create(:user) }
- let_it_be(:top_nav_partial) { 'layouts/header/_default' }
-
let(:user) { creator }
before do
@@ -156,27 +154,27 @@ RSpec.describe IdeController, feature_category: :web_ide do
end
end
- # This indirectly tests that `minimal: true` was passed to the fullscreen layout
- describe 'layout' do
- where(:ff_state, :expect_top_nav) do
- false | true
- true | false
+ describe 'legacy Web IDE' do
+ before do
+ stub_feature_flags(vscode_web_ide: false)
end
- with_them do
- before do
- stub_feature_flags(vscode_web_ide: ff_state)
+ it 'uses application layout' do
+ subject
- subject
- end
+ expect(response).to render_template('layouts/application')
+ end
+ end
- it 'handles rendering top nav' do
- if expect_top_nav
- expect(response).to render_template(top_nav_partial)
- else
- expect(response).not_to render_template(top_nav_partial)
- end
- end
+ describe 'vscode IDE' do
+ before do
+ stub_feature_flags(vscode_web_ide: true)
+ end
+
+ it 'uses fullscreen layout' do
+ subject
+
+ expect(response).to render_template('layouts/fullscreen')
end
end
end
diff --git a/spec/support/helpers/features/sorting_helpers.rb b/spec/support/helpers/features/sorting_helpers.rb
index 8dda16af625..0ea7c5432fb 100644
--- a/spec/support/helpers/features/sorting_helpers.rb
+++ b/spec/support/helpers/features/sorting_helpers.rb
@@ -22,9 +22,11 @@ module Features
# pajamas_sort_by is used to sort new pajamas dropdowns. When
# all of the dropdowns are converted, pajamas_sort_by can be renamed to sort_by
# https://gitlab.com/groups/gitlab-org/-/epics/7551
- def pajamas_sort_by(value)
- find('.filter-dropdown-container .gl-new-dropdown').click
- find('.gl-new-dropdown-item', text: value).click
+ def pajamas_sort_by(value, from: nil)
+ raise ArgumentError, 'The :from option must be given' if from.nil?
+
+ click_button from
+ find('[role="option"]', text: value).click
end
end
end
diff --git a/spec/support/rspec_order_todo.yml b/spec/support/rspec_order_todo.yml
index 688db4e9ffd..d3b93c67a0a 100644
--- a/spec/support/rspec_order_todo.yml
+++ b/spec/support/rspec_order_todo.yml
@@ -7569,7 +7569,6 @@
- './spec/models/user_custom_attribute_spec.rb'
- './spec/models/user_detail_spec.rb'
- './spec/models/user_highest_role_spec.rb'
-- './spec/models/user_interacted_project_spec.rb'
- './spec/models/user_mentions/commit_user_mention_spec.rb'
- './spec/models/user_mentions/issue_user_mention_spec.rb'
- './spec/models/user_mentions/merge_request_user_mention_spec.rb'
diff --git a/spec/views/layouts/fullscreen.html.haml_spec.rb b/spec/views/layouts/fullscreen.html.haml_spec.rb
index 2309e885b75..46c3b08b954 100644
--- a/spec/views/layouts/fullscreen.html.haml_spec.rb
+++ b/spec/views/layouts/fullscreen.html.haml_spec.rb
@@ -4,8 +4,6 @@ require 'spec_helper'
RSpec.describe 'layouts/fullscreen' do
let_it_be(:template) { 'layouts/fullscreen' }
- let_it_be(:top_nav_partial) { 'layouts/header/_default' }
- let_it_be(:top_nav_responsive_partial) { 'layouts/nav/_top_nav_responsive' }
let_it_be(:user) { create(:user) }
@@ -27,13 +25,6 @@ RSpec.describe 'layouts/fullscreen' do
expect(rendered).to have_selector(".flash-container.flash-container-no-margin")
end
- it 'renders top nav' do
- render
-
- expect(view).to render_template(top_nav_partial)
- expect(view).to render_template(top_nav_responsive_partial)
- end
-
it_behaves_like 'a layout which reflects the application theme setting'
it_behaves_like 'a layout which reflects the preferred language'
@@ -70,15 +61,4 @@ RSpec.describe 'layouts/fullscreen' do
end
end
end
-
- context 'when minimal is set' do
- subject { render(template: template, formats: :html, locals: { minimal: true }) }
-
- it 'does not render top nav' do
- subject
-
- expect(view).not_to render_template(top_nav_partial)
- expect(view).not_to render_template(top_nav_responsive_partial)
- end
- end
end