Commit 8a0c21b4 authored by Jonas Waelter's avatar Jonas Waelter Committed by Jonas Wälter

Move 'Explore topics' page into 'Projects' area

parent 745b7d7c
......@@ -69,12 +69,6 @@ export const GO_TO_YOUR_SNIPPETS = {
defaultKeys: ['shift+s'],
};
export const GO_TO_YOUR_TOPICS = {
id: 'globalShortcuts.goToYourTopics',
description: __('Go to your topics'),
defaultKeys: ['shift+o'],
};
export const START_SEARCH = {
id: 'globalShortcuts.startSearch',
description: __('Start search'),
......@@ -504,7 +498,6 @@ export const GLOBAL_SHORTCUTS_GROUP = {
GO_TO_ACTIVITY_FEED,
GO_TO_MILESTONE_LIST,
GO_TO_YOUR_SNIPPETS,
GO_TO_YOUR_TOPICS,
START_SEARCH,
FOCUS_FILTER_BAR,
GO_TO_YOUR_ISSUES,
......
......@@ -23,7 +23,6 @@ import {
GO_TO_YOUR_GROUPS,
GO_TO_MILESTONE_LIST,
GO_TO_YOUR_SNIPPETS,
GO_TO_YOUR_TOPICS,
GO_TO_PROJECT_FIND_FILE,
} from './keybindings';
import { disableShortcuts, shouldDisableShortcuts } from './shortcuts_toggle';
......@@ -107,9 +106,6 @@ export default class Shortcuts {
Mousetrap.bind(keysFor(GO_TO_YOUR_SNIPPETS), () =>
findAndFollowLink('.dashboard-shortcuts-snippets'),
);
Mousetrap.bind(keysFor(GO_TO_YOUR_TOPICS), () =>
findAndFollowLink('.dashboard-shortcuts-topics'),
);
Mousetrap.bind(keysFor(TOGGLE_MARKDOWN_PREVIEW), Shortcuts.toggleMarkdownPreview);
......
# frozen_string_literal: true
class Dashboard::TopicsController < Dashboard::ApplicationController
feature_category :projects
def index
@topics = Projects::TopicsFinder.new(current_user: current_user, params: finder_params).execute.page(params[:page])
end
def sort
@sort ||= params[:sort] || 'popularity_desc'
end
private
def finder_params
params.permit(:name).merge(sort: sort, all_available: false)
end
end
......@@ -12,7 +12,8 @@ class Explore::ProjectsController < Explore::ApplicationController
PAGE_LIMIT = 50
before_action :set_non_archived_param
before_action :set_sorting
before_action :set_sorting, except: [:topics]
before_action :set_topics_sorting, only: [:topics]
# For background information on the limit, see:
# https://gitlab.com/gitlab-org/gitlab/-/issues/38357
......@@ -68,6 +69,11 @@ class Explore::ProjectsController < Explore::ApplicationController
end
# rubocop: enable CodeReuse/ActiveRecord
def topics
load_project_counts
load_topics
end
private
def load_project_counts
......@@ -86,6 +92,10 @@ class Explore::ProjectsController < Explore::ApplicationController
prepare_projects_for_rendering(projects)
end
def load_topics
@topics = Projects::TopicsFinder.new(current_user: current_user, params: params.permit(:search, :personal, :sort)).execute.page(params[:page])
end
# rubocop: disable CodeReuse/ActiveRecord
def preload_associations(projects)
projects.includes(:route, :creator, :group, :project_feature, :topics, namespace: [:route, :owner])
......@@ -105,6 +115,10 @@ class Explore::ProjectsController < Explore::ApplicationController
Project::SORTING_PREFERENCE_FIELD
end
def set_topics_sorting
@sort = params[:sort] ||= sort_value_most_popular
end
def page_out_of_bounds(error)
load_project_counts
@max_page_number = error.message
......
# frozen_string_literal: true
class Explore::TopicsController < Explore::ApplicationController
feature_category :projects
def index
@topics = Projects::TopicsFinder.new(current_user: current_user, params: finder_params).execute.page(params[:page])
end
def sort
@sort ||= params[:sort] || 'popularity_desc'
end
private
def finder_params
params.permit(:name).merge(sort: sort, all_available: true)
end
end
......@@ -7,8 +7,8 @@
# Arguments:
# current_user - which user is requesting groups
# params:
# all_available: boolean (defaults to true)
# name: string
# personal: boolean (defaults to false)
# search: string
# sort: string
module Projects
class TopicsFinder
......@@ -26,15 +26,15 @@ module Projects
private
def projects_relation
if current_user.nil? || all_available?
Project.public_or_visible_to_user(current_user)
else
if current_user && personal?
current_user.authorized_projects
else
Project.public_or_visible_to_user(current_user)
end
end
def all_available?
params.fetch(:all_available, true)
def personal?
params.fetch(:personal, false)
end
def options
......@@ -45,7 +45,7 @@ module Projects
end
def filter_by_name
ActsAsTaggableOn::Tag.arel_table[:name].matches("%#{params[:name]}%") if params[:name].present?
ActsAsTaggableOn::Tag.arel_table[:name].matches("%#{params[:search]}%") if params[:search].present?
end
def sort_by_attribute
......
......@@ -52,7 +52,7 @@ module DashboardHelper
private
def get_dashboard_nav_links
links = [:projects, :groups, :snippets, :topics]
links = [:projects, :groups, :snippets]
if can?(current_user, :read_cross_project)
links += [:activity, :milestones]
......
......@@ -35,6 +35,17 @@ module ExploreHelper
request_path_with_options(options)
end
def filter_topics_path(options = {})
exist_opts = {
sort: params[:sort] || @sort,
search: params[:search],
personal: params[:personal]
}
options = exist_opts.merge(options).delete_if { |key, value| value.blank? }
request_path_with_options(options)
end
def explore_controller?
controller.class.name.split("::").first == "Explore"
end
......@@ -58,7 +69,7 @@ module ExploreHelper
private
def get_explore_nav_links
[:projects, :groups, :snippets, :topics]
[:projects, :groups, :snippets]
end
def request_path_with_options(options = {})
......
......@@ -82,14 +82,6 @@ module Nav
)
end
if explore_nav_link?(:topics)
builder.add_primary_menu_item_with_shortcut(
active: active_nav_link?(controller: :topics),
href: explore_topics_path,
**topics_menu_item_attrs
)
end
builder.add_secondary_menu_item(
id: 'help',
title: _('Help'),
......@@ -149,15 +141,6 @@ module Nav
)
end
if dashboard_nav_link?(:topics)
builder.add_primary_menu_item_with_shortcut(
active: active_nav_link?(controller: 'dashboard/topics'),
data: { qa_selector: 'topics_link' },
href: dashboard_topics_path,
**topics_menu_item_attrs
)
end
if dashboard_nav_link?(:activity)
builder.add_primary_menu_item_with_shortcut(
id: 'activity',
......@@ -244,15 +227,6 @@ module Nav
}
end
def topics_menu_item_attrs
{
id: 'topics',
title: _('Topics'),
icon: 'label',
shortcut_class: 'dashboard-shortcuts-topics'
}
end
def container_view_props(namespace:, current_item:, submenu:)
{
namespace: namespace,
......
......@@ -14,19 +14,7 @@
.top-area.scrolling-tabs-container.inner-page-scroll-tabs
.fade-left= sprite_icon('chevron-lg-left', size: 12)
.fade-right= sprite_icon('chevron-lg-right', size: 12)
%ul.nav-links.scrolling-tabs.mobile-separator.nav.nav-tabs{ class: ('border-0' if feature_project_list_filter_bar) }
= nav_link(page: [dashboard_projects_path, root_path]) do
= link_to dashboard_projects_path, class: 'shortcuts-activity', data: {placement: 'right'} do
= _("Your projects")
%span.badge.gl-tab-counter-badge.badge-muted.badge-pill.gl-badge.sm= limited_counter_with_delimiter(@total_user_projects_count)
= nav_link(page: starred_dashboard_projects_path) do
= link_to starred_dashboard_projects_path, data: {placement: 'right'} do
= _("Starred projects")
%span.badge.gl-tab-counter-badge.badge-muted.badge-pill.gl-badge.sm= limited_counter_with_delimiter(@total_starred_projects_count)
= nav_link(page: [explore_root_path, trending_explore_projects_path, starred_explore_projects_path, explore_projects_path]) do
= link_to explore_root_path, data: {placement: 'right'} do
= _("Explore projects")
= render_if_exists "dashboard/removed_projects_tab", removed_projects_count: @removed_projects_count
= render 'dashboard/projects_nav'
- unless feature_project_list_filter_bar
.nav-controls
= render 'shared/projects/search_form'
......
- feature_project_list_filter_bar = Feature.enabled?(:project_list_filter_bar)
%ul.nav-links.scrolling-tabs.mobile-separator.nav.nav-tabs{ class: ('border-0' if feature_project_list_filter_bar) }
= nav_link(page: [dashboard_projects_path, root_path]) do
= link_to dashboard_projects_path, class: 'shortcuts-activity', data: {placement: 'right'} do
= _("Your projects")
%span.badge.gl-tab-counter-badge.badge-muted.badge-pill.gl-badge.sm= limited_counter_with_delimiter(@total_user_projects_count)
= nav_link(page: starred_dashboard_projects_path) do
= link_to starred_dashboard_projects_path, data: {placement: 'right'} do
= _("Starred projects")
%span.badge.gl-tab-counter-badge.badge-muted.badge-pill.gl-badge.sm= limited_counter_with_delimiter(@total_starred_projects_count)
= nav_link(page: [explore_root_path, trending_explore_projects_path, starred_explore_projects_path, explore_projects_path]) do
= link_to explore_root_path, data: {placement: 'right'} do
= _("Explore projects")
= nav_link(page: topics_explore_projects_path) do
= link_to topics_explore_projects_path, data: {placement: 'right'} do
= _("Explore topics")
= render_if_exists "dashboard/removed_projects_tab", removed_projects_count: @removed_projects_count
.page-title-holder.d-flex.align-items-center
%h1.page-title= _('Topics')
.top-area
%ul.nav-links.mobile-separator.nav.nav-tabs
= nav_link(page: dashboard_topics_path) do
= link_to dashboard_topics_path, title: _("Your topics") do
Your topics
= nav_link(page: explore_topics_path) do
= link_to explore_topics_path, title: _("Explore topics") do
Explore topics
.nav-controls
= render 'shared/topics/search_form'
= render 'shared/topics/dropdown'
- @hide_top_links = true
- page_title _("Topics")
- header_title _("Topics"), dashboard_topics_path
= render 'dashboard/topics_head'
- if @topics.empty?
= render 'shared/empty_states/topics'
- else
= render partial: 'shared/topics/list'
......@@ -2,5 +2,5 @@
%h2
= _("Explore GitLab")
%p.lead
= _("Discover projects, groups, snippets and topics. Share your projects with others")
= _("Discover projects, groups and snippets. Share your projects with others")
%br
- @hide_top_links = true
- page_title _("Topics")
- header_title _("Topics"), explore_topics_path
- header_title _("Topics"), topics_explore_projects_path
= render_dashboard_ultimate_trial(current_user)
- if current_user
= render 'dashboard/topics_head'
= render 'explore/topics/head'
= render 'explore/topics/nav'
- else
= render 'explore/head'
- if @topics.empty?
= render 'shared/empty_states/topics'
- else
= render partial: 'shared/topics/list'
= render partial: 'shared/topics/list'
.page-title-holder.d-flex.align-items-center
%h1.page-title= _('Projects')
.top-area.scrolling-tabs-container.inner-page-scroll-tabs
.fade-left= sprite_icon('chevron-lg-left', size: 12)
.fade-right= sprite_icon('chevron-lg-right', size: 12)
= render 'dashboard/projects_nav'
.nav-controls
= render 'shared/topics/search_form'
= render 'shared/topics/dropdown'
.top-area
%ul.nav-links.nav.nav-tabs
= nav_link(html_options: { class: ("active" unless params[:personal].present?) }) do
= link_to filter_topics_path(personal: nil) do
= _("All")
= nav_link(html_options: { class: ("active" if params[:personal].present?) }) do
= link_to filter_topics_path(personal: true) do
= _("Personal")
- remote = local_assigns.fetch(:remote, false)
- if @topics.to_a.empty?
.nothing-here-block= s_("TopicsEmptyState|No topics found")
- if @topics.empty?
= render 'shared/empty_states/topics'
- else
.row.gl-mt-3
= render partial: 'shared/topics/topic', collection: @topics
......
= form_tag request.path, method: :get, class: "topic-filter-form js-topic-filter-form", id: 'topic-filter-form' do |f|
= search_field_tag :name, params[:name], placeholder: s_('Topics|Search by name'), class: 'topic-filter-form-field form-control js-topics-list-filter qa-topics-filter', spellcheck: false, id: 'topic-filter-form-field'
= form_tag filter_topics_path, method: :get, class: "topic-filter-form js-topic-filter-form", id: 'topic-filter-form' do |f|
= search_field_tag :search, params[:search], placeholder: s_('Topics|Search by name'), class: 'topic-filter-form-field form-control js-topics-list-filter qa-topics-filter', spellcheck: false, id: 'topic-filter-form-field'
- if params[:sort].present?
= hidden_field_tag :sort, params[:sort]
- if params[:personal].present?
= hidden_field_tag :personal, params[:personal]
......@@ -12,7 +12,6 @@ resource :dashboard, controller: 'dashboard', only: [] do
resources :groups, only: [:index]
resources :snippets, only: [:index]
resources :topics, only: [:index]
resources :todos, only: [:index, :destroy] do
collection do
......
......@@ -5,12 +5,12 @@ namespace :explore do
collection do
get :trending
get :starred
get :topics
end
end
resources :groups, only: [:index]
resources :snippets, only: [:index]
resources :topics, only: [:index]
root to: 'projects#index'
end
......
......@@ -11983,7 +11983,7 @@ msgstr ""
msgid "Discover GitLab Geo"
msgstr ""
msgid "Discover projects, groups, snippets and topics. Share your projects with others"
msgid "Discover projects, groups and snippets. Share your projects with others"
msgstr ""
msgid "Discover|Check your application for security vulnerabilities that may lead to unauthorized access, data leaks, and denial of services."
......@@ -15908,9 +15908,6 @@ msgstr ""
msgid "Go to your snippets"
msgstr ""
msgid "Go to your topics"
msgstr ""
msgid "Goal of the changes and what reviewers should be aware of"
msgstr ""
......@@ -24812,6 +24809,9 @@ msgstr ""
msgid "Permissions, LFS, 2FA"
msgstr ""
msgid "Personal"
msgstr ""
msgid "Personal Access Token"
msgstr ""
......@@ -35824,9 +35824,6 @@ msgstr ""
msgid "Topics (optional)"
msgstr ""
msgid "TopicsEmptyState|No topics found"
msgstr ""
msgid "TopicsEmptyState|There are no topics to show."
msgstr ""
......
......@@ -41,10 +41,6 @@ RSpec.describe 'Dashboard shortcuts', :js do
find('body').send_keys([:shift, 'L'])
check_page_title('Milestones')
find('body').send_keys([:shift, 'O'])
check_page_title('Topics')
end
end
......@@ -68,16 +64,6 @@ RSpec.describe 'Dashboard shortcuts', :js do
find('.nothing-here-block')
expect(page).to have_content('Explore public groups to find projects to contribute to.')
find('body').send_keys([:shift, 'O'])
find('.empty-state')
expect(page).to have_content('There are no topics to show')
end
end
find('.nothing-here-block')
expect(page).to have_content('Explore public groups to find projects to contribute to.')
end
end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Dashboard > Topics' do
describe 'as anonymous user' do
before do
visit dashboard_topics_path
end
it 'is redirected to sign-in page' do
expect(current_path).to eq new_user_session_path
end
end
describe 'as logged-in user' do
let(:user) { create(:user) }
let(:user_project) { create(:project, namespace: user.namespace) }
let(:other_project) { create(:project, :public) }
before do
sign_in(user)
end
context 'when topics exist' do
before do
user_project.update!(topic_list: 'topic1')
other_project.update!(topic_list: 'topic2')
end
it 'renders correct topics' do
visit dashboard_topics_path
expect(current_path).to eq dashboard_topics_path
expect(page).to have_content('topic1')
expect(page).not_to have_content('topic2')
end
end
context 'when no topics exist' do
it 'renders empty message' do
visit dashboard_topics_path
expect(current_path).to eq dashboard_topics_path
expect(page).to have_content('There are no topics to show')
end
end
end
end
......@@ -11,9 +11,9 @@ RSpec.describe 'Explore Topics' do
context 'when no topics exist' do
it 'renders empty message' do
visit explore_topics_path
visit topics_explore_projects_path
expect(current_path).to eq explore_topics_path
expect(current_path).to eq topics_explore_projects_path
expect(page).to have_content('There are no topics to show')
end
end
......@@ -31,22 +31,32 @@ RSpec.describe 'Explore Topics' do
sign_in(user)
end
it 'renders correct topics' do
visit explore_topics_path
it 'renders all topics correcty' do
visit topics_explore_projects_path
expect(current_path).to eq explore_topics_path
expect(current_path).to eq topics_explore_projects_path
expect(page).to have_content('topic1')
expect(page).not_to have_content('topic2')
expect(page).to have_content('topic3')
expect(page).to have_content('topic4')
end
it 'renders personal topics correcty' do
visit topics_explore_projects_path(personal: true)
expect(current_path).to eq topics_explore_projects_path
expect(page).to have_content('topic1')
expect(page).not_to have_content('topic2')
expect(page).not_to have_content('topic3')
expect(page).not_to have_content('topic4')
end
end
context 'as anonymous user' do
it 'renders correct topics' do
visit explore_topics_path
it 'renders all topics correcty' do
visit topics_explore_projects_path
expect(current_path).to eq explore_topics_path
expect(current_path).to eq topics_explore_projects_path
expect(page).not_to have_content('topic1')
expect(page).not_to have_content('topic2')
expect(page).not_to have_content('topic3')
......
# frozen_string_literal: true
# TODO: replace 'tag_list' by 'topic_list' as soon as the following MR is merged:
# https://gitlab.com/gitlab-org/gitlab/-/merge_requests/60834
require 'spec_helper'
RSpec.describe Projects::TopicsFinder do
......@@ -29,10 +26,10 @@ RSpec.describe Projects::TopicsFinder do
context 'count' do
before do
user_public_project.update!(tag_list: 'aaa, bbb, ccc, ddd')
other_user_public_project.update!(tag_list: 'bbb, ccc, ddd')
group_public_project.update!(tag_list: 'ccc, ddd')
other_group_public_project.update!(tag_list: 'ddd')
user_public_project.update!(topic_list: 'aaa, bbb, ccc, ddd')
other_user_public_project.update!(topic_list: 'bbb, ccc, ddd')
group_public_project.update!(topic_list: 'ccc, ddd')
other_group_public_project.update!(topic_list: 'ddd')
end
it 'returns topics with correct count' do
......@@ -44,23 +41,23 @@ RSpec.describe Projects::TopicsFinder do
context 'filter projects' do
before do
user_private_project.update!(tag_list: 'topic1')
user_public_project.update!(tag_list: 'topic2')
other_user_private_project.update!(tag_list: 'topic3')
other_user_public_project.update!(tag_list: 'topic4')
group_private_project.update!(tag_list: 'topic5')
group_public_project.update!(tag_list: 'topic6')
other_group_private_project.update!(tag_list: 'topic7')
other_group_public_project.update!(tag_list: 'topic8')
user_private_project.update!(topic_list: 'topic1')
user_public_project.update!(topic_list: 'topic2')
other_user_private_project.update!(topic_list: 'topic3')
other_user_public_project.update!(topic_list: 'topic4')
group_private_project.update!(topic_list: 'topic5')
group_public_project.update!(topic_list: 'topic6')
other_group_private_project.update!(topic_list: 'topic7')
other_group_public_project.update!(topic_list: 'topic8')
end
context 'with current_user' do
using RSpec::Parameterized::TableSyntax
where(:params, :expected_topics) do
{} | %w[topic1 topic2 topic4 topic5 topic6 topic8]
{ all_available: true } | %w[topic1 topic2 topic4 topic5 topic6 topic8]
{ all_available: false } | %w[topic1 topic2 topic5 topic6]
{} | %w[topic1 topic2 topic4 topic5 topic6 topic8]
{ personal: false } | %w[topic1 topic2 topic4 topic5 topic6 topic8]
{ personal: true } | %w[topic1 topic2 topic5 topic6]
end
with_them do
......@@ -76,9 +73,9 @@ RSpec.describe Projects::TopicsFinder do
using RSpec::Parameterized::TableSyntax
where(:params, :expected_topics) do
{} | %w[topic2 topic4 topic6 topic8]
{ all_available: true } | %w[topic2 topic4 topic6 topic8]
{ all_available: false } | %w[topic2 topic4 topic6 topic8]
{} | %w[topic2 topic4 topic6 topic8]
{ personal: false } | %w[topic2 topic4 topic6 topic8]
{ personal: true } | %w[topic2 topic4 topic6 topic8]
end
with_them do
......@@ -93,7 +90,7 @@ RSpec.describe Projects::TopicsFinder do
context 'filter by name' do
before do
user_public_project.update!(tag_list: 'aaabbb, bbbccc, dDd, dddddd')
user_public_project.update!(topic_list: 'aaabbb, bbbccc, dDd, dddddd')
end
using RSpec::Parameterized::TableSyntax
......@@ -109,7 +106,7 @@ RSpec.describe Projects::TopicsFinder do
with_them do
it 'returns correct topics filtered by name' do
topics = described_class.new(params: { name: search }).execute
topics = described_class.new(params: { search: search }).execute
expect(topics.map(&:name)).to contain_exactly(*expected_topics)
end
......@@ -118,10 +115,10 @@ RSpec.describe Projects::TopicsFinder do
context 'sort by attribute' do
before do
user_public_project.update!(tag_list: 'aaa, bbb, ccc, ddd')
other_user_public_project.update!(tag_list: 'bbb, ccc, ddd')
group_public_project.update!(tag_list: 'bbb, ccc')
other_group_public_project.update!(tag_list: 'ccc')
user_public_project.update!(topic_list: 'aaa, bbb, ccc, ddd')
other_user_public_project.update!(topic_list: 'bbb, ccc, ddd')
group_public_project.update!(topic_list: 'bbb, ccc')
other_group_public_project.update!(topic_list: 'ccc')
end
using RSpec::Parameterized::TableSyntax
......
......@@ -12,7 +12,7 @@ RSpec.describe DashboardHelper do
describe '#dashboard_nav_links' do
it 'has all the expected links by default' do
menu_items = [:projects, :groups, :activity, :milestones, :snippets, :topics]
menu_items = [:projects, :groups, :activity, :milestones, :snippets]
expect(helper.dashboard_nav_links).to include(*menu_items)
end
......
......@@ -12,7 +12,7 @@ RSpec.describe ExploreHelper do
describe '#explore_nav_links' do
it 'has all the expected links by default' do
menu_items = [:projects, :groups, :snippets, :topics]
menu_items = [:projects, :groups, :snippets]
expect(helper.explore_nav_links).to contain_exactly(*menu_items)
end
......
......@@ -71,12 +71,6 @@ RSpec.describe Nav::TopNavHelper do
icon: 'snippet',
id: 'snippets',
title: 'Snippets'
),
::Gitlab::Nav::TopNavMenuItem.build(
href: '/explore/topics',
icon: 'label',
id: 'topics',
title: 'Topics'
)
]
expect(subject[:primary]).to eq(expected_primary)
......@@ -101,12 +95,6 @@ RSpec.describe Nav::TopNavHelper do
id: 'snippets-shortcut',
title: 'Snippets',
css_class: 'dashboard-shortcuts-snippets'
),
::Gitlab::Nav::TopNavMenuItem.build(
href: '/explore/topics',
id: 'topics-shortcut',
title: 'Topics',
css_class: 'dashboard-shortcuts-topics'
)
]
expect(subject[:shortcuts]).to eq(expected_shortcuts)
......
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