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
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-04-29 12:09:48 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-04-29 12:09:48 +0300
commit7510fe06eba02c3cee247f8ceb4ee6f6a4de54f6 (patch)
tree95025711e64d000172e47209bfdc1d7cd7d6b972 /app
parent401607eed7e0918553e985e1f12e99dc1ff9e948 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/issues/list/components/issues_list_app.vue91
-rw-r--r--app/assets/javascripts/issues/list/constants.js3
-rw-r--r--app/assets/javascripts/issues/list/index.js16
-rw-r--r--app/assets/javascripts/issues/list/utils.js6
-rw-r--r--app/assets/javascripts/projects/settings/topics/components/topics_token_selector.vue2
-rw-r--r--app/assets/javascripts/projects/settings/topics/queries/project_topics_search.query.graphql1
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue52
-rw-r--r--app/assets/javascripts/vue_shared/issuable/list/components/issuable_list_root.vue6
-rw-r--r--app/controllers/admin/background_migrations_controller.rb15
-rw-r--r--app/graphql/types/projects/topic_type.rb4
-rw-r--r--app/models/projects/topic.rb6
-rw-r--r--app/presenters/project_presenter.rb2
-rw-r--r--app/views/explore/projects/topic.html.haml12
-rw-r--r--app/views/shared/projects/_topics.html.haml20
-rw-r--r--app/views/shared/topics/_topic.html.haml10
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