diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-04-29 12:09:48 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-04-29 12:09:48 +0300 |
commit | 7510fe06eba02c3cee247f8ceb4ee6f6a4de54f6 (patch) | |
tree | 95025711e64d000172e47209bfdc1d7cd7d6b972 /app | |
parent | 401607eed7e0918553e985e1f12e99dc1ff9e948 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
15 files changed, 163 insertions, 83 deletions
diff --git a/app/assets/javascripts/issues/list/components/issues_list_app.vue b/app/assets/javascripts/issues/list/components/issues_list_app.vue index a43aed6c521..eb7e85bde41 100644 --- a/app/assets/javascripts/issues/list/components/issues_list_app.vue +++ b/app/assets/javascripts/issues/list/components/issues_list_app.vue @@ -45,6 +45,7 @@ import { PAGE_SIZE, PARAM_PAGE_AFTER, PARAM_PAGE_BEFORE, + PARAM_SORT, PARAM_STATE, RELATIVE_POSITION_ASC, TOKEN_TYPE_ASSIGNEE, @@ -138,43 +139,17 @@ export default { }, }, data() { - const pageAfter = getParameterByName(PARAM_PAGE_AFTER); - const pageBefore = getParameterByName(PARAM_PAGE_BEFORE); - const state = getParameterByName(PARAM_STATE); - const defaultSortKey = state === IssuableStates.Closed ? UPDATED_DESC : CREATED_DESC; - const dashboardSortKey = getSortKey(this.initialSort); - const graphQLSortKey = - isSortKey(this.initialSort?.toUpperCase()) && this.initialSort.toUpperCase(); - - // The initial sort is an old enum value when it is saved on the dashboard issues page. - // The initial sort is a GraphQL enum value when it is saved on the Vue issues list page. - let sortKey = dashboardSortKey || graphQLSortKey || defaultSortKey; - - if (this.isIssueRepositioningDisabled && sortKey === RELATIVE_POSITION_ASC) { - this.showIssueRepositioningMessage(); - sortKey = defaultSortKey; - } - - const isSearchDisabled = - this.isAnonymousSearchDisabled && - !this.isSignedIn && - window.location.search.includes('search='); - - if (isSearchDisabled) { - this.showAnonymousSearchingMessage(); - } - return { exportCsvPathWithQuery: this.getExportCsvPathWithQuery(), - filterTokens: isSearchDisabled ? [] : getFilterTokens(window.location.search), + filterTokens: [], issues: [], issuesCounts: {}, issuesError: null, pageInfo: {}, - pageParams: getInitialPageParams(sortKey, pageAfter, pageBefore), + pageParams: {}, showBulkEditSidebar: false, - sortKey, - state: state || IssuableStates.Opened, + sortKey: CREATED_DESC, + state: IssuableStates.Opened, }; }, apollo: { @@ -416,7 +391,15 @@ export default { }; }, }, + watch: { + $route(newValue, oldValue) { + if (newValue.fullPath !== oldValue.fullPath) { + this.updateData(getParameterByName(PARAM_SORT)); + } + }, + }, created() { + this.updateData(this.initialSort); this.cache = {}; }, mounted() { @@ -516,6 +499,8 @@ export default { this.pageParams = getInitialPageParams(this.sortKey); } this.state = state; + + this.$router.push({ query: this.urlParams }); }, handleDismissAlert() { this.issuesError = null; @@ -525,8 +510,11 @@ export default { this.showAnonymousSearchingMessage(); return; } + this.pageParams = getInitialPageParams(this.sortKey); this.filterTokens = filter; + + this.$router.push({ query: this.urlParams }); }, handleNextPage() { this.pageParams = { @@ -534,6 +522,8 @@ export default { firstPageSize: PAGE_SIZE, }; scrollUp(); + + this.$router.push({ query: this.urlParams }); }, handlePreviousPage() { this.pageParams = { @@ -541,6 +531,8 @@ export default { lastPageSize: PAGE_SIZE, }; scrollUp(); + + this.$router.push({ query: this.urlParams }); }, handleReorder({ newIndex, oldIndex }) { const issueToMove = this.issues[oldIndex]; @@ -592,6 +584,8 @@ export default { if (this.isSignedIn) { this.saveSortPreference(sortKey); } + + this.$router.push({ query: this.urlParams }); }, saveSortPreference(sortKey) { this.$apollo @@ -623,6 +617,39 @@ export default { toggleBulkEditSidebar(showBulkEditSidebar) { this.showBulkEditSidebar = showBulkEditSidebar; }, + updateData(sortValue) { + const pageAfter = getParameterByName(PARAM_PAGE_AFTER); + const pageBefore = getParameterByName(PARAM_PAGE_BEFORE); + const state = getParameterByName(PARAM_STATE); + + const defaultSortKey = state === IssuableStates.Closed ? UPDATED_DESC : CREATED_DESC; + const dashboardSortKey = getSortKey(sortValue); + const graphQLSortKey = isSortKey(sortValue?.toUpperCase()) && sortValue.toUpperCase(); + + // The initial sort is an old enum value when it is saved on the dashboard issues page. + // The initial sort is a GraphQL enum value when it is saved on the Vue issues list page. + let sortKey = dashboardSortKey || graphQLSortKey || defaultSortKey; + + if (this.isIssueRepositioningDisabled && sortKey === RELATIVE_POSITION_ASC) { + this.showIssueRepositioningMessage(); + sortKey = defaultSortKey; + } + + const isSearchDisabled = + this.isAnonymousSearchDisabled && + !this.isSignedIn && + window.location.search.includes('search='); + + if (isSearchDisabled) { + this.showAnonymousSearchingMessage(); + } + + this.exportCsvPathWithQuery = this.getExportCsvPathWithQuery(); + this.filterTokens = isSearchDisabled ? [] : getFilterTokens(window.location.search); + this.pageParams = getInitialPageParams(sortKey, pageAfter, pageBefore); + this.sortKey = sortKey; + this.state = state || IssuableStates.Opened; + }, }, }; </script> @@ -649,10 +676,10 @@ export default { :is-manual-ordering="isManualOrdering" :show-bulk-edit-sidebar="showBulkEditSidebar" :show-pagination-controls="showPaginationControls" - :use-keyset-pagination="true" + sync-filter-and-sort + use-keyset-pagination :has-next-page="pageInfo.hasNextPage" :has-previous-page="pageInfo.hasPreviousPage" - :url-params="urlParams" @click-tab="handleClickTab" @dismiss-alert="handleDismissAlert" @filter="handleFilter" diff --git a/app/assets/javascripts/issues/list/constants.js b/app/assets/javascripts/issues/list/constants.js index 4b07a078512..d4e2cdcfb1d 100644 --- a/app/assets/javascripts/issues/list/constants.js +++ b/app/assets/javascripts/issues/list/constants.js @@ -58,6 +58,7 @@ export const PAGE_SIZE = 20; export const PAGE_SIZE_MANUAL = 100; export const PARAM_PAGE_AFTER = 'page_after'; export const PARAM_PAGE_BEFORE = 'page_before'; +export const PARAM_SORT = 'sort'; export const PARAM_STATE = 'state'; export const RELATIVE_POSITION = 'relative_position'; @@ -174,6 +175,7 @@ export const filters = { }, [OPERATOR_IS_NOT]: { [NORMAL_FILTER]: 'not[milestone_title]', + [SPECIAL_FILTER]: 'not[milestone_title]', }, }, }, @@ -258,6 +260,7 @@ export const filters = { }, [OPERATOR_IS_NOT]: { [NORMAL_FILTER]: 'not[iteration_id]', + [SPECIAL_FILTER]: 'not[iteration_id]', }, }, }, diff --git a/app/assets/javascripts/issues/list/index.js b/app/assets/javascripts/issues/list/index.js index 3b2d37eab74..61cd3840cc1 100644 --- a/app/assets/javascripts/issues/list/index.js +++ b/app/assets/javascripts/issues/list/index.js @@ -1,6 +1,7 @@ import produce from 'immer'; import Vue from 'vue'; import VueApollo from 'vue-apollo'; +import VueRouter from 'vue-router'; import getIssuesQuery from 'ee_else_ce/issues/list/queries/get_issues.query.graphql'; import IssuesListApp from 'ee_else_ce/issues/list/components/issues_list_app.vue'; import createDefaultClient from '~/lib/graphql'; @@ -53,6 +54,7 @@ export function mountIssuesListApp() { } Vue.use(VueApollo); + Vue.use(VueRouter); const resolvers = { Mutation: { @@ -74,11 +76,6 @@ export function mountIssuesListApp() { }, }; - const defaultClient = createDefaultClient(resolvers); - const apolloProvider = new VueApollo({ - defaultClient, - }); - const { autocompleteAwardEmojisPath, calendarPath, @@ -121,7 +118,14 @@ export function mountIssuesListApp() { return new Vue({ el, name: 'IssuesListRoot', - apolloProvider, + apolloProvider: new VueApollo({ + defaultClient: createDefaultClient(resolvers), + }), + router: new VueRouter({ + base: window.location.pathname, + mode: 'history', + routes: [{ path: '/' }], + }), provide: { autocompleteAwardEmojisPath, calendarPath, diff --git a/app/assets/javascripts/issues/list/utils.js b/app/assets/javascripts/issues/list/utils.js index 4b77bd9bc5f..2aa74dd2ea9 100644 --- a/app/assets/javascripts/issues/list/utils.js +++ b/app/assets/javascripts/issues/list/utils.js @@ -1,3 +1,4 @@ +import { createTerm } from '@gitlab/ui/src/components/base/filtered_search/filtered_search_utils'; import { isPositiveInteger } from '~/lib/utils/number_utils'; import { __ } from '~/locale'; import { @@ -195,11 +196,12 @@ const convertToFilteredSearchTerms = (locationSearch) => export const getFilterTokens = (locationSearch) => { if (!locationSearch) { - return []; + return [createTerm()]; } const filterTokens = convertToFilteredTokens(locationSearch); const searchTokens = convertToFilteredSearchTerms(locationSearch); - return filterTokens.concat(searchTokens); + const tokens = filterTokens.concat(searchTokens); + return tokens.length ? tokens : [createTerm()]; }; const getFilterType = (data, tokenType = '') => diff --git a/app/assets/javascripts/projects/settings/topics/components/topics_token_selector.vue b/app/assets/javascripts/projects/settings/topics/components/topics_token_selector.vue index d4c97cbf038..9c8de9bef2d 100644 --- a/app/assets/javascripts/projects/settings/topics/components/topics_token_selector.vue +++ b/app/assets/javascripts/projects/settings/topics/components/topics_token_selector.vue @@ -85,7 +85,7 @@ export default { <gl-avatar-labeled :src="dropdownItem.avatarUrl" :entity-name="dropdownItem.name" - :label="dropdownItem.name" + :label="dropdownItem.title" :size="32" :shape="$options.AVATAR_SHAPE_OPTION_RECT" /> diff --git a/app/assets/javascripts/projects/settings/topics/queries/project_topics_search.query.graphql b/app/assets/javascripts/projects/settings/topics/queries/project_topics_search.query.graphql index b193165062a..0c0a874d950 100644 --- a/app/assets/javascripts/projects/settings/topics/queries/project_topics_search.query.graphql +++ b/app/assets/javascripts/projects/settings/topics/queries/project_topics_search.query.graphql @@ -3,6 +3,7 @@ query searchProjectTopics($search: String) { nodes { id name + title avatarUrl } } 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 6638a5de62f..230e1b009c8 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 @@ -89,32 +89,20 @@ export default { required: false, default: () => ({}), }, + syncFilterAndSort: { + type: Boolean, + required: false, + default: false, + }, }, data() { - let selectedSortOption = this.sortOptions[0]?.sortDirection?.descending; - let selectedSortDirection = SortDirection.descending; - - // Extract correct sortBy value based on initialSortBy - if (this.initialSortBy) { - selectedSortOption = this.sortOptions - .filter( - (sortBy) => - sortBy.sortDirection.ascending === this.initialSortBy || - sortBy.sortDirection.descending === this.initialSortBy, - ) - .pop(); - selectedSortDirection = Object.keys(selectedSortOption.sortDirection).find( - (key) => selectedSortOption.sortDirection[key] === this.initialSortBy, - ); - } - return { initialRender: true, recentSearchesPromise: null, recentSearches: [], filterValue: this.initialFilterValue, - selectedSortOption, - selectedSortDirection, + selectedSortOption: this.sortOptions[0], + selectedSortDirection: SortDirection.descending, }; }, computed: { @@ -173,7 +161,20 @@ export default { return undefined; }, }, + watch: { + initialFilterValue(newValue) { + if (this.syncFilterAndSort) { + this.filterValue = newValue; + } + }, + initialSortBy(newValue) { + if (this.syncFilterAndSort) { + this.updateSelectedSortValues(newValue); + } + }, + }, created() { + this.updateSelectedSortValues(this.initialSortBy); if (this.recentSearchesStorageKey) this.setupRecentSearch(); }, methods: { @@ -309,6 +310,19 @@ export default { const cleared = true; this.$emit('onFilter', [], cleared); }, + updateSelectedSortValues(sort) { + if (!sort) { + return; + } + + 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, + ); + }, }, }; </script> diff --git a/app/assets/javascripts/vue_shared/issuable/list/components/issuable_list_root.vue b/app/assets/javascripts/vue_shared/issuable/list/components/issuable_list_root.vue index 20f178dfb7d..8b293b2e9f6 100644 --- a/app/assets/javascripts/vue_shared/issuable/list/components/issuable_list_root.vue +++ b/app/assets/javascripts/vue_shared/issuable/list/components/issuable_list_root.vue @@ -168,6 +168,11 @@ export default { required: false, default: '', }, + syncFilterAndSort: { + type: Boolean, + required: false, + default: false, + }, }, data() { return { @@ -282,6 +287,7 @@ export default { :sort-options="sortOptions" :initial-filter-value="initialFilterValue" :initial-sort-by="initialSortBy" + :sync-filter-and-sort="syncFilterAndSort" :show-checkbox="showBulkEditSidebar" :checkbox-checked="allIssuablesChecked" class="gl-flex-grow-1 gl-border-t-none row-content-block" diff --git a/app/controllers/admin/background_migrations_controller.rb b/app/controllers/admin/background_migrations_controller.rb index 03a9b436453..16e53c8bd0c 100644 --- a/app/controllers/admin/background_migrations_controller.rb +++ b/app/controllers/admin/background_migrations_controller.rb @@ -4,6 +4,8 @@ class Admin::BackgroundMigrationsController < Admin::ApplicationController feature_category :database urgency :low + around_action :support_multiple_databases + def index @relations_by_tab = { 'queued' => batched_migration_class.queued.queue_order, @@ -14,6 +16,7 @@ class Admin::BackgroundMigrationsController < Admin::ApplicationController @current_tab = @relations_by_tab.key?(params[:tab]) ? params[:tab] : 'queued' @migrations = @relations_by_tab[@current_tab].page(params[:page]) @successful_rows_counts = batched_migration_class.successful_rows_counts(@migrations.map(&:id)) + @databases = Gitlab::Database.db_config_names end def show @@ -43,6 +46,18 @@ class Admin::BackgroundMigrationsController < Admin::ApplicationController private + def support_multiple_databases + Gitlab::Database::SharedModel.using_connection(base_model.connection) do + yield + end + end + + def base_model + database = params[:database] || Gitlab::Database::MAIN_DATABASE_NAME + + Gitlab::Database.database_base_models[database] + end + def batched_migration_class @batched_migration_class ||= Gitlab::Database::BackgroundMigration::BatchedMigration end diff --git a/app/graphql/types/projects/topic_type.rb b/app/graphql/types/projects/topic_type.rb index c579f2f2b9d..bde6d79ddbf 100644 --- a/app/graphql/types/projects/topic_type.rb +++ b/app/graphql/types/projects/topic_type.rb @@ -12,6 +12,10 @@ module Types field :name, GraphQL::Types::String, null: false, description: 'Name of the topic.' + field :title, GraphQL::Types::String, null: false, + method: :title_or_name, + description: 'Title of the topic.' + field :description, GraphQL::Types::String, null: true, description: 'Description of the topic.' diff --git a/app/models/projects/topic.rb b/app/models/projects/topic.rb index c21689a72d2..bc7f94e4374 100644 --- a/app/models/projects/topic.rb +++ b/app/models/projects/topic.rb @@ -23,13 +23,17 @@ module Projects reorder(order_expression.desc, arel_table['non_private_projects_count'].desc, arel_table['id']) end + def title_or_name + title || name + end + class << self def find_by_name_case_insensitive(name) find_by('LOWER(name) = ?', name.downcase) end def search(query) - fuzzy_search(query, [:name]) + fuzzy_search(query, [:name, :title]) end def update_non_private_projects_counter(ids_before, ids_after, project_visibility_level_before, project_visibility_level_after) diff --git a/app/presenters/project_presenter.rb b/app/presenters/project_presenter.rb index ffbe154c1a8..af1b254c46f 100644 --- a/app/presenters/project_presenter.rb +++ b/app/presenters/project_presenter.rb @@ -457,7 +457,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated def project_topic_list strong_memoize(:project_topic_list) do - project.topics.map(&:name) + project.topics.map { |topic| { name: topic.name, title: topic.title_or_name } } end end diff --git a/app/views/explore/projects/topic.html.haml b/app/views/explore/projects/topic.html.haml index aeb040ea61f..76e59a49ed1 100644 --- a/app/views/explore/projects/topic.html.haml +++ b/app/views/explore/projects/topic.html.haml @@ -1,7 +1,7 @@ - @hide_top_links = false - @no_container = true -- page_title @topic.name, _("Topics") -- max_topic_name_length = 50 +- page_title @topic.title_or_name, _("Topics") +- max_topic_title_length = 50 = render_dashboard_ultimate_trial(current_user) @@ -9,12 +9,12 @@ .gl-pb-5.gl-align-items-center.gl-justify-content-center.gl-display-flex .avatar-container.rect-avatar.s60.gl-flex-shrink-0 = topic_icon(@topic, alt: _('Topic avatar'), class: 'avatar topic-avatar s60') - - if @topic.name.length > max_topic_name_length - %h1.gl-mt-3.str-truncated.has-tooltip{ title: @topic.name } - = truncate(@topic.name, length: max_topic_name_length) + - if @topic.title_or_name.length > max_topic_title_length + %h1.gl-mt-3.gl-str-truncated.has-tooltip{ title: @topic.title_or_name } + = truncate(@topic.title_or_name, length: max_topic_title_length) - else %h1.gl-mt-3 - = @topic.name + = @topic.title_or_name - if @topic.description.present? .topic-description.gl-ml-4.gl-mr-4 = markdown(@topic.description) diff --git a/app/views/shared/projects/_topics.html.haml b/app/views/shared/projects/_topics.html.haml index b7df369327c..e3895663033 100644 --- a/app/views/shared/projects/_topics.html.haml +++ b/app/views/shared/projects/_topics.html.haml @@ -7,25 +7,25 @@ = sprite_icon('tag', css_class: 'icon gl-relative gl-mr-2') - project.topics_to_show.each do |topic| - - explore_project_topic_path = topic_explore_projects_path(topic_name: topic) - - if topic.length > max_project_topic_length - %a.gl-mr-3.has-tooltip{ data: { container: "body" }, title: topic, href: explore_project_topic_path, itemprop: 'keywords' } - = gl_badge_tag truncate(topic, length: max_project_topic_length) + - explore_project_topic_path = topic_explore_projects_path(topic_name: topic[:name]) + - if topic[:title].length > max_project_topic_length + %a.gl-mr-3.has-tooltip{ data: { container: "body" }, title: topic[:title], href: explore_project_topic_path, itemprop: 'keywords' } + = gl_badge_tag truncate(topic[:title], length: max_project_topic_length) - else %a.gl-mr-3{ href: explore_project_topic_path, itemprop: 'keywords' } - = gl_badge_tag topic + = gl_badge_tag topic[:title] - if project.has_extra_topics? - title = _('More topics') - content = capture do %span.gl-display-inline-flex.gl-flex-wrap - project.topics_not_shown.each do |topic| - - explore_project_topic_path = topic_explore_projects_path(topic_name: topic) - - if topic.length > max_project_topic_length - %a.gl-mr-3.gl-mb-3.has-tooltip{ data: { container: "body" }, title: topic, href: explore_project_topic_path, itemprop: 'keywords' } - = gl_badge_tag truncate(topic, length: max_project_topic_length) + - explore_project_topic_path = topic_explore_projects_path(topic_name: topic[:name]) + - if topic[:title].length > max_project_topic_length + %a.gl-mr-3.gl-mb-3.has-tooltip{ data: { container: "body" }, title: topic[:title], href: explore_project_topic_path, itemprop: 'keywords' } + = gl_badge_tag truncate(topic[:title], length: max_project_topic_length) - else %a.gl-mr-3.gl-mb-3{ href: explore_project_topic_path, itemprop: 'keywords' } - = gl_badge_tag topic + = gl_badge_tag topic[:title] .text-nowrap{ role: 'button', tabindex: 0, data: { toggle: 'popover', html: 'true', placement: 'top', title: title, content: content } } = _("+ %{count} more") % { count: project.count_of_extra_topics_not_shown } diff --git a/app/views/shared/topics/_topic.html.haml b/app/views/shared/topics/_topic.html.haml index a47d4495777..ca1098511da 100644 --- a/app/views/shared/topics/_topic.html.haml +++ b/app/views/shared/topics/_topic.html.haml @@ -1,4 +1,4 @@ -- max_topic_name_length = 30 +- max_topic_title_length = 30 - detail_page_link = topic_explore_projects_path(topic_name: topic.name) .col-lg-3.col-md-4.col-sm-12 @@ -8,9 +8,9 @@ = link_to detail_page_link do = topic_icon(topic, class: "avatar s40") = link_to detail_page_link do - - if topic.name.length > max_topic_name_length - %h5.str-truncated.has-tooltip{ title: topic.name } - = truncate(topic.name, length: max_topic_name_length) + - if topic.title_or_name.length > max_topic_title_length + %h5.gl-str-truncated.has-tooltip{ title: topic.title_or_name } + = truncate(topic.title_or_name, length: max_topic_title_length) - else %h5 - = topic.name + = topic.title_or_name |