Commit 3410b89c authored by Vitaly Slobodin's avatar Vitaly Slobodin

Merge branch 'project-list-topics' into 'master'

Show topics in the project list

See merge request gitlab-org/gitlab!65335
parents 6b0f3424 f9cb6e70
...@@ -26,9 +26,10 @@ class Admin::UsersController < Admin::ApplicationController ...@@ -26,9 +26,10 @@ class Admin::UsersController < Admin::ApplicationController
def show def show
end end
# rubocop: disable CodeReuse/ActiveRecord
def projects def projects
@personal_projects = user.personal_projects @personal_projects = user.personal_projects.includes(:topics)
@joined_projects = user.projects.joined(@user) @joined_projects = user.projects.joined(@user).includes(:topics)
end end
def keys def keys
......
...@@ -81,7 +81,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController ...@@ -81,7 +81,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def preload_associations(projects) def preload_associations(projects)
projects.includes(:route, :creator, :group, namespace: [:route, :owner]).preload(:project_feature) projects.includes(:route, :creator, :group, :topics, namespace: [:route, :owner]).preload(:project_feature)
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
......
...@@ -87,7 +87,7 @@ class Explore::ProjectsController < Explore::ApplicationController ...@@ -87,7 +87,7 @@ class Explore::ProjectsController < Explore::ApplicationController
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def preload_associations(projects) def preload_associations(projects)
projects.includes(:route, :creator, :group, :project_feature, namespace: [:route, :owner]) projects.includes(:route, :creator, :group, :project_feature, :topics, namespace: [:route, :owner])
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
......
...@@ -99,7 +99,7 @@ class Projects::ForksController < Projects::ApplicationController ...@@ -99,7 +99,7 @@ class Projects::ForksController < Projects::ApplicationController
current_user: current_user current_user: current_user
).execute ).execute
forks.includes(:route, :creator, :group, namespace: [:route, :owner]) forks.includes(:route, :creator, :group, :topics, namespace: [:route, :owner])
end end
def fork_service def fork_service
......
...@@ -397,16 +397,16 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated ...@@ -397,16 +397,16 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
end end
def topics_to_show def topics_to_show
project.topic_list.take(MAX_TOPICS_TO_SHOW) # rubocop: disable CodeReuse/ActiveRecord project_topic_list.take(MAX_TOPICS_TO_SHOW) # rubocop: disable CodeReuse/ActiveRecord
end end
def topics_not_shown def topics_not_shown
project.topic_list - topics_to_show project_topic_list - topics_to_show
end end
def count_of_extra_topics_not_shown def count_of_extra_topics_not_shown
if project.topic_list.count > MAX_TOPICS_TO_SHOW if project_topic_list.count > MAX_TOPICS_TO_SHOW
project.topic_list.count - MAX_TOPICS_TO_SHOW project_topic_list.count - MAX_TOPICS_TO_SHOW
else else
0 0
end end
...@@ -486,6 +486,12 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated ...@@ -486,6 +486,12 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
**additional_params **additional_params
) )
end end
def project_topic_list
strong_memoize(:project_topic_list) do
project.topics.map(&:name)
end
end
end end
ProjectPresenter.prepend_mod_with('ProjectPresenter') ProjectPresenter.prepend_mod_with('ProjectPresenter')
...@@ -22,8 +22,9 @@ module Search ...@@ -22,8 +22,9 @@ module Search
filters: { state: params[:state], confidential: params[:confidential] }) filters: { state: params[:state], confidential: params[:confidential] })
end end
# rubocop: disable CodeReuse/ActiveRecord
def projects def projects
@projects ||= ProjectsFinder.new(params: { non_archived: true }, current_user: current_user).execute @projects ||= ProjectsFinder.new(params: { non_archived: true }, current_user: current_user).execute.includes(:topics, :taggings)
end end
def allowed_scopes def allowed_scopes
......
- empty_repo = @project.empty_repo? - empty_repo = @project.empty_repo?
- show_auto_devops_callout = show_auto_devops_callout?(@project) - show_auto_devops_callout = show_auto_devops_callout?(@project)
- max_project_topic_length = 15
- emails_disabled = @project.emails_disabled? - emails_disabled = @project.emails_disabled?
- cache_enabled = Feature.enabled?(:cache_home_panel, @project, type: :development, default_enabled: :yaml) - cache_enabled = Feature.enabled?(:cache_home_panel, @project, type: :development, default_enabled: :yaml)
...@@ -25,24 +24,8 @@ ...@@ -25,24 +24,8 @@
%span.access-request-links.gl-ml-3 %span.access-request-links.gl-ml-3
= render 'shared/members/access_request_links', source: @project = render 'shared/members/access_request_links', source: @project
- if @project.topic_list.present? .gl-mt-3.gl-pl-3.gl-w-full
= cache_if(cache_enabled, [@project, :topic_list], expires_in: 1.day) do = render "shared/projects/topics", project: @project, cache_enabled: cache_enabled
%span.home-panel-topic-list.mt-2.w-100.d-inline-flex.gl-font-base.gl-font-weight-normal.gl-align-items-center
= sprite_icon('tag', css_class: 'icon gl-relative gl-mr-2')
- @project.topics_to_show.each do |topic|
- project_topics_classes = "badge badge-pill badge-secondary gl-mr-2"
- explore_project_topic_path = explore_projects_path(topic: topic)
- if topic.length > max_project_topic_length
%a{ class: "#{ project_topics_classes } str-truncated-30 has-tooltip", data: { container: "body" }, title: topic, href: explore_project_topic_path, itemprop: 'keywords' }
= topic.titleize
- else
%a{ class: project_topics_classes, href: explore_project_topic_path, itemprop: 'keywords' }
= topic.titleize
- if @project.has_extra_topics?
.text-nowrap.has-tooltip{ data: { container: 'body' }, title: @project.has_extra_topics? ? @project.topics_not_shown.join(', ') : nil }
= _("+ %{count} more") % { count: @project.count_of_extra_topics_not_shown }
= cache_if(cache_enabled, [@project, :buttons, current_user, @notification_setting], expires_in: 1.day) do = cache_if(cache_enabled, [@project, :buttons, current_user, @notification_setting], expires_in: 1.day) do
.project-repo-buttons.gl-display-flex.gl-justify-content-md-end.gl-align-items-start.gl-flex-wrap.gl-mt-5 .project-repo-buttons.gl-display-flex.gl-justify-content-md-end.gl-align-items-start.gl-flex-wrap.gl-mt-5
......
...@@ -65,6 +65,10 @@ ...@@ -65,6 +65,10 @@
.description.d-none.d-sm-block.gl-mr-3 .description.d-none.d-sm-block.gl-mr-3
= markdown_field(project, :description) = markdown_field(project, :description)
- if project.topics.any?
.gl-mt-2
= render "shared/projects/topics", project: project.present(current_user: current_user)
= render_if_exists 'shared/projects/removed', project: project = render_if_exists 'shared/projects/removed', project: project
.controls.d-flex.flex-sm-column.align-items-center.align-items-sm-end.flex-wrap.flex-shrink-0.text-secondary{ class: css_controls_class.join(" ") } .controls.d-flex.flex-sm-column.align-items-center.align-items-sm-end.flex-wrap.flex-shrink-0.text-secondary{ class: css_controls_class.join(" ") }
......
- cache_enabled = false unless local_assigns[:cache_enabled] == true
- max_project_topic_length = 15
- project_topics_classes = "badge badge-pill badge-secondary gl-mr-2"
- if project.topics.present?
= cache_if(cache_enabled, [project, :topic_list], expires_in: 1.day) do
%span.gl-w-full.gl-display-inline-flex.gl-font-base.gl-font-weight-normal.gl-align-items-center{ 'data-testid': 'project_topic_list' }
= sprite_icon('tag', css_class: 'icon gl-relative gl-mr-2')
- project.topics_to_show.each do |topic|
- explore_project_topic_path = explore_projects_path(topic: topic)
- if topic.length > max_project_topic_length
%a{ class: "#{ project_topics_classes } str-truncated-30 has-tooltip", data: { container: "body" }, title: topic, href: explore_project_topic_path, itemprop: 'keywords' }
= truncate(topic, length: max_project_topic_length)
- else
%a{ class: project_topics_classes, href: explore_project_topic_path, itemprop: 'keywords' }
= topic
- if project.has_extra_topics?
- title = _('More topics')
- content = capture do
%span.gl-display-inline-flex
- project.topics_not_shown.each do |topic|
- explore_project_topic_path = explore_projects_path(topic: topic)
- if topic.length > max_project_topic_length
%a{ class: "#{ project_topics_classes } gl-mb-3 str-truncated has-tooltip", data: { container: "body" }, title: topic, href: explore_project_topic_path, itemprop: 'keywords' }
= truncate(topic, length: max_project_topic_length)
- else
%a{ class: "#{ project_topics_classes } gl-mb-3", href: explore_project_topic_path, itemprop: 'keywords' }
= topic
.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 }
...@@ -28,7 +28,7 @@ module Gitlab ...@@ -28,7 +28,7 @@ module Gitlab
case scope case scope
when 'projects' when 'projects'
eager_load(projects, page, per_page, preload_method, [:route, :namespace]) eager_load(projects, page, per_page, preload_method, [:route, :namespace, :topics])
when 'issues' when 'issues'
eager_load(issues, page, per_page, preload_method, project: [:route, :namespace], labels: [], timelogs: [], assignees: []) eager_load(issues, page, per_page, preload_method, project: [:route, :namespace], labels: [], timelogs: [], assignees: [])
when 'merge_requests' when 'merge_requests'
......
...@@ -21619,6 +21619,9 @@ msgstr "" ...@@ -21619,6 +21619,9 @@ msgstr ""
msgid "More than %{number_commits_distance} commits different with %{default_branch}" msgid "More than %{number_commits_distance} commits different with %{default_branch}"
msgstr "" msgstr ""
msgid "More topics"
msgstr ""
msgid "Most relevant" msgid "Most relevant"
msgstr "" msgstr ""
......
...@@ -194,6 +194,29 @@ RSpec.describe 'Dashboard Projects' do ...@@ -194,6 +194,29 @@ RSpec.describe 'Dashboard Projects' do
end end
end end
describe 'with topics' do
context 'when project has topics' do
before do
project.update_attribute(:topic_list, 'topic1')
end
it 'shows project topics if exist' do
visit dashboard_projects_path
expect(page).to have_selector('[data-testid="project_topic_list"]')
expect(page).to have_link('topic1', href: explore_projects_path(topic: 'topic1'))
end
end
context 'when project does not have topics' do
it 'does not show project topics' do
visit dashboard_projects_path
expect(page).not_to have_selector('[data-testid="project_topic_list"]')
end
end
end
context 'last push widget', :use_clean_rails_memory_store_caching do context 'last push widget', :use_clean_rails_memory_store_caching do
before do before do
event = create(:push_event, project: project, author: user) event = create(:push_event, project: project, author: user)
......
...@@ -16,7 +16,7 @@ RSpec.describe 'Projects > Show > Schema Markup' do ...@@ -16,7 +16,7 @@ RSpec.describe 'Projects > Show > Schema Markup' do
expect(page).to have_selector('[itemprop="identifier"]', text: "Project ID: #{project.id}") expect(page).to have_selector('[itemprop="identifier"]', text: "Project ID: #{project.id}")
expect(page).to have_selector('[itemprop="description"]', text: project.description) expect(page).to have_selector('[itemprop="description"]', text: project.description)
expect(page).to have_selector('[itemprop="license"]', text: project.repository.license.name) expect(page).to have_selector('[itemprop="license"]', text: project.repository.license.name)
expect(find_all('[itemprop="keywords"]').map(&:text)).to match_array(project.topic_list.map(&:capitalize)) expect(find_all('[itemprop="keywords"]').map(&:text)).to match_array(project.topic_list)
expect(page).to have_selector('[itemprop="about"]') expect(page).to have_selector('[itemprop="about"]')
end end
end end
......
...@@ -132,8 +132,8 @@ RSpec.describe 'Project' do ...@@ -132,8 +132,8 @@ RSpec.describe 'Project' do
visit path visit path
expect(page).to have_css('.home-panel-topic-list') expect(page).to have_selector('[data-testid="project_topic_list"]')
expect(page).to have_link('Topic1', href: explore_projects_path(topic: 'topic1')) expect(page).to have_link('topic1', href: explore_projects_path(topic: 'topic1'))
end end
it 'shows up to 3 project topics' do it 'shows up to 3 project topics' do
...@@ -141,10 +141,10 @@ RSpec.describe 'Project' do ...@@ -141,10 +141,10 @@ RSpec.describe 'Project' do
visit path visit path
expect(page).to have_css('.home-panel-topic-list') expect(page).to have_selector('[data-testid="project_topic_list"]')
expect(page).to have_link('Topic1', href: explore_projects_path(topic: 'topic1')) expect(page).to have_link('topic1', href: explore_projects_path(topic: 'topic1'))
expect(page).to have_link('Topic2', href: explore_projects_path(topic: 'topic2')) expect(page).to have_link('topic2', href: explore_projects_path(topic: 'topic2'))
expect(page).to have_link('Topic3', href: explore_projects_path(topic: 'topic3')) expect(page).to have_link('topic3', href: explore_projects_path(topic: 'topic3'))
expect(page).to have_content('+ 1 more') expect(page).to have_content('+ 1 more')
end end
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment