From 75496059a1c36c2139bf29fa20fd4370c511fdd1 Mon Sep 17 00:00:00 2001 From: Dennis Tang Date: Fri, 7 Dec 2018 14:11:42 +0000 Subject: Further design iteration on project overview Continues the iteration on the project overview UI: - moved star, fork and new clone button (copy SSH/HTTPS URLs) to top right, made them smaller - avatar is now larger (64px) - 'Request access' is now a link instead of a button - overview comes before the description + changed styling and added icons - description font-size is now 16px (large-paragraph) - quick links to files are moved downwards below the commit/pipeline info - margins changed to group content into 4 groups to clean up the interface - visibility info reduced to icon-only and moved to the right of the title --- app/assets/javascripts/notifications_dropdown.js | 4 + app/assets/javascripts/pages/projects/project.js | 9 +- app/assets/stylesheets/framework/avatar.scss | 2 + app/assets/stylesheets/framework/buttons.scss | 10 +- app/assets/stylesheets/framework/mobile.scss | 9 - app/assets/stylesheets/framework/variables.scss | 6 +- app/assets/stylesheets/pages/projects.scss | 187 +++++++++++++++------ .../notification_settings_controller.rb | 10 +- app/helpers/button_helper.rb | 5 +- app/helpers/visibility_level_helper.rb | 2 +- app/presenters/project_presenter.rb | 140 +++++++++------ app/views/groups/_home_panel.html.haml | 2 +- app/views/projects/_files.html.haml | 6 + app/views/projects/_home_panel.html.haml | 130 +++++++------- app/views/projects/_stat_anchor_list.html.haml | 4 +- app/views/projects/buttons/_clone.html.haml | 31 ++++ app/views/projects/buttons/_download.html.haml | 2 +- app/views/projects/buttons/_fork.html.haml | 6 +- .../projects/buttons/_notifications.html.haml | 27 +++ app/views/projects/buttons/_star.html.haml | 12 +- app/views/projects/empty.html.haml | 122 +++++++------- app/views/projects/show.html.haml | 16 +- app/views/projects/tree/_tree_header.html.haml | 4 + app/views/shared/_mobile_clone_panel.html.haml | 12 +- .../shared/members/_access_request_links.html.haml | 17 ++ app/views/shared/notifications/_button.html.haml | 6 +- ...further-improvements-to-project-overview-ui.yml | 5 + locale/gitlab.pot | 94 +++++++---- ...eloper_views_empty_project_instructions_spec.rb | 49 +----- .../show/user_manages_notifications_spec.rb | 15 +- .../show/user_sees_collaboration_links_spec.rb | 23 --- .../show/user_sees_git_instructions_spec.rb | 4 +- .../show/user_sees_setup_shortcut_buttons_spec.rb | 90 +++++----- spec/features/tags/master_views_tags_spec.rb | 2 +- spec/presenters/project_presenter_spec.rb | 96 +++++------ spec/views/projects/_home_panel.html.haml_spec.rb | 2 +- 36 files changed, 673 insertions(+), 488 deletions(-) create mode 100644 app/views/projects/buttons/_clone.html.haml create mode 100644 app/views/projects/buttons/_notifications.html.haml create mode 100644 app/views/shared/members/_access_request_links.html.haml create mode 100644 changelogs/unreleased/51243-further-improvements-to-project-overview-ui.yml diff --git a/app/assets/javascripts/notifications_dropdown.js b/app/assets/javascripts/notifications_dropdown.js index c4c8cf86cb0..e7fa05faa8a 100644 --- a/app/assets/javascripts/notifications_dropdown.js +++ b/app/assets/javascripts/notifications_dropdown.js @@ -12,6 +12,10 @@ export default function notificationsDropdown() { const form = $(this).parents('.notification-form:first'); form.find('.js-notification-loading').toggleClass('fa-bell fa-spin fa-spinner'); + if (form.hasClass('no-label')) { + form.find('.js-notification-loading').toggleClass('hidden'); + form.find('.js-notifications-icon').toggleClass('hidden'); + } form.find('#notification_setting_level').val(notificationLevel); form.submit(); }); diff --git a/app/assets/javascripts/pages/projects/project.js b/app/assets/javascripts/pages/projects/project.js index a6bee49a6b1..b288989b252 100644 --- a/app/assets/javascripts/pages/projects/project.js +++ b/app/assets/javascripts/pages/projects/project.js @@ -13,6 +13,9 @@ export default class Project { const $cloneOptions = $('ul.clone-options-dropdown'); const $projectCloneField = $('#project_clone'); const $cloneBtnLabel = $('.js-git-clone-holder .js-clone-dropdown-label'); + const mobileCloneField = document.querySelector( + '.js-mobile-git-clone .js-clone-dropdown-label', + ); const selectedCloneOption = $cloneBtnLabel.text().trim(); if (selectedCloneOption.length > 0) { @@ -36,7 +39,11 @@ export default class Project { $label.text(activeText); }); - $projectCloneField.val(url); + if (mobileCloneField) { + mobileCloneField.dataset.clipboardText = url; + } else { + $projectCloneField.val(url); + } $('.js-git-empty .js-clone').text(url); }); // Ref switcher diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss index fcf282a7d7c..054c75912ea 100644 --- a/app/assets/stylesheets/framework/avatar.scss +++ b/app/assets/stylesheets/framework/avatar.scss @@ -21,6 +21,7 @@ &.s46 { @include avatar-size(46px, 15px); } &.s48 { @include avatar-size(48px, 10px); } &.s60 { @include avatar-size(60px, 12px); } + &.s64 { @include avatar-size(64px, 14px); } &.s70 { @include avatar-size(70px, 14px); } &.s90 { @include avatar-size(90px, 15px); } &.s100 { @include avatar-size(100px, 15px); } @@ -80,6 +81,7 @@ &.s40 { font-size: 16px; line-height: 38px; } &.s48 { font-size: 20px; line-height: 46px; } &.s60 { font-size: 32px; line-height: 58px; } + &.s64 { font-size: 32px; line-height: 64px; } &.s70 { font-size: 34px; line-height: 70px; } &.s90 { font-size: 36px; line-height: 88px; } &.s100 { font-size: 36px; line-height: 98px; } diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index 219fd99b097..e36f99ac577 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -142,8 +142,14 @@ &.btn-sm { padding: 4px 10px; - font-size: 13px; - line-height: 18px; + font-size: $gl-btn-small-font-size; + line-height: $gl-btn-small-line-height; + } + + &.btn-xs { + padding: 2px $gl-btn-padding; + font-size: $gl-btn-small-font-size; + line-height: $gl-btn-small-line-height; } &.btn-success, diff --git a/app/assets/stylesheets/framework/mobile.scss b/app/assets/stylesheets/framework/mobile.scss index 6d20c46b99d..3bb046d0e51 100644 --- a/app/assets/stylesheets/framework/mobile.scss +++ b/app/assets/stylesheets/framework/mobile.scss @@ -39,15 +39,6 @@ .git-clone-holder { display: none; } - - // Display Star and Fork buttons without counters on mobile. - .project-repo-buttons { - display: block; - - .count-buttons .count-badge { - margin-top: $gl-padding-8; - } - } } .group-buttons { diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 4fcdb862b6d..134b3a4521b 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -197,6 +197,7 @@ $well-light-text-color: #5b6169; $gl-font-size: 14px; $gl-font-size-xs: 11px; $gl-font-size-small: 12px; +$gl-font-size-large: 16px; $gl-font-weight-normal: 400; $gl-font-weight-bold: 600; $gl-text-color: #2e2e2e; @@ -270,7 +271,8 @@ $performance-bar-height: 35px; $flash-height: 52px; $context-header-height: 60px; $breadcrumb-min-height: 48px; -$project-title-row-height: 24px; +$project-title-row-height: 64px; +$project-avatar-mobile-size: 24px; $gl-line-height: 16px; $gl-line-height-24: 24px; @@ -365,6 +367,8 @@ $gl-btn-padding: 10px; $gl-btn-line-height: 16px; $gl-btn-vert-padding: 8px; $gl-btn-horz-padding: 12px; +$gl-btn-small-font-size: 13px; +$gl-btn-small-line-height: 13px; /* * Badges diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 6cc21072acd..278800aba95 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -144,7 +144,6 @@ .group-home-panel { padding-top: 24px; padding-bottom: 24px; - border-bottom: 1px solid $border-color; .group-avatar { float: none; @@ -155,7 +154,6 @@ } } - .project-title, .group-title { margin-top: 10px; margin-bottom: 10px; @@ -195,25 +193,69 @@ } .project-home-panel { - padding-top: $gl-padding-8; - padding-bottom: $gl-padding-24; - - .project-title-row { - margin-right: $gl-padding-8; - } + padding-top: $gl-padding; + padding-bottom: $gl-padding; .project-avatar { width: $project-title-row-height; height: $project-title-row-height; flex-shrink: 0; flex-basis: $project-title-row-height; - margin: 0 $gl-padding-8 0 0; + margin: 0 $gl-padding 0 0; } .project-title { + margin-top: 8px; + margin-bottom: 5px; font-size: 20px; - line-height: $project-title-row-height; + line-height: $gl-line-height-24; font-weight: bold; + + .icon { + font-size: $gl-font-size-large; + } + + .project-visibility { + color: $gl-text-color-secondary; + } + + .project-tag-list { + font-size: $gl-font-size; + font-weight: $gl-font-weight-normal; + + .icon { + position: relative; + top: 3px; + margin-right: $gl-padding-4; + } + } + } + + .project-title-row { + @include media-breakpoint-down(sm) { + .project-avatar { + width: $project-avatar-mobile-size; + height: $project-avatar-mobile-size; + flex-basis: $project-avatar-mobile-size; + + .avatar { + font-size: 20px; + line-height: 46px; + } + } + + .project-title { + margin-top: 4px; + margin-bottom: 2px; + font-size: $gl-font-size; + line-height: $gl-font-size-large; + } + + .project-tag-list, + .project-metadata { + font-size: $gl-font-size-small; + } + } } .project-metadata { @@ -222,16 +264,6 @@ line-height: $gl-btn-line-height; color: $gl-text-color-secondary; - .icon { - margin-right: $gl-padding-4; - font-size: 16px; - } - - .project-visibility, - .project-license, - .project-tag-list { - margin-right: $gl-padding-8; - } .project-license { .btn { @@ -240,12 +272,22 @@ } } - .project-tag-list, - .project-license { - .icon { - position: relative; - top: 2px; - } + .access-request-link, + .project-tag-list { + padding-left: $gl-padding-8; + border-left: 1px solid $gl-text-color-secondary; + } + } + + .project-description { + @include media-breakpoint-up(md) { + font-size: $gl-font-size-large; + } + } + + .notifications-btn { + .fa-bell { + margin-right: 0; } } } @@ -298,14 +340,6 @@ vertical-align: top; margin-top: $gl-padding; - .count-badge { - height: $input-height; - - .icon { - top: -1px; - } - } - .count-badge-count, .count-badge-button { border: 1px solid $border-color; @@ -319,29 +353,25 @@ .count-badge-count { padding: 0 12px; - border-right: 0; - border-radius: $border-radius-base 0 0 $border-radius-base; background: $gray-light; + border-radius: 0 $border-radius-base $border-radius-base 0; } .count-badge-button { - border-radius: 0 $border-radius-base $border-radius-base 0; + border-right: 0; + border-radius: $border-radius-base 0 0 $border-radius-base; } } .project-clone-holder { display: inline-block; - margin: $gl-padding $gl-padding-8 0 0; + margin: $gl-padding 0 0; input { height: $input-height; } } - .clone-dropdown-btn { - background-color: $white-light; - } - .clone-options-dropdown { min-width: 240px; @@ -355,6 +385,31 @@ } } +.project-repo-buttons { + .icon { + top: 0; + } + + .count-badge, + .btn-xs { + height: 24px; + } + + .dropdown-toggle, + .clone-dropdown-btn { + .fa { + color: unset; + } + } + + .btn { + .notifications-icon { + top: 1px; + margin-right: 0; + } + } +} + .split-one { display: inline-table; margin-right: 12px; @@ -715,10 +770,10 @@ border-bottom: 1px solid $border-color; } -.project-stats { +.project-stats, +.project-buttons { font-size: 0; text-align: center; - border-bottom: 1px solid $border-color; .scrolling-tabs-container { .scrolling-tabs { @@ -786,23 +841,43 @@ font-size: $gl-font-size; line-height: $gl-btn-line-height; color: $gl-text-color-secondary; - white-space: nowrap; + white-space: pre-wrap; } .stat-link { border-bottom: 0; + color: $black; &:hover, &:focus { - color: $gl-text-color; text-decoration: underline; border-bottom: 0; } + + .project-stat-value { + color: $gl-text-color; + } + + .icon { + color: $gl-text-color-secondary; + } + + .add-license-link { + &, + .icon { + color: $blue-600; + } + } } .btn { - padding: $gl-btn-vert-padding $gl-btn-horz-padding; + margin-top: $gl-padding; + padding: $gl-btn-vert-padding $gl-btn-padding; line-height: $gl-btn-line-height; + + .icon { + top: 0; + } } .btn-missing { @@ -811,6 +886,13 @@ } } +.project-buttons { + .stat-text { + @extend .btn; + @extend .btn-default; + } +} + .repository-languages-bar { height: 8px; margin-bottom: $gl-padding-8; @@ -934,8 +1016,6 @@ pre.light-well { } .git-clone-holder { - width: 320px; - .btn-clipboard { border: 1px solid $border-color; } @@ -958,6 +1038,15 @@ pre.light-well { } } +.git-clone-holder, +.mobile-git-clone { + .btn { + .icon { + fill: $white; + } + } +} + .cannot-be-merged, .cannot-be-merged:hover { color: $red-500; diff --git a/app/controllers/notification_settings_controller.rb b/app/controllers/notification_settings_controller.rb index 84dce74ace8..384f308269a 100644 --- a/app/controllers/notification_settings_controller.rb +++ b/app/controllers/notification_settings_controller.rb @@ -16,7 +16,11 @@ class NotificationSettingsController < ApplicationController @notification_setting = current_user.notification_settings.find(params[:id]) @saved = @notification_setting.update(notification_setting_params_for(@notification_setting.source)) - render_response + if params[:hide_label].present? + render_response("projects/buttons/_notifications") + else + render_response + end end private @@ -37,9 +41,9 @@ class NotificationSettingsController < ApplicationController can?(current_user, ability_name, resource) end - def render_response + def render_response(response_template = "shared/notifications/_button") render json: { - html: view_to_html_string("shared/notifications/_button", notification_setting: @notification_setting), + html: view_to_html_string(response_template, notification_setting: @notification_setting), saved: @saved } end diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb index 7f071d55a6b..494c754e7d5 100644 --- a/app/helpers/button_helper.rb +++ b/app/helpers/button_helper.rb @@ -85,13 +85,14 @@ module ButtonHelper dropdown_item_with_description('SSH', dropdown_description, href: append_url, data: { clone_type: 'ssh' }) end - def dropdown_item_with_description(title, description, href: nil, data: nil) + def dropdown_item_with_description(title, description, href: nil, data: nil, default: false) + active_class = "is-active" if default button_content = content_tag(:strong, title, class: 'dropdown-menu-inner-title') button_content << content_tag(:span, description, class: 'dropdown-menu-inner-content') if description content_tag (href ? :a : :span), (href ? button_content : title), - class: "#{title.downcase}-selector", + class: "#{title.downcase}-selector #{active_class}", href: (href if href), data: (data if data) end diff --git a/app/helpers/visibility_level_helper.rb b/app/helpers/visibility_level_helper.rb index e690350a0d1..712f0f808dd 100644 --- a/app/helpers/visibility_level_helper.rb +++ b/app/helpers/visibility_level_helper.rb @@ -140,7 +140,7 @@ module VisibilityLevelHelper end def project_visibility_icon_description(level) - "#{project_visibility_level_description(level)}" + "#{visibility_level_label(level)} - #{project_visibility_level_description(level)}" end def visibility_level_label(level) diff --git a/app/presenters/project_presenter.rb b/app/presenters/project_presenter.rb index d61124fa787..9bd64ea217e 100644 --- a/app/presenters/project_presenter.rb +++ b/app/presenters/project_presenter.rb @@ -6,27 +6,27 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated include GitlabRoutingHelper include StorageHelper include TreeHelper + include IconsHelper include ChecksCollaboration include Gitlab::Utils::StrongMemoize presents :project - AnchorData = Struct.new(:enabled, :label, :link, :class_modifier) + AnchorData = Struct.new(:is_link, :label, :link, :class_modifier, :icon) MAX_TAGS_TO_SHOW = 3 + def statistic_icon(icon_name = 'plus-square-o') + sprite_icon(icon_name, size: 16, css_class: 'icon append-right-4') + end + def statistics_anchors(show_auto_devops_callout:) [ - readme_anchor_data, - changelog_anchor_data, - contribution_guide_anchor_data, - files_anchor_data, + license_anchor_data, commits_anchor_data, branches_anchor_data, tags_anchor_data, - gitlab_ci_anchor_data, - autodevops_anchor_data(show_auto_devops_callout: show_auto_devops_callout), - kubernetes_cluster_anchor_data - ].compact.select { |item| item.enabled } + files_anchor_data + ].compact.select(&:is_link) end def statistics_buttons(show_auto_devops_callout:) @@ -37,27 +37,28 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated autodevops_anchor_data(show_auto_devops_callout: show_auto_devops_callout), kubernetes_cluster_anchor_data, gitlab_ci_anchor_data - ].compact.reject { |item| item.enabled } + ].compact.reject(&:is_link) end def empty_repo_statistics_anchors [ - files_anchor_data, + license_anchor_data, commits_anchor_data, branches_anchor_data, tags_anchor_data, - autodevops_anchor_data, - kubernetes_cluster_anchor_data - ].compact.select { |item| item.enabled } + files_anchor_data + ].compact.select { |item| item.is_link } end def empty_repo_statistics_buttons [ new_file_anchor_data, readme_anchor_data, + changelog_anchor_data, + contribution_guide_anchor_data, autodevops_anchor_data, kubernetes_cluster_anchor_data - ].compact.reject { |item| item.enabled } + ].compact.reject { |item| item.is_link } end def default_view @@ -113,7 +114,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated end def add_contribution_guide_path - add_special_file_path(file_name: 'CONTRIBUTING.md', commit_message: 'Add contribution guide') + add_special_file_path(file_name: 'CONTRIBUTING.md', commit_message: 'Add CONTRIBUTING') end def add_ci_yml_path @@ -149,32 +150,52 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated def files_anchor_data AnchorData.new(true, - _('Files (%{human_size})') % { human_size: storage_counter(statistics.total_repository_size) }, + statistic_icon('doc-code') + + _('%{strong_start}%{human_size}%{strong_end} Files').html_safe % { + human_size: storage_counter(statistics.total_repository_size), + strong_start: ''.html_safe, + strong_end: ''.html_safe + }, empty_repo? ? nil : project_tree_path(project)) end def commits_anchor_data AnchorData.new(true, - n_('Commit (%{commit_count})', 'Commits (%{commit_count})', statistics.commit_count) % { commit_count: number_with_delimiter(statistics.commit_count) }, + statistic_icon('commit') + + n_('%{strong_start}%{commit_count}%{strong_end} Commit', '%{strong_start}%{commit_count}%{strong_end} Commits', statistics.commit_count).html_safe % { + commit_count: number_with_delimiter(statistics.commit_count), + strong_start: ''.html_safe, + strong_end: ''.html_safe + }, empty_repo? ? nil : project_commits_path(project, repository.root_ref)) end def branches_anchor_data AnchorData.new(true, - n_('Branch (%{branch_count})', 'Branches (%{branch_count})', repository.branch_count) % { branch_count: number_with_delimiter(repository.branch_count) }, + statistic_icon('branch') + + n_('%{strong_start}%{branch_count}%{strong_end} Branch', '%{strong_start}%{branch_count}%{strong_end} Branches', repository.branch_count).html_safe % { + branch_count: number_with_delimiter(repository.branch_count), + strong_start: ''.html_safe, + strong_end: ''.html_safe + }, empty_repo? ? nil : project_branches_path(project)) end def tags_anchor_data AnchorData.new(true, - n_('Tag (%{tag_count})', 'Tags (%{tag_count})', repository.tag_count) % { tag_count: number_with_delimiter(repository.tag_count) }, + statistic_icon('label') + + n_('%{strong_start}%{tag_count}%{strong_end} Tag', '%{strong_start}%{tag_count}%{strong_end} Tags', repository.tag_count).html_safe % { + tag_count: number_with_delimiter(repository.tag_count), + strong_start: ''.html_safe, + strong_end: ''.html_safe + }, empty_repo? ? nil : project_tags_path(project)) end def new_file_anchor_data if current_user && can_current_user_push_to_default_branch? AnchorData.new(false, - _('New file'), + statistic_icon + _('New file'), project_new_blob_path(project, default_branch || 'master'), 'success') end @@ -183,40 +204,45 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated def readme_anchor_data if current_user && can_current_user_push_to_default_branch? && repository.readme.nil? AnchorData.new(false, - _('Add Readme'), + statistic_icon + _('Add README'), add_readme_path) elsif repository.readme - AnchorData.new(true, - _('Readme'), - default_view != 'readme' ? readme_path : '#readme') + AnchorData.new(false, + statistic_icon('doc-text') + _('README'), + default_view != 'readme' ? readme_path : '#readme', + 'default', + 'doc-text') end end def changelog_anchor_data if current_user && can_current_user_push_to_default_branch? && repository.changelog.blank? AnchorData.new(false, - _('Add Changelog'), + statistic_icon + _('Add CHANGELOG'), add_changelog_path) elsif repository.changelog.present? - AnchorData.new(true, - _('Changelog'), - changelog_path) + AnchorData.new(false, + statistic_icon('doc-text') + _('CHANGELOG'), + changelog_path, + 'default') end end def license_anchor_data + icon = statistic_icon('scale') + if repository.license_blob.present? AnchorData.new(true, - license_short_name, + icon + content_tag(:strong, license_short_name, class: 'project-stat-value'), license_path) else if current_user && can_current_user_push_to_default_branch? - AnchorData.new(false, - _('Add license'), + AnchorData.new(true, + content_tag(:span, icon + _('Add license'), class: 'add-license-link d-flex'), add_license_path) else - AnchorData.new(false, - _('No license. All rights reserved'), + AnchorData.new(true, + icon + content_tag(:strong, _('No license. All rights reserved'), class: 'project-stat-value'), nil) end end @@ -225,22 +251,29 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated def contribution_guide_anchor_data if current_user && can_current_user_push_to_default_branch? && repository.contribution_guide.blank? AnchorData.new(false, - _('Add Contribution guide'), + statistic_icon + _('Add CONTRIBUTING'), add_contribution_guide_path) elsif repository.contribution_guide.present? - AnchorData.new(true, - _('Contribution guide'), + AnchorData.new(false, + statistic_icon('doc-text') + _('CONTRIBUTING'), contribution_guide_path) end end def autodevops_anchor_data(show_auto_devops_callout: false) if current_user && can?(current_user, :admin_pipeline, project) && repository.gitlab_ci_yml.blank? && !show_auto_devops_callout - AnchorData.new(auto_devops_enabled?, - auto_devops_enabled? ? _('Auto DevOps enabled') : _('Enable Auto DevOps'), - project_settings_ci_cd_path(project, anchor: 'autodevops-settings')) + if auto_devops_enabled? + AnchorData.new(false, + statistic_icon('doc-text') + _('Auto DevOps enabled'), + project_settings_ci_cd_path(project, anchor: 'autodevops-settings'), + 'default') + else + AnchorData.new(false, + statistic_icon + _('Enable Auto DevOps'), + project_settings_ci_cd_path(project, anchor: 'autodevops-settings')) + end elsif auto_devops_enabled? - AnchorData.new(true, + AnchorData.new(false, _('Auto DevOps enabled'), nil) end @@ -248,27 +281,32 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated def kubernetes_cluster_anchor_data if current_user && can?(current_user, :create_cluster, project) - cluster_link = clusters.count == 1 ? project_cluster_path(project, clusters.first) : project_clusters_path(project) if clusters.empty? - cluster_link = new_project_cluster_path(project) - end + AnchorData.new(false, + statistic_icon + _('Add Kubernetes cluster'), + new_project_cluster_path(project)) + else + cluster_link = clusters.count == 1 ? project_cluster_path(project, clusters.first) : project_clusters_path(project) - AnchorData.new(!clusters.empty?, - clusters.empty? ? _('Add Kubernetes cluster') : _('Kubernetes configured'), - cluster_link) + AnchorData.new(false, + _('Kubernetes configured'), + cluster_link, + 'default') + end end end def gitlab_ci_anchor_data if current_user && can_current_user_push_code? && repository.gitlab_ci_yml.blank? && !auto_devops_enabled? AnchorData.new(false, - _('Set up CI/CD'), + statistic_icon + _('Set up CI/CD'), add_ci_yml_path) elsif repository.gitlab_ci_yml.present? - AnchorData.new(true, - _('CI/CD configuration'), - ci_configuration_path) + AnchorData.new(false, + statistic_icon('doc-text') + _('CI/CD configuration'), + ci_configuration_path, + 'default') end end diff --git a/app/views/groups/_home_panel.html.haml b/app/views/groups/_home_panel.html.haml index a0760c2073b..6219da2c715 100644 --- a/app/views/groups/_home_panel.html.haml +++ b/app/views/groups/_home_panel.html.haml @@ -1,4 +1,4 @@ -.group-home-panel.text-center +.group-home-panel.text-center.border-bottom %div{ class: container_class } .avatar-container.s70.group-avatar = group_icon(@group, class: "avatar s70 avatar-tile") diff --git a/app/views/projects/_files.html.haml b/app/views/projects/_files.html.haml index 79530e78154..22a721ee9ad 100644 --- a/app/views/projects/_files.html.haml +++ b/app/views/projects/_files.html.haml @@ -1,7 +1,9 @@ +- is_project_overview = local_assigns.fetch(:is_project_overview, false) - commit = local_assigns.fetch(:commit) { @repository.commit } - ref = local_assigns.fetch(:ref) { current_ref } - project = local_assigns.fetch(:project) { @project } - content_url = local_assigns.fetch(:content_url) { @tree.readme ? project_blob_path(@project, tree_join(@ref, @tree.readme.path)) : project_tree_path(@project, @ref) } +- show_auto_devops_callout = show_auto_devops_callout?(@project) #tree-holder.tree-holder.clearfix .nav-block @@ -10,4 +12,8 @@ - if commit = render 'shared/commit_well', commit: commit, ref: ref, project: project + - if is_project_overview + .project-buttons.append-bottom-default + = render 'stat_anchor_list', anchors: @project.statistics_buttons(show_auto_devops_callout: show_auto_devops_callout) + = render 'projects/tree/tree_content', tree: @tree, content_url: content_url diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml index dcef4dd5b69..e191b009db2 100644 --- a/app/views/projects/_home_panel.html.haml +++ b/app/views/projects/_home_panel.html.haml @@ -1,83 +1,75 @@ - empty_repo = @project.empty_repo? -- license = @project.license_anchor_data +- show_auto_devops_callout = show_auto_devops_callout?(@project) .project-home-panel{ class: ("empty-project" if empty_repo) } - .limit-container-width{ class: container_class } - .project-header.d-flex.flex-row.flex-wrap.align-items-center.append-bottom-8 - .project-title-row.d-flex.align-items-center - .avatar-container.project-avatar.float-none - = project_icon(@project, alt: @project.name, class: 'avatar avatar-tile s24', width: 24, height: 24) - %h1.project-title.d-flex.align-items-baseline.qa-project-name - = @project.name - .project-metadata.d-flex.flex-row.flex-wrap.align-items-baseline - .project-visibility.d-inline-flex.align-items-baseline.visibility-icon.has-tooltip{ data: { container: 'body' }, title: visibility_icon_description(@project) } - = visibility_level_icon(@project.visibility_level, fw: false, options: {class: 'icon'}) - = visibility_level_label(@project.visibility_level) - - if license.present? - .project-license.d-inline-flex.align-items-baseline - = link_to_if license.link, sprite_icon('scale', size: 16, css_class: 'icon') + license.label, license.link, class: license.enabled ? 'btn btn-link btn-secondary-hover-link' : 'btn btn-link' - - if @project.tag_list.present? - .project-tag-list.d-inline-flex.align-items-baseline.has-tooltip{ data: { container: 'body' }, title: @project.has_extra_tags? ? @project.tag_list.join(', ') : nil } - = sprite_icon('tag', size: 16, css_class: 'icon') - = @project.tags_to_show - - if @project.has_extra_tags? - = _("+ %{count} more") % { count: @project.count_of_extra_tags_not_shown } + .project-header.row.append-bottom-8 + .project-title-row.col-md-12.col-lg-7.d-flex + .avatar-container.project-avatar.float-none + = project_icon(@project, alt: @project.name, class: 'avatar avatar-tile s64', width: 64, height: 64) + .d-flex.flex-column.flex-wrap.align-items-baseline + .d-inline-flex.align-items-baseline + %h1.project-title.qa-project-name + = @project.name + %span.project-visibility.prepend-left-8.d-inline-flex.align-items-baseline.visibility-icon.has-tooltip{ data: { container: 'body' }, title: visibility_icon_description(@project) } + = visibility_level_icon(@project.visibility_level, fw: false, options: {class: 'icon'}) + .project-metadata.d-flex.align-items-center + - if can?(current_user, :read_project, @project) + %span.text-secondary + = s_('ProjectPage|Project ID: %{project_id}') % { project_id: @project.id } + - if current_user + %span.access-request-links.prepend-left-8 + = render 'shared/members/access_request_links', source: @project + - if @project.tag_list.present? + %span.project-tag-list.d-inline-flex.prepend-left-8.has-tooltip{ data: { container: 'body' }, title: @project.has_extra_tags? ? @project.tag_list.join(', ') : nil } + = sprite_icon('tag', size: 16, css_class: 'icon append-right-4') + = @project.tags_to_show + - if @project.has_extra_tags? + = _("+ %{count} more") % { count: @project.count_of_extra_tags_not_shown } - .project-home-desc - - if @project.description.present? - .project-description - .project-description-markdown.read-more-container - = markdown_field(@project, :description) - %button.btn.btn-blank.btn-link.text-secondary.js-read-more-trigger.text-secondary.d-lg-none{ type: "button" } - = _("Read more") - - - if can?(current_user, :read_project, @project) - .text-secondary.prepend-top-8 - = s_('ProjectPage|Project ID: %{project_id}') % { project_id: @project.id } - - - if @project.forked? - %p - - if @project.fork_source - #{ s_('ForkedFromProjectPath|Forked from') } - = link_to project_path(@project.fork_source) do - = fork_source_name(@project) - - else - - deleted_message = s_('ForkedFromProjectPath|Forked from %{project_name} (deleted)') - = deleted_message % { project_name: fork_source_name(@project) } - - - if @project.badges.present? - .project-badges.prepend-top-default.append-bottom-default - - @project.badges.each do |badge| - %a.append-right-8{ href: badge.rendered_link_url(@project), - target: '_blank', - rel: 'noopener noreferrer' }> - %img.project-badge{ src: badge.rendered_image_url(@project), - 'aria-hidden': true, - alt: 'Project badge' }> + .project-repo-buttons.col-md-12.col-lg-5.d-inline-flex.flex-wrap.justify-content-lg-end + - if current_user + .d-inline-flex + = render 'projects/buttons/notifications', notification_setting: @notification_setting, btn_class: 'btn-xs' - .project-repo-buttons.d-inline-flex.flex-wrap .count-buttons.d-inline-flex = render 'projects/buttons/star' = render 'projects/buttons/fork' - if can?(current_user, :download_code, @project) - .project-clone-holder.d-inline-flex.d-sm-none + .project-clone-holder.d-inline-flex.d-md-none.btn-block = render "shared/mobile_clone_panel" - .project-clone-holder.d-none.d-sm-inline-flex - = render "shared/clone_panel" + .project-clone-holder.d-none.d-md-inline-flex + = render "projects/buttons/clone" - - if show_xcode_link?(@project) - .project-action-button.project-xcode.inline - = render "projects/buttons/xcode_link" + - if can?(current_user, :download_code, @project) + %nav.project-stats + .nav-links.quick-links.mt-3 + = render 'stat_anchor_list', anchors: @project.statistics_anchors(show_auto_devops_callout: show_auto_devops_callout) - - if current_user - - if can?(current_user, :download_code, @project) - .d-none.d-sm-inline-flex - = render 'projects/buttons/download', project: @project, ref: @ref - .d-none.d-sm-inline-flex - = render 'projects/buttons/dropdown' + .project-home-desc.mt-1 + - if @project.description.present? + .project-description + .project-description-markdown.read-more-container + = markdown_field(@project, :description) + %button.btn.btn-blank.btn-link.js-read-more-trigger.d-lg-none{ type: "button" } + = _("Read more") + + - if @project.forked? + %p + - if @project.fork_source + #{ s_('ForkedFromProjectPath|Forked from') } + = link_to project_path(@project.fork_source) do + = fork_source_name(@project) + - else + - deleted_message = s_('ForkedFromProjectPath|Forked from %{project_name} (deleted)') + = deleted_message % { project_name: fork_source_name(@project) } - .d-none.d-sm-inline-flex - = render 'shared/notifications/button', notification_setting: @notification_setting - .d-none.d-sm-inline-flex - = render 'shared/members/access_request_buttons', source: @project + - if @project.badges.present? + .project-badges.mb-2 + - @project.badges.each do |badge| + %a.append-right-8{ href: badge.rendered_link_url(@project), + target: '_blank', + rel: 'noopener noreferrer' }> + %img.project-badge{ src: badge.rendered_image_url(@project), + 'aria-hidden': true, + alt: 'Project badge' }> diff --git a/app/views/projects/_stat_anchor_list.html.haml b/app/views/projects/_stat_anchor_list.html.haml index 4cf49f3cf62..8e3d759b683 100644 --- a/app/views/projects/_stat_anchor_list.html.haml +++ b/app/views/projects/_stat_anchor_list.html.haml @@ -4,5 +4,5 @@ %ul.nav - anchors.each do |anchor| %li.nav-item - = link_to_if anchor.link, anchor.label, anchor.link, class: anchor.enabled ? 'nav-link stat-link' : "nav-link btn btn-#{anchor.class_modifier || 'missing'}" do - .stat-text= anchor.label + = link_to_if anchor.link, anchor.label, anchor.link, class: anchor.is_link ? 'nav-link stat-link d-flex align-items-center' : "nav-link btn btn-#{anchor.class_modifier || 'missing'} d-flex align-items-center" do + .stat-text.d-flex.align-items-center= anchor.label diff --git a/app/views/projects/buttons/_clone.html.haml b/app/views/projects/buttons/_clone.html.haml new file mode 100644 index 00000000000..d82a3dd70f9 --- /dev/null +++ b/app/views/projects/buttons/_clone.html.haml @@ -0,0 +1,31 @@ +- project = project || @project + +.git-clone-holder.js-git-clone-holder.input-group + - if allowed_protocols_present? + .input-group-text.clone-dropdown-btn.btn + %span.js-clone-dropdown-label + = enabled_project_button(project, enabled_protocol) + - else + %a#clone-dropdown.input-group-text.btn.btn-primary.btn-xs.clone-dropdown-btn.qa-clone-dropdown{ href: '#', data: { toggle: 'dropdown' } } + %span.append-right-4.js-clone-dropdown-label + = _('Clone') + = sprite_icon("arrow-down", css_class: "icon") + %form.p-3.dropdown-menu.dropdown-menu-right.dropdown-menu-large.dropdown-menu-selectable.clone-options-dropdown + %li.pb-2 + %label.label-bold + = _('Clone with SSH') + .input-group + = text_field_tag :ssh_project_clone, project.ssh_url_to_repo, class: "js-select-on-focus form-control", readonly: true, aria: { label: 'Project clone URL' } + .input-group-append + = clipboard_button(target: '#ssh_project_clone', title: _("Copy URL to clipboard"), class: "input-group-text btn-default btn-clipboard") + = render_if_exists 'projects/buttons/geo' + %li + %label.label-bold + = _('Clone with %{http_label}') % { http_label: gitlab_config.protocol.upcase } + .input-group + = text_field_tag :http_project_clone, project.http_url_to_repo, class: "js-select-on-focus form-control", readonly: true, aria: { label: 'Project clone URL' } + .input-group-append + = clipboard_button(target: '#http_project_clone', title: _("Copy URL to clipboard"), class: "input-group-text btn-default btn-clipboard") + = render_if_exists 'projects/buttons/geo' + += render_if_exists 'shared/geo_info_modal', project: project diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml index f7551434d47..4eb53faa6ff 100644 --- a/app/views/projects/buttons/_download.html.haml +++ b/app/views/projects/buttons/_download.html.haml @@ -5,8 +5,8 @@ .project-action-button.dropdown.inline> %button.btn.has-tooltip{ title: s_('DownloadSource|Download'), 'data-toggle' => 'dropdown', 'aria-label' => s_('DownloadSource|Download'), 'data-display' => 'static' } = sprite_icon('download') - = icon("caret-down") %span.sr-only= _('Select Archive Format') + = sprite_icon("arrow-down") %ul.dropdown-menu.dropdown-menu-right{ role: 'menu' } %li.dropdown-header #{ _('Source code') } diff --git a/app/views/projects/buttons/_fork.html.haml b/app/views/projects/buttons/_fork.html.haml index 8da27ca7cb3..bc0a89bea62 100644 --- a/app/views/projects/buttons/_fork.html.haml +++ b/app/views/projects/buttons/_fork.html.haml @@ -1,9 +1,6 @@ - unless @project.empty_repo? - if current_user && can?(current_user, :fork_project, @project) .count-badge.d-inline-flex.align-item-stretch.append-right-8 - %span.fork-count.count-badge-count.d-flex.align-items-center - = link_to project_forks_path(@project), title: n_(s_('ProjectOverview|Fork'), s_('ProjectOverview|Forks'), @project.forks_count), class: 'count' do - = @project.forks_count - if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2 = link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: s_('ProjectOverview|Go to your fork'), class: 'btn btn-default has-tooltip count-badge-button d-flex align-items-center fork-btn' do = sprite_icon('fork', { css_class: 'icon' }) @@ -15,3 +12,6 @@ title: (s_('ProjectOverview|You have reached your project limit') unless can_create_fork) do = sprite_icon('fork', { css_class: 'icon' }) %span= s_('ProjectOverview|Fork') + %span.fork-count.count-badge-count.d-flex.align-items-center + = link_to project_forks_path(@project), title: n_(s_('ProjectOverview|Fork'), s_('ProjectOverview|Forks'), @project.forks_count), class: 'count' do + = @project.forks_count diff --git a/app/views/projects/buttons/_notifications.html.haml b/app/views/projects/buttons/_notifications.html.haml new file mode 100644 index 00000000000..745983ace7e --- /dev/null +++ b/app/views/projects/buttons/_notifications.html.haml @@ -0,0 +1,27 @@ +- btn_class = local_assigns.fetch(:btn_class, "btn-xs") + +- if notification_setting + .js-notification-dropdown.notification-dropdown.project-action-button.dropdown.inline + = form_for notification_setting, remote: true, html: { class: "inline notification-form no-label" } do |f| + = hidden_setting_source_input(notification_setting) + = hidden_field_tag "hide_label", true + = f.hidden_field :level, class: "notification_setting_level" + .js-notification-toggle-btns + %div{ class: ("btn-group" if notification_setting.custom?) } + - if notification_setting.custom? + %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: _("Notification setting - %{notification_title}") % { notification_title: notification_title(notification_setting.level) }, class: "#{btn_class}", "aria-label" => _("Notification setting - %{notification_title}") % { notification_title: notification_title(notification_setting.level) }, data: { container: "body", toggle: "modal", target: "#" + notifications_menu_identifier("modal", notification_setting), display: 'static' } } + = sprite_icon("notifications", css_class: "icon notifications-icon js-notifications-icon") + %span.js-notification-loading.fa.hidden + %button.btn.dropdown-toggle{ data: { toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } } + = sprite_icon("arrow-down", css_class: "icon") + .sr-only Toggle dropdown + - else + %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: "Notification setting - #{notification_title(notification_setting.level)}", class: "#{btn_class}", "aria-label" => "Notification setting: #{notification_title(notification_setting.level)}", data: { container: "body", toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } } + = sprite_icon("notifications", css_class: "icon notifications-icon js-notifications-icon") + %span.js-notification-loading.fa.hidden + = sprite_icon("arrow-down", css_class: "icon") + + = render "shared/notifications/notification_dropdown", notification_setting: notification_setting + + = content_for :scripts_body do + = render "shared/notifications/custom_notifications", notification_setting: notification_setting diff --git a/app/views/projects/buttons/_star.html.haml b/app/views/projects/buttons/_star.html.haml index 0d04ecb3a58..090d1549aa7 100644 --- a/app/views/projects/buttons/_star.html.haml +++ b/app/views/projects/buttons/_star.html.haml @@ -1,19 +1,19 @@ - if current_user .count-badge.d-inline-flex.align-item-stretch.append-right-8 - %span.star-count.count-badge-count.d-flex.align-items-center - = @project.star_count - %button.count-badge-button.btn.btn-default.d-flex.align-items-center.star-btn.toggle-star{ type: "button", data: { endpoint: toggle_star_project_path(@project, :json) } } + %button.count-badge-button.btn.btn-default.btn-xs.d-flex.align-items-center.star-btn.toggle-star{ type: "button", data: { endpoint: toggle_star_project_path(@project, :json) } } - if current_user.starred?(@project) = sprite_icon('star', { css_class: 'icon' }) %span.starred= s_('ProjectOverview|Unstar') - else = sprite_icon('star-o', { css_class: 'icon' }) %span= s_('ProjectOverview|Star') + %span.star-count.count-badge-count.d-flex.align-items-center + = @project.star_count - else .count-badge.d-inline-flex.align-item-stretch.append-right-8 - %span.star-count.count-badge-count.d-flex.align-items-center - = @project.star_count - = link_to new_user_session_path, class: 'btn btn-default has-tooltip count-badge-button d-flex align-items-center star-btn', title: s_('ProjectOverview|You must sign in to star a project') do + = link_to new_user_session_path, class: 'btn btn-default btn-xs has-tooltip count-badge-button d-flex align-items-center star-btn', title: s_('ProjectOverview|You must sign in to star a project') do = sprite_icon('star-o', { css_class: 'icon' }) %span= s_('ProjectOverview|Star') + %span.star-count.count-badge-count.d-flex.align-items-center + = @project.star_count diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml index 936900a0087..aa690b12eb7 100644 --- a/app/views/projects/empty.html.haml +++ b/app/views/projects/empty.html.haml @@ -4,11 +4,10 @@ = render partial: 'flash_messages', locals: { project: @project } -= render "home_panel" +%div{ class: [container_class, ("limit-container-width" unless fluid_layout)] } + = render "home_panel" -.project-empty-note-panel - %div{ class: [container_class, ("limit-container-width" unless fluid_layout)] } - .prepend-top-20 + .project-empty-note-panel %h4.append-bottom-20 = _('The repository for this project is empty') @@ -32,66 +31,65 @@ = _('Otherwise it is recommended you start with one of the options below.') .prepend-top-20 -%nav.project-stats{ class: [container_class, ("limit-container-width" unless fluid_layout)] } - .scrolling-tabs-container.inner-page-scroll-tabs.is-smaller - .fade-left= icon('angle-left') - .fade-right= icon('angle-right') - .nav-links.scrolling-tabs.quick-links - = render 'stat_anchor_list', anchors: @project.empty_repo_statistics_anchors - = render 'stat_anchor_list', anchors: @project.empty_repo_statistics_buttons + %nav.project-buttons + .scrolling-tabs-container.inner-page-scroll-tabs.is-smaller + .fade-left= icon('angle-left') + .fade-right= icon('angle-right') + .nav-links.scrolling-tabs.quick-links + = render 'stat_anchor_list', anchors: @project.empty_repo_statistics_buttons -- if can?(current_user, :push_code, @project) - %div{ class: [container_class, ("limit-container-width" unless fluid_layout)] } - .prepend-top-20 - .empty_wrapper - %h3#repo-command-line-instructions.page-title-empty - Command line instructions - .git-empty.js-git-empty - %fieldset - %h5 Git global setup - %pre.bg-light - :preserve - git config --global user.name "#{h git_user_name}" - git config --global user.email "#{h git_user_email}" + - if can?(current_user, :push_code, @project) + %div + .prepend-top-20 + .empty_wrapper + %h3#repo-command-line-instructions.page-title-empty + = _('Command line instructions') + .git-empty.js-git-empty + %fieldset + %h5= _('Git global setup') + %pre.bg-light + :preserve + git config --global user.name "#{h git_user_name}" + git config --global user.email "#{h git_user_email}" - %fieldset - %h5 Create a new repository - %pre.bg-light - :preserve - git clone #{ content_tag(:span, default_url_to_repo, class: 'js-clone')} - cd #{h @project.path} - touch README.md - git add README.md - git commit -m "add README" - - if @project.can_current_user_push_to_default_branch? - %span>< - git push -u origin master + %fieldset + %h5= _('Create a new repository') + %pre.bg-light + :preserve + git clone #{ content_tag(:span, default_url_to_repo, class: 'js-clone')} + cd #{h @project.path} + touch README.md + git add README.md + git commit -m "add README" + - if @project.can_current_user_push_to_default_branch? + %span>< + git push -u origin master - %fieldset - %h5 Existing folder - %pre.bg-light - :preserve - cd existing_folder - git init - git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'js-clone')} - git add . - git commit -m "Initial commit" - - if @project.can_current_user_push_to_default_branch? - %span>< - git push -u origin master + %fieldset + %h5= _('Existing folder') + %pre.bg-light + :preserve + cd existing_folder + git init + git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'js-clone')} + git add . + git commit -m "Initial commit" + - if @project.can_current_user_push_to_default_branch? + %span>< + git push -u origin master - %fieldset - %h5 Existing Git repository - %pre.bg-light - :preserve - cd existing_repo - git remote rename origin old-origin - git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'js-clone')} - - if @project.can_current_user_push_to_default_branch? - %span>< - git push -u origin --all - git push -u origin --tags + %fieldset + %h5= _('Existing Git repository') + %pre.bg-light + :preserve + cd existing_repo + git remote rename origin old-origin + git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'js-clone')} + - if @project.can_current_user_push_to_default_branch? + %span>< + git push -u origin --all + git push -u origin --tags - - if can? current_user, :remove_project, @project - .prepend-top-20 - = link_to 'Remove project', [@project.namespace.becomes(Namespace), @project], data: { confirm: remove_project_message(@project)}, method: :delete, class: "btn btn-inverted btn-remove float-right" + - if can? current_user, :remove_project, @project + .prepend-top-20 + = link_to _('Remove project'), [@project.namespace.becomes(Namespace), @project], data: { confirm: remove_project_message(@project)}, method: :delete, class: "btn btn-inverted btn-remove float-right" diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index f29ce4f5c06..c87a084740b 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -1,7 +1,6 @@ - @no_container = true - breadcrumb_title _("Details") - @content_class = "limit-container-width" unless fluid_layout -- show_auto_devops_callout = show_auto_devops_callout?(@project) = content_for :meta_tags do = auto_discovery_link_tag(:atom, project_path(@project, rss_url_options), title: "#{@project.name} activity") @@ -15,20 +14,11 @@ %div{ class: [container_class, ("limit-container-width" unless fluid_layout)] } = render "projects/last_push" -= render "home_panel" - -- if can?(current_user, :download_code, @project) - %nav.project-stats{ class: [container_class, ("limit-container-width" unless fluid_layout)] } - .scrolling-tabs-container.inner-page-scroll-tabs.is-smaller - .fade-left= icon('angle-left') - .fade-right= icon('angle-right') - .nav-links.scrolling-tabs.quick-links - = render 'stat_anchor_list', anchors: @project.statistics_anchors(show_auto_devops_callout: show_auto_devops_callout) - = render 'stat_anchor_list', anchors: @project.statistics_buttons(show_auto_devops_callout: show_auto_devops_callout) + = render "home_panel" + - if can?(current_user, :download_code, @project) && @project.repository_languages.present? = repository_languages_bar(@project.repository_languages) -%div{ class: [container_class, ("limit-container-width" unless fluid_layout)] } - if @project.archived? .text-warning.center.prepend-top-20 %p @@ -41,4 +31,4 @@ = render 'shared/auto_devops_callout' %div{ class: project_child_container_class(view_path) } - = render view_path + = render view_path, is_project_overview: true diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml index 601e3f25852..a89df6adfb3 100644 --- a/app/views/projects/tree/_tree_header.html.haml +++ b/app/views/projects/tree/_tree_header.html.haml @@ -85,4 +85,8 @@ = link_to ide_edit_path(@project, @ref, @path), class: 'btn btn-default qa-web-ide-button' do = _('Web IDE') + - if show_xcode_link?(@project) + .project-action-button.project-xcode.inline + = render "projects/buttons/xcode_link" + = render 'projects/buttons/download', project: @project, ref: @ref diff --git a/app/views/shared/_mobile_clone_panel.html.haml b/app/views/shared/_mobile_clone_panel.html.haml index 998985cabe1..b43662947a8 100644 --- a/app/views/shared/_mobile_clone_panel.html.haml +++ b/app/views/shared/_mobile_clone_panel.html.haml @@ -1,13 +1,13 @@ - project = project || @project - ssh_copy_label = _("Copy SSH clone URL") -- http_copy_label = _("Copy HTTPS clone URL") +- http_copy_label = _('Copy %{http_label} clone URL') % { http_label: gitlab_config.protocol.upcase } -.btn-group.mobile-git-clone.js-mobile-git-clone - = clipboard_button(button_text: default_clone_label, target: '#project_clone', hide_button_icon: true, class: "input-group-text clone-dropdown-btn js-clone-dropdown-label btn btn-default") - %button.btn.btn-default.dropdown-toggle.js-dropdown-toggle{ type: "button", data: { toggle: "dropdown" } } - = icon("caret-down", class: "dropdown-btn-icon") +.btn-group.mobile-git-clone.js-mobile-git-clone.btn-block + = clipboard_button(button_text: default_clone_label, text: default_url_to_repo(project), hide_button_icon: true, class: "btn-primary flex-fill bold justify-content-center input-group-text clone-dropdown-btn js-clone-dropdown-label") + %button.btn.btn-primary.dropdown-toggle.js-dropdown-toggle{ type: "button", data: { toggle: "dropdown" } } + = sprite_icon("arrow-down", css_class: "dropdown-btn-icon icon") %ul.dropdown-menu.dropdown-menu-selectable.dropdown-menu-right.clone-options-dropdown{ data: { dropdown: true } } %li - = dropdown_item_with_description(ssh_copy_label, project.ssh_url_to_repo, href: project.ssh_url_to_repo, data: { clone_type: 'ssh' }) + = dropdown_item_with_description(ssh_copy_label, project.ssh_url_to_repo, href: project.ssh_url_to_repo, data: { clone_type: 'ssh' }, default: true) %li = dropdown_item_with_description(http_copy_label, project.http_url_to_repo, href: project.http_url_to_repo, data: { clone_type: 'http' }) diff --git a/app/views/shared/members/_access_request_links.html.haml b/app/views/shared/members/_access_request_links.html.haml new file mode 100644 index 00000000000..f7227b9101e --- /dev/null +++ b/app/views/shared/members/_access_request_links.html.haml @@ -0,0 +1,17 @@ +- model_name = source.model_name.to_s.downcase + +- if can?(current_user, :"destroy_#{model_name}_member", source.members.find_by(user_id: current_user.id)) # rubocop: disable CodeReuse/ActiveRecord + - link_text = source.is_a?(Group) ? _('Leave group') : _('Leave project') + = link_to link_text, polymorphic_path([:leave, source, :members]), + method: :delete, + data: { confirm: leave_confirmation_message(source) }, + class: 'access-request-link' +- elsif requester = source.requesters.find_by(user_id: current_user.id) # rubocop: disable CodeReuse/ActiveRecord + = link_to _('Withdraw Access Request'), polymorphic_path([:leave, source, :members]), + method: :delete, + data: { confirm: remove_member_message(requester) }, + class: 'access-request-link' +- elsif source.request_access_enabled && can?(current_user, :request_access, source) + = link_to _('Request Access'), polymorphic_path([:request_access, source, :members]), + method: :post, + class: 'access-request-link' diff --git a/app/views/shared/notifications/_button.html.haml b/app/views/shared/notifications/_button.html.haml index f6c7ca70ebd..30860988bbb 100644 --- a/app/views/shared/notifications/_button.html.haml +++ b/app/views/shared/notifications/_button.html.haml @@ -1,3 +1,5 @@ +- btn_class = local_assigns.fetch(:btn_class, nil) + - if notification_setting .js-notification-dropdown.notification-dropdown.project-action-button.dropdown.inline = form_for notification_setting, remote: true, html: { class: "inline notification-form" } do |f| @@ -6,14 +8,14 @@ .js-notification-toggle-btns %div{ class: ("btn-group" if notification_setting.custom?) } - if notification_setting.custom? - %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: "Notification setting", "aria-label" => "Notification setting: #{notification_title(notification_setting.level)}", data: { container: "body", toggle: "modal", target: "#" + notifications_menu_identifier("modal", notification_setting), display: 'static' } } + %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: _("Notification setting"), class: "#{btn_class}", "aria-label" => _("Notification setting - %{notification_title}") % { notification_title: notification_title(notification_setting.level) }, data: { container: "body", toggle: "modal", target: "#" + notifications_menu_identifier("modal", notification_setting), display: 'static' } } = icon("bell", class: "js-notification-loading") = notification_title(notification_setting.level) %button.btn.dropdown-toggle{ data: { toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } } = icon('caret-down') .sr-only Toggle dropdown - else - %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: "Notification setting", "aria-label" => "Notification setting: #{notification_title(notification_setting.level)}", data: { container: "body", toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } } + %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: "Notification setting", class: "#{btn_class}", "aria-label" => "Notification setting: #{notification_title(notification_setting.level)}", data: { container: "body", toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } } = icon("bell", class: "js-notification-loading") = notification_title(notification_setting.level) = icon("caret-down") diff --git a/changelogs/unreleased/51243-further-improvements-to-project-overview-ui.yml b/changelogs/unreleased/51243-further-improvements-to-project-overview-ui.yml new file mode 100644 index 00000000000..ddb5eaa89d0 --- /dev/null +++ b/changelogs/unreleased/51243-further-improvements-to-project-overview-ui.yml @@ -0,0 +1,5 @@ +--- +title: Design improvements to project overview page +merge_request: 22196 +author: +type: changed diff --git a/locale/gitlab.pot b/locale/gitlab.pot index fc923bf1554..d9707e17e4a 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -141,6 +141,24 @@ msgstr "" msgid "%{percent}%% complete" msgstr "" +msgid "%{strong_start}%{branch_count}%{strong_end} Branch" +msgid_plural "%{strong_start}%{branch_count}%{strong_end} Branches" +msgstr[0] "" +msgstr[1] "" + +msgid "%{strong_start}%{commit_count}%{strong_end} Commit" +msgid_plural "%{strong_start}%{commit_count}%{strong_end} Commits" +msgstr[0] "" +msgstr[1] "" + +msgid "%{strong_start}%{human_size}%{strong_end} Files" +msgstr "" + +msgid "%{strong_start}%{tag_count}%{strong_end} Tag" +msgid_plural "%{strong_start}%{tag_count}%{strong_end} Tags" +msgstr[0] "" +msgstr[1] "" + msgid "%{text} %{files}" msgid_plural "%{text} %{files} files" msgstr[0] "" @@ -333,16 +351,16 @@ msgstr "" msgid "Activity" msgstr "" -msgid "Add Changelog" +msgid "Add CHANGELOG" msgstr "" -msgid "Add Contribution guide" +msgid "Add CONTRIBUTING" msgstr "" msgid "Add Kubernetes cluster" msgstr "" -msgid "Add Readme" +msgid "Add README" msgstr "" msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message." @@ -945,11 +963,6 @@ msgstr "" msgid "Branch %{branchName} was not found in this project's repository." msgstr "" -msgid "Branch (%{branch_count})" -msgid_plural "Branches (%{branch_count})" -msgstr[0] "" -msgstr[1] "" - msgid "Branch %{branch_name} was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}" msgstr "" @@ -1103,6 +1116,9 @@ msgstr "" msgid "ByAuthor|by" msgstr "" +msgid "CHANGELOG" +msgstr "" + msgid "CI / CD" msgstr "" @@ -1160,6 +1176,9 @@ msgstr "" msgid "CICD|instance enabled" msgstr "" +msgid "CONTRIBUTING" +msgstr "" + msgid "Callback URL" msgstr "" @@ -1202,9 +1221,6 @@ msgstr "" msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes." msgstr "" -msgid "Changelog" -msgstr "" - msgid "Changes are shown as if the source revision was being merged into the target revision." msgstr "" @@ -1388,9 +1404,18 @@ msgstr "" msgid "Clients" msgstr "" +msgid "Clone" +msgstr "" + msgid "Clone repository" msgstr "" +msgid "Clone with %{http_label}" +msgstr "" + +msgid "Clone with SSH" +msgstr "" + msgid "Close" msgstr "" @@ -1811,6 +1836,9 @@ msgstr "" msgid "Collapse sidebar" msgstr "" +msgid "Command line instructions" +msgstr "" + msgid "Comment" msgstr "" @@ -1831,11 +1859,6 @@ msgid_plural "Commits" msgstr[0] "" msgstr[1] "" -msgid "Commit (%{commit_count})" -msgid_plural "Commits (%{commit_count})" -msgstr[0] "" -msgstr[1] "" - msgid "Commit Message" msgstr "" @@ -2019,9 +2042,6 @@ msgstr "" msgid "Contribution Charts" msgstr "" -msgid "Contribution guide" -msgstr "" - msgid "Contributions for %{calendar_date}" msgstr "" @@ -2046,10 +2066,10 @@ msgstr "" msgid "ConvDev Index" msgstr "" -msgid "Copy %{protocol} clone URL" +msgid "Copy %{http_label} clone URL" msgstr "" -msgid "Copy HTTPS clone URL" +msgid "Copy %{protocol} clone URL" msgstr "" msgid "Copy ID to clipboard" @@ -2112,6 +2132,9 @@ msgstr "" msgid "Create a new issue" msgstr "" +msgid "Create a new repository" +msgstr "" + msgid "Create a personal access token on your account to pull or push via %{protocol}." msgstr "" @@ -2870,6 +2893,12 @@ msgstr "" msgid "Everyone can contribute" msgstr "" +msgid "Existing Git repository" +msgstr "" + +msgid "Existing folder" +msgstr "" + msgid "Expand" msgstr "" @@ -2978,9 +3007,6 @@ msgstr "" msgid "Files" msgstr "" -msgid "Files (%{human_size})" -msgstr "" - msgid "Filter" msgstr "" @@ -3107,6 +3133,9 @@ msgstr "" msgid "Git" msgstr "" +msgid "Git global setup" +msgstr "" + msgid "Git repository URL" msgstr "" @@ -4438,6 +4467,12 @@ msgstr "" msgid "Notification events" msgstr "" +msgid "Notification setting" +msgstr "" + +msgid "Notification setting - %{notification_title}" +msgstr "" + msgid "NotificationEvent|Close issue" msgstr "" @@ -5349,6 +5384,9 @@ msgstr "" msgid "Quick actions can be used in the issues description and comment boxes." msgstr "" +msgid "README" +msgstr "" + msgid "Read more" msgstr "" @@ -5358,9 +5396,6 @@ msgstr "" msgid "Read more about project permissions %{link_to_help}" msgstr "" -msgid "Readme" -msgstr "" - msgid "Real-time features" msgstr "" @@ -6294,11 +6329,6 @@ msgstr "" msgid "System metrics (Kubernetes)" msgstr "" -msgid "Tag (%{tag_count})" -msgid_plural "Tags (%{tag_count})" -msgstr[0] "" -msgstr[1] "" - msgid "Tags" msgstr "" diff --git a/spec/features/projects/show/developer_views_empty_project_instructions_spec.rb b/spec/features/projects/show/developer_views_empty_project_instructions_spec.rb index 227bdf524fe..8ba91fe7fd7 100644 --- a/spec/features/projects/show/developer_views_empty_project_instructions_spec.rb +++ b/spec/features/projects/show/developer_views_empty_project_instructions_spec.rb @@ -10,54 +10,9 @@ describe 'Projects > Show > Developer views empty project instructions' do sign_in(developer) end - context 'without an SSH key' do - it 'defaults to HTTP' do - visit_project - - expect_instructions_for('http') - end - - it 'switches to SSH', :js do - visit_project - - select_protocol('SSH') - - expect_instructions_for('ssh') - end - end - - context 'with an SSH key' do - before do - create(:personal_key, user: developer) - end - - it 'defaults to SSH' do - visit_project - - expect_instructions_for('ssh') - end - - it 'switches to HTTP', :js do - visit_project - - select_protocol('HTTP') - - expect_instructions_for('http') - end - end - - def visit_project + it 'displays "git clone" instructions' do visit project_path(project) - end - - def select_protocol(protocol) - find('#clone-dropdown').click - find(".#{protocol.downcase}-selector").click - end - - def expect_instructions_for(protocol) - msg = :"#{protocol.downcase}_url_to_repo" - expect(page).to have_content("git clone #{project.send(msg)}") + expect(page).to have_content("git clone") end end diff --git a/spec/features/projects/show/user_manages_notifications_spec.rb b/spec/features/projects/show/user_manages_notifications_spec.rb index 546619e88ec..88f3397608f 100644 --- a/spec/features/projects/show/user_manages_notifications_spec.rb +++ b/spec/features/projects/show/user_manages_notifications_spec.rb @@ -8,13 +8,18 @@ describe 'Projects > Show > User manages notifications', :js do visit project_path(project) end - it 'changes the notification setting' do + def click_notifications_button first('.notifications-btn').click + end + + it 'changes the notification setting' do + click_notifications_button click_link 'On mention' - page.within '#notifications-button' do - expect(page).to have_content 'On mention' - end + wait_for_requests + + click_notifications_button + expect(find('.update-notification.is-active')).to have_content('On mention') end context 'custom notification settings' do @@ -38,7 +43,7 @@ describe 'Projects > Show > User manages notifications', :js do end it 'shows notification settings checkbox' do - first('.notifications-btn').click + click_notifications_button page.find('a[data-notification-level="custom"]').click page.within('.custom-notifications-form') do diff --git a/spec/features/projects/show/user_sees_collaboration_links_spec.rb b/spec/features/projects/show/user_sees_collaboration_links_spec.rb index 7b3711531c6..24777788248 100644 --- a/spec/features/projects/show/user_sees_collaboration_links_spec.rb +++ b/spec/features/projects/show/user_sees_collaboration_links_spec.rb @@ -21,18 +21,6 @@ describe 'Projects > Show > Collaboration links' do end end - # The project header - page.within('.project-home-panel') do - aggregate_failures 'dropdown links in the project home panel' do - expect(page).to have_link('New issue') - expect(page).to have_link('New merge request') - expect(page).to have_link('New snippet') - expect(page).to have_link('New file') - expect(page).to have_link('New branch') - expect(page).to have_link('New tag') - end - end - # The dropdown above the tree page.within('.repo-breadcrumb') do aggregate_failures 'dropdown links above the repo tree' do @@ -61,17 +49,6 @@ describe 'Projects > Show > Collaboration links' do end end - page.within('.project-home-panel') do - aggregate_failures 'dropdown links' do - expect(page).not_to have_link('New issue') - expect(page).not_to have_link('New merge request') - expect(page).not_to have_link('New snippet') - expect(page).not_to have_link('New file') - expect(page).not_to have_link('New branch') - expect(page).not_to have_link('New tag') - end - end - page.within('.repo-breadcrumb') do aggregate_failures 'dropdown links' do expect(page).not_to have_link('New file') diff --git a/spec/features/projects/show/user_sees_git_instructions_spec.rb b/spec/features/projects/show/user_sees_git_instructions_spec.rb index 9a82fee1b5d..ffa80235083 100644 --- a/spec/features/projects/show/user_sees_git_instructions_spec.rb +++ b/spec/features/projects/show/user_sees_git_instructions_spec.rb @@ -29,7 +29,7 @@ describe 'Projects > Show > User sees Git instructions' do expect(element.text).to include(project.http_url_to_repo) end - expect(page).to have_field('project_clone', with: project.http_url_to_repo) unless user_has_ssh_key + expect(page).to have_field('http_project_clone', with: project.http_url_to_repo) unless user_has_ssh_key end end @@ -41,7 +41,7 @@ describe 'Projects > Show > User sees Git instructions' do expect(page).to have_content(project.title) end - expect(page).to have_field('project_clone', with: project.http_url_to_repo) unless user_has_ssh_key + expect(page).to have_field('http_project_clone', with: project.http_url_to_repo) unless user_has_ssh_key end end diff --git a/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb b/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb index df2b492ae6b..dcca1d388c7 100644 --- a/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb +++ b/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb @@ -21,7 +21,7 @@ describe 'Projects > Show > User sees setup shortcut buttons' do end it 'no Auto DevOps button if can not manage pipelines' do - page.within('.project-stats') do + page.within('.project-buttons') do expect(page).not_to have_link('Enable Auto DevOps') expect(page).not_to have_link('Auto DevOps enabled') end @@ -30,7 +30,7 @@ describe 'Projects > Show > User sees setup shortcut buttons' do it '"Auto DevOps enabled" button not linked' do visit project_path(project) - page.within('.project-stats') do + page.within('.project-buttons') do expect(page).to have_text('Auto DevOps enabled') end end @@ -45,19 +45,19 @@ describe 'Projects > Show > User sees setup shortcut buttons' do end it '"New file" button linked to new file page' do - page.within('.project-stats') do + page.within('.project-buttons') do expect(page).to have_link('New file', href: project_new_blob_path(project, project.default_branch || 'master')) end end - it '"Add Readme" button linked to new file populated for a readme' do - page.within('.project-stats') do - expect(page).to have_link('Add Readme', href: presenter.add_readme_path) + it '"Add README" button linked to new file populated for a README' do + page.within('.project-buttons') do + expect(page).to have_link('Add README', href: presenter.add_readme_path) end end it '"Add license" button linked to new file populated for a license' do - page.within('.project-metadata') do + page.within('.project-stats') do expect(page).to have_link('Add license', href: presenter.add_license_path) end end @@ -67,7 +67,7 @@ describe 'Projects > Show > User sees setup shortcut buttons' do it '"Auto DevOps enabled" anchor linked to settings page' do visit project_path(project) - page.within('.project-stats') do + page.within('.project-buttons') do expect(page).to have_link('Auto DevOps enabled', href: project_settings_ci_cd_path(project, anchor: 'autodevops-settings')) end end @@ -77,7 +77,7 @@ describe 'Projects > Show > User sees setup shortcut buttons' do let(:project) { create(:project, :public, :empty_repo, auto_devops_attributes: { enabled: false }) } it '"Enable Auto DevOps" button linked to settings page' do - page.within('.project-stats') do + page.within('.project-buttons') do expect(page).to have_link('Enable Auto DevOps', href: project_settings_ci_cd_path(project, anchor: 'autodevops-settings')) end end @@ -86,7 +86,7 @@ describe 'Projects > Show > User sees setup shortcut buttons' do describe 'Kubernetes cluster button' do it '"Add Kubernetes cluster" button linked to clusters page' do - page.within('.project-stats') do + page.within('.project-buttons') do expect(page).to have_link('Add Kubernetes cluster', href: new_project_cluster_path(project)) end end @@ -96,7 +96,7 @@ describe 'Projects > Show > User sees setup shortcut buttons' do visit project_path(project) - page.within('.project-stats') do + page.within('.project-buttons') do expect(page).to have_link('Kubernetes configured', href: project_cluster_path(project, cluster)) end end @@ -119,7 +119,7 @@ describe 'Projects > Show > User sees setup shortcut buttons' do it '"Auto DevOps enabled" button not linked' do visit project_path(project) - page.within('.project-stats') do + page.within('.project-buttons') do expect(page).to have_text('Auto DevOps enabled') end end @@ -129,14 +129,14 @@ describe 'Projects > Show > User sees setup shortcut buttons' do let(:project) { create(:project, :public, :repository, auto_devops_attributes: { enabled: false }) } it 'no Auto DevOps button if can not manage pipelines' do - page.within('.project-stats') do + page.within('.project-buttons') do expect(page).not_to have_link('Enable Auto DevOps') expect(page).not_to have_link('Auto DevOps enabled') end end it 'no Kubernetes cluster button if can not manage clusters' do - page.within('.project-stats') do + page.within('.project-buttons') do expect(page).not_to have_link('Add Kubernetes cluster') expect(page).not_to have_link('Kubernetes configured') end @@ -151,59 +151,59 @@ describe 'Projects > Show > User sees setup shortcut buttons' do sign_in(user) end - context 'Readme button' do + context 'README button' do before do allow(Project).to receive(:find_by_full_path) .with(project.full_path, follow_redirects: true) .and_return(project) end - context 'when the project has a populated Readme' do - it 'show the "Readme" anchor' do + context 'when the project has a populated README' do + it 'show the "README" anchor' do visit project_path(project) expect(project.repository.readme).not_to be_nil - page.within('.project-stats') do - expect(page).not_to have_link('Add Readme', href: presenter.add_readme_path) - expect(page).to have_link('Readme', href: presenter.readme_path) + page.within('.project-buttons') do + expect(page).not_to have_link('Add README', href: presenter.add_readme_path) + expect(page).to have_link('README', href: presenter.readme_path) end end - context 'when the project has an empty Readme' do - it 'show the "Readme" anchor' do + context 'when the project has an empty README' do + it 'show the "README" anchor' do allow(project.repository).to receive(:readme).and_return(fake_blob(path: 'README.md', data: '', size: 0)) visit project_path(project) - page.within('.project-stats') do - expect(page).not_to have_link('Add Readme', href: presenter.add_readme_path) - expect(page).to have_link('Readme', href: presenter.readme_path) + page.within('.project-buttons') do + expect(page).not_to have_link('Add README', href: presenter.add_readme_path) + expect(page).to have_link('README', href: presenter.readme_path) end end end end - context 'when the project does not have a Readme' do - it 'shows the "Add Readme" button' do + context 'when the project does not have a README' do + it 'shows the "Add README" button' do allow(project.repository).to receive(:readme).and_return(nil) visit project_path(project) - page.within('.project-stats') do - expect(page).to have_link('Add Readme', href: presenter.add_readme_path) + page.within('.project-buttons') do + expect(page).to have_link('Add README', href: presenter.add_readme_path) end end end end - it 'no "Add Changelog" button if the project already has a changelog' do + it 'no "Add CHANGELOG" button if the project already has a changelog' do visit project_path(project) expect(project.repository.changelog).not_to be_nil - page.within('.project-stats') do - expect(page).not_to have_link('Add Changelog') + page.within('.project-buttons') do + expect(page).not_to have_link('Add CHANGELOG') end end @@ -212,18 +212,18 @@ describe 'Projects > Show > User sees setup shortcut buttons' do expect(project.repository.license_blob).not_to be_nil - page.within('.project-stats') do + page.within('.project-buttons') do expect(page).not_to have_link('Add license') end end - it 'no "Add Contribution guide" button if the project already has a contribution guide' do + it 'no "Add CONTRIBUTING" button if the project already has a contribution guide' do visit project_path(project) expect(project.repository.contribution_guide).not_to be_nil - page.within('.project-stats') do - expect(page).not_to have_link('Add Contribution guide') + page.within('.project-buttons') do + expect(page).not_to have_link('Add CONTRIBUTING') end end @@ -232,7 +232,7 @@ describe 'Projects > Show > User sees setup shortcut buttons' do it 'no "Set up CI/CD" button if the project has Auto DevOps enabled' do visit project_path(project) - page.within('.project-stats') do + page.within('.project-buttons') do expect(page).not_to have_link('Set up CI/CD') end end @@ -246,7 +246,7 @@ describe 'Projects > Show > User sees setup shortcut buttons' do expect(project.repository.gitlab_ci_yml).to be_nil - page.within('.project-stats') do + page.within('.project-buttons') do expect(page).to have_link('Set up CI/CD', href: presenter.add_ci_yml_path) end end @@ -266,7 +266,7 @@ describe 'Projects > Show > User sees setup shortcut buttons' do visit project_path(project) - page.within('.project-stats') do + page.within('.project-buttons') do expect(page).not_to have_link('Set up CI/CD') end end @@ -278,7 +278,7 @@ describe 'Projects > Show > User sees setup shortcut buttons' do it '"Auto DevOps enabled" anchor linked to settings page' do visit project_path(project) - page.within('.project-stats') do + page.within('.project-buttons') do expect(page).to have_link('Auto DevOps enabled', href: project_settings_ci_cd_path(project, anchor: 'autodevops-settings')) end end @@ -290,7 +290,7 @@ describe 'Projects > Show > User sees setup shortcut buttons' do it '"Enable Auto DevOps" button linked to settings page' do visit project_path(project) - page.within('.project-stats') do + page.within('.project-buttons') do expect(page).to have_link('Enable Auto DevOps', href: project_settings_ci_cd_path(project, anchor: 'autodevops-settings')) end end @@ -302,7 +302,7 @@ describe 'Projects > Show > User sees setup shortcut buttons' do expect(page).to have_selector('.js-autodevops-banner') - page.within('.project-stats') do + page.within('.project-buttons') do expect(page).not_to have_link('Enable Auto DevOps') expect(page).not_to have_link('Auto DevOps enabled') end @@ -323,7 +323,7 @@ describe 'Projects > Show > User sees setup shortcut buttons' do visit project_path(project) - page.within('.project-stats') do + page.within('.project-buttons') do expect(page).not_to have_link('Enable Auto DevOps') expect(page).not_to have_link('Auto DevOps enabled') end @@ -335,7 +335,7 @@ describe 'Projects > Show > User sees setup shortcut buttons' do it '"Add Kubernetes cluster" button linked to clusters page' do visit project_path(project) - page.within('.project-stats') do + page.within('.project-buttons') do expect(page).to have_link('Add Kubernetes cluster', href: new_project_cluster_path(project)) end end @@ -345,7 +345,7 @@ describe 'Projects > Show > User sees setup shortcut buttons' do visit project_path(project) - page.within('.project-stats') do + page.within('.project-buttons') do expect(page).to have_link('Kubernetes configured', href: project_cluster_path(project, cluster)) end end diff --git a/spec/features/tags/master_views_tags_spec.rb b/spec/features/tags/master_views_tags_spec.rb index 3f4fe549f3e..36cfeb5ed84 100644 --- a/spec/features/tags/master_views_tags_spec.rb +++ b/spec/features/tags/master_views_tags_spec.rb @@ -13,7 +13,7 @@ describe 'Maintainer views tags' do before do visit project_path(project) - click_on 'Add Readme' + click_on 'Add README' fill_in :commit_message, with: 'Add a README file', visible: true click_button 'Commit changes' visit project_tags_path(project) diff --git a/spec/presenters/project_presenter_spec.rb b/spec/presenters/project_presenter_spec.rb index 7b0192fa9c8..456de5f1b9a 100644 --- a/spec/presenters/project_presenter_spec.rb +++ b/spec/presenters/project_presenter_spec.rb @@ -165,32 +165,32 @@ describe ProjectPresenter do describe '#files_anchor_data' do it 'returns files data' do - expect(presenter.files_anchor_data).to have_attributes(enabled: true, - label: 'Files (0 Bytes)', + expect(presenter.files_anchor_data).to have_attributes(is_link: true, + label: a_string_including('0 Bytes'), link: nil) end end describe '#commits_anchor_data' do it 'returns commits data' do - expect(presenter.commits_anchor_data).to have_attributes(enabled: true, - label: 'Commits (0)', + expect(presenter.commits_anchor_data).to have_attributes(is_link: true, + label: a_string_including('0'), link: nil) end end describe '#branches_anchor_data' do it 'returns branches data' do - expect(presenter.branches_anchor_data).to have_attributes(enabled: true, - label: "Branches (0)", + expect(presenter.branches_anchor_data).to have_attributes(is_link: true, + label: a_string_including('0'), link: nil) end end describe '#tags_anchor_data' do it 'returns tags data' do - expect(presenter.tags_anchor_data).to have_attributes(enabled: true, - label: "Tags (0)", + expect(presenter.tags_anchor_data).to have_attributes(is_link: true, + label: a_string_including('0'), link: nil) end end @@ -202,32 +202,32 @@ describe ProjectPresenter do describe '#files_anchor_data' do it 'returns files data' do - expect(presenter.files_anchor_data).to have_attributes(enabled: true, - label: 'Files (0 Bytes)', + expect(presenter.files_anchor_data).to have_attributes(is_link: true, + label: a_string_including('0 Bytes'), link: presenter.project_tree_path(project)) end end describe '#commits_anchor_data' do it 'returns commits data' do - expect(presenter.commits_anchor_data).to have_attributes(enabled: true, - label: 'Commits (0)', + expect(presenter.commits_anchor_data).to have_attributes(is_link: true, + label: a_string_including('0'), link: presenter.project_commits_path(project, project.repository.root_ref)) end end describe '#branches_anchor_data' do it 'returns branches data' do - expect(presenter.branches_anchor_data).to have_attributes(enabled: true, - label: "Branches (#{project.repository.branches.size})", + expect(presenter.branches_anchor_data).to have_attributes(is_link: true, + label: a_string_including("#{project.repository.branches.size}"), link: presenter.project_branches_path(project)) end end describe '#tags_anchor_data' do it 'returns tags data' do - expect(presenter.tags_anchor_data).to have_attributes(enabled: true, - label: "Tags (#{project.repository.tags.size})", + expect(presenter.tags_anchor_data).to have_attributes(is_link: true, + label: a_string_including("#{project.repository.tags.size}"), link: presenter.project_tags_path(project)) end end @@ -236,8 +236,8 @@ describe ProjectPresenter do it 'returns new file data if user can push' do project.add_developer(user) - expect(presenter.new_file_anchor_data).to have_attributes(enabled: false, - label: "New file", + expect(presenter.new_file_anchor_data).to have_attributes(is_link: false, + label: a_string_including("New file"), link: presenter.project_new_blob_path(project, 'master'), class_modifier: 'success') end @@ -264,8 +264,8 @@ describe ProjectPresenter do project.add_developer(user) allow(project.repository).to receive(:readme).and_return(nil) - expect(presenter.readme_anchor_data).to have_attributes(enabled: false, - label: 'Add Readme', + expect(presenter.readme_anchor_data).to have_attributes(is_link: false, + label: a_string_including('Add README'), link: presenter.add_readme_path) end end @@ -274,21 +274,21 @@ describe ProjectPresenter do it 'returns anchor data' do allow(project.repository).to receive(:readme).and_return(double(name: 'readme')) - expect(presenter.readme_anchor_data).to have_attributes(enabled: true, - label: 'Readme', + expect(presenter.readme_anchor_data).to have_attributes(is_link: false, + label: a_string_including('README'), link: presenter.readme_path) end end end describe '#changelog_anchor_data' do - context 'when user can push and CHANGELOG does not exists' do + context 'when user can push and CHANGELOG does not exist' do it 'returns anchor data' do project.add_developer(user) allow(project.repository).to receive(:changelog).and_return(nil) - expect(presenter.changelog_anchor_data).to have_attributes(enabled: false, - label: 'Add Changelog', + expect(presenter.changelog_anchor_data).to have_attributes(is_link: false, + label: a_string_including('Add CHANGELOG'), link: presenter.add_changelog_path) end end @@ -297,21 +297,21 @@ describe ProjectPresenter do it 'returns anchor data' do allow(project.repository).to receive(:changelog).and_return(double(name: 'foo')) - expect(presenter.changelog_anchor_data).to have_attributes(enabled: true, - label: 'Changelog', + expect(presenter.changelog_anchor_data).to have_attributes(is_link: false, + label: a_string_including('CHANGELOG'), link: presenter.changelog_path) end end end describe '#license_anchor_data' do - context 'when user can push and LICENSE does not exists' do + context 'when user can push and LICENSE does not exist' do it 'returns anchor data' do project.add_developer(user) allow(project.repository).to receive(:license_blob).and_return(nil) - expect(presenter.license_anchor_data).to have_attributes(enabled: false, - label: 'Add license', + expect(presenter.license_anchor_data).to have_attributes(is_link: true, + label: a_string_including('Add license'), link: presenter.add_license_path) end end @@ -320,21 +320,21 @@ describe ProjectPresenter do it 'returns anchor data' do allow(project.repository).to receive(:license_blob).and_return(double(name: 'foo')) - expect(presenter.license_anchor_data).to have_attributes(enabled: true, - label: presenter.license_short_name, + expect(presenter.license_anchor_data).to have_attributes(is_link: true, + label: a_string_including(presenter.license_short_name), link: presenter.license_path) end end end describe '#contribution_guide_anchor_data' do - context 'when user can push and CONTRIBUTING does not exists' do + context 'when user can push and CONTRIBUTING does not exist' do it 'returns anchor data' do project.add_developer(user) allow(project.repository).to receive(:contribution_guide).and_return(nil) - expect(presenter.contribution_guide_anchor_data).to have_attributes(enabled: false, - label: 'Add Contribution guide', + expect(presenter.contribution_guide_anchor_data).to have_attributes(is_link: false, + label: a_string_including('Add CONTRIBUTING'), link: presenter.add_contribution_guide_path) end end @@ -343,8 +343,8 @@ describe ProjectPresenter do it 'returns anchor data' do allow(project.repository).to receive(:contribution_guide).and_return(double(name: 'foo')) - expect(presenter.contribution_guide_anchor_data).to have_attributes(enabled: true, - label: 'Contribution guide', + expect(presenter.contribution_guide_anchor_data).to have_attributes(is_link: false, + label: a_string_including('CONTRIBUTING'), link: presenter.contribution_guide_path) end end @@ -355,20 +355,20 @@ describe ProjectPresenter do it 'returns anchor data' do allow(project).to receive(:auto_devops_enabled?).and_return(true) - expect(presenter.autodevops_anchor_data).to have_attributes(enabled: true, - label: 'Auto DevOps enabled', + expect(presenter.autodevops_anchor_data).to have_attributes(is_link: false, + label: a_string_including('Auto DevOps enabled'), link: nil) end end - context 'when user can admin pipeline and CI yml does not exists' do + context 'when user can admin pipeline and CI yml does not exist' do it 'returns anchor data' do project.add_maintainer(user) allow(project).to receive(:auto_devops_enabled?).and_return(false) allow(project.repository).to receive(:gitlab_ci_yml).and_return(nil) - expect(presenter.autodevops_anchor_data).to have_attributes(enabled: false, - label: 'Enable Auto DevOps', + expect(presenter.autodevops_anchor_data).to have_attributes(is_link: false, + label: a_string_including('Enable Auto DevOps'), link: presenter.project_settings_ci_cd_path(project, anchor: 'autodevops-settings')) end end @@ -380,8 +380,8 @@ describe ProjectPresenter do project.add_maintainer(user) cluster = create(:cluster, projects: [project]) - expect(presenter.kubernetes_cluster_anchor_data).to have_attributes(enabled: true, - label: 'Kubernetes configured', + expect(presenter.kubernetes_cluster_anchor_data).to have_attributes(is_link: false, + label: a_string_including('Kubernetes configured'), link: presenter.project_cluster_path(project, cluster)) end @@ -390,16 +390,16 @@ describe ProjectPresenter do create(:cluster, :production_environment, projects: [project]) create(:cluster, projects: [project]) - expect(presenter.kubernetes_cluster_anchor_data).to have_attributes(enabled: true, - label: 'Kubernetes configured', + expect(presenter.kubernetes_cluster_anchor_data).to have_attributes(is_link: false, + label: a_string_including('Kubernetes configured'), link: presenter.project_clusters_path(project)) end it 'returns link to create a cluster if no cluster exists' do project.add_maintainer(user) - expect(presenter.kubernetes_cluster_anchor_data).to have_attributes(enabled: false, - label: 'Add Kubernetes cluster', + expect(presenter.kubernetes_cluster_anchor_data).to have_attributes(is_link: false, + label: a_string_including('Add Kubernetes cluster'), link: presenter.new_project_cluster_path(project)) end end diff --git a/spec/views/projects/_home_panel.html.haml_spec.rb b/spec/views/projects/_home_panel.html.haml_spec.rb index fc1fe5739c3..006c93686d5 100644 --- a/spec/views/projects/_home_panel.html.haml_spec.rb +++ b/spec/views/projects/_home_panel.html.haml_spec.rb @@ -23,7 +23,7 @@ describe 'projects/_home_panel' do it 'makes it possible to set notification level' do render - expect(view).to render_template('shared/notifications/_button') + expect(view).to render_template('projects/buttons/_notifications') expect(rendered).to have_selector('.notification-dropdown') end end -- cgit v1.2.1