Commit 772e482b authored by Phil Hughes's avatar Phil Hughes

Merge branch '44704-improve-project-overview-ui' into 'master'

Resolve "Improve project overview UI"

Closes #44704

See merge request gitlab-org/gitlab-ce!20536
parents d32cec18 ec4ad656
......@@ -13,40 +13,52 @@ export default class Project {
constructor() {
const $cloneOptions = $('ul.clone-options-dropdown');
const $projectCloneField = $('#project_clone');
const $cloneBtnText = $('a.clone-dropdown-btn span');
const $cloneBtnLabel = $('.js-git-clone-holder .js-clone-dropdown-label');
const selectedCloneOption = $cloneBtnText.text().trim();
const selectedCloneOption = $cloneBtnLabel.text().trim();
if (selectedCloneOption.length > 0) {
$(`a:contains('${selectedCloneOption}')`, $cloneOptions).addClass('is-active');
}
$('a', $cloneOptions).on('click', (e) => {
$('a', $cloneOptions).on('click', e => {
e.preventDefault();
const $this = $(e.currentTarget);
const url = $this.attr('href');
const activeText = $this.find('.dropdown-menu-inner-title').text();
const cloneType = $this.data('cloneType');
e.preventDefault();
$('.is-active', $cloneOptions).removeClass('is-active');
$(`a[data-clone-type="${cloneType}"]`).each(function() {
const $el = $(this);
const activeText = $el.find('.dropdown-menu-inner-title').text();
const $container = $el.closest('.project-clone-holder');
const $label = $container.find('.js-clone-dropdown-label');
$('.is-active', $cloneOptions).not($this).removeClass('is-active');
$this.toggleClass('is-active');
$projectCloneField.val(url);
$cloneBtnText.text(activeText);
$el.toggleClass('is-active');
$label.text(activeText);
});
return $('.clone').text(url);
$projectCloneField.val(url);
$('.js-git-empty .js-clone').text(url);
});
// Ref switcher
Project.initRefSwitcher();
$('.project-refs-select').on('change', function() {
return $(this).parents('form').submit();
return $(this)
.parents('form')
.submit();
});
$('.hide-no-ssh-message').on('click', function(e) {
Cookies.set('hide_no_ssh_message', 'false');
$(this).parents('.no-ssh-key-message').remove();
$(this)
.parents('.no-ssh-key-message')
.remove();
return e.preventDefault();
});
$('.hide-no-password-message').on('click', function(e) {
Cookies.set('hide_no_password_message', 'false');
$(this).parents('.no-password-message').remove();
$(this)
.parents('.no-password-message')
.remove();
return e.preventDefault();
});
Project.projectSelectDropdown();
......@@ -58,7 +70,7 @@ export default class Project {
}
static changeProject(url) {
return window.location = url;
return (window.location = url);
}
static initRefSwitcher() {
......@@ -73,14 +85,15 @@ export default class Project {
selected = $dropdown.data('selected');
return $dropdown.glDropdown({
data(term, callback) {
axios.get($dropdown.data('refsUrl'), {
params: {
ref: $dropdown.data('ref'),
search: term,
},
})
.then(({ data }) => callback(data))
.catch(() => flash(__('An error occurred while getting projects')));
axios
.get($dropdown.data('refsUrl'), {
params: {
ref: $dropdown.data('ref'),
search: term,
},
})
.then(({ data }) => callback(data))
.catch(() => flash(__('An error occurred while getting projects')));
},
selectable: true,
filterable: true,
......
......@@ -8,15 +8,18 @@ import BlobViewer from '~/blob/viewer/index';
import Activities from '~/activities';
import { ajaxGet } from '~/lib/utils/common_utils';
import GpgBadges from '~/gpg_badges';
import initReadMore from '~/read_more';
import Star from '../../../star';
import notificationsDropdown from '../../../notifications_dropdown';
document.addEventListener('DOMContentLoaded', () => {
initReadMore();
new Star(); // eslint-disable-line no-new
notificationsDropdown();
new ShortcutsNavigation(); // eslint-disable-line no-new
new NotificationsForm(); // eslint-disable-line no-new
new UserCallout({ // eslint-disable-line no-new
// eslint-disable-next-line no-new
new UserCallout({
setCalloutPerProject: false,
className: 'js-autodevops-banner',
});
......
/**
* ReadMore
*
* Adds "read more" functionality to elements.
*
* Specifically, it looks for a trigger, by default ".js-read-more-trigger", and adds the class
* "is-expanded" to the previous element in order to provide a click to expand functionality.
*
* This is useful for long text elements that you would like to truncate, especially for mobile.
*
* Example Markup
* <div class="read-more-container">
* <p>Some text that should be long enough to have to truncate within a specified container.</p>
* <p>This text will not appear in the container, as only the first line can be truncated.</p>
* <p>This should also not appear, if everything is working correctly!</p>
* </div>
* <button class="js-read-more-trigger">Read more</button>
*
*/
export default function initReadMore(triggerSelector = '.js-read-more-trigger') {
const triggerEls = document.querySelectorAll(triggerSelector);
if (!triggerEls) return;
triggerEls.forEach(triggerEl => {
const targetEl = triggerEl.previousElementSibling;
if (!targetEl) {
return;
}
triggerEl.addEventListener(
'click',
e => {
targetEl.classList.add('is-expanded');
e.target.remove();
},
{ once: true },
);
});
}
......@@ -64,3 +64,4 @@
@import 'framework/ci_variable_list';
@import 'framework/feature_highlight';
@import 'framework/terms';
@import 'framework/read_more';
......@@ -44,12 +44,8 @@
.project-repo-buttons {
display: block;
.count-buttons .btn {
margin: 0 10px;
}
.count-buttons .count-with-arrow {
display: none;
.count-buttons .count-badge {
margin-top: $gl-padding-8;
}
}
}
......
.read-more-container {
@include media-breakpoint-down(md) {
&:not(.is-expanded) {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
> * {
display: inline;
}
}
}
}
......@@ -271,6 +271,7 @@ $performance-bar-height: 35px;
$flash-height: 52px;
$context-header-height: 60px;
$breadcrumb-min-height: 48px;
$project-title-row-height: 24px;
/*
* Common component specific colors
......
......@@ -115,7 +115,7 @@
.project-feature-controls {
display: flex;
align-items: center;
margin: 8px 0;
margin: $gl-padding-8 0;
max-width: 432px;
.toggle-wrapper {
......@@ -144,12 +144,8 @@
.group-home-panel {
padding-top: 24px;
padding-bottom: 24px;
border-bottom: 1px solid $border-color;
@include media-breakpoint-up(sm) {
border-bottom: 1px solid $border-color;
}
.project-avatar,
.group-avatar {
float: none;
margin: 0 auto;
......@@ -175,7 +171,6 @@
}
}
.project-home-desc,
.group-home-desc {
margin-left: auto;
margin-right: auto;
......@@ -199,6 +194,62 @@
}
}
.project-home-panel {
padding-top: $gl-padding-8;
padding-bottom: $gl-padding-24;
.project-title-row {
margin-right: $gl-padding-8;
}
.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;
}
.project-title {
font-size: 20px;
line-height: $project-title-row-height;
font-weight: bold;
}
.project-metadata {
font-weight: normal;
font-size: 14px;
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 {
line-height: 0;
border-width: 0;
}
}
.project-tag-list,
.project-license {
.icon {
position: relative;
top: 2px;
}
}
}
}
.nav > .project-repo-buttons {
margin-top: 0;
}
......@@ -206,8 +257,6 @@
.project-repo-buttons,
.group-buttons {
.btn {
padding: 3px 10px;
&:last-child {
margin-left: 0;
}
......@@ -222,11 +271,15 @@
.fa-caret-down {
margin-left: 3px;
&.dropdown-btn-icon {
margin-left: 0;
}
}
}
.project-action-button {
margin: 15px 5px 0;
margin: $gl-padding $gl-padding-8 0 0;
vertical-align: top;
}
......@@ -243,82 +296,45 @@
.count-buttons {
display: inline-block;
vertical-align: top;
margin-top: 15px;
}
margin-top: $gl-padding;
.project-clone-holder {
display: inline-block;
margin: 15px 5px 0 0;
.count-badge {
height: $input-height;
input {
height: 28px;
.icon {
top: -1px;
}
}
}
.count-with-arrow {
display: inline-block;
position: relative;
margin-left: 4px;
.count-badge-count,
.count-badge-button {
border: 1px solid $border-color;
line-height: 1;
}
.arrow {
&::before {
content: '';
display: inline-block;
position: absolute;
width: 0;
height: 0;
border-color: transparent;
border-style: solid;
top: 50%;
left: 0;
margin-top: -6px;
border-width: 7px 5px 7px 0;
border-right-color: $count-arrow-border;
pointer-events: none;
}
.count,
.count-badge-button {
color: $gl-text-color;
}
&::after {
content: '';
position: absolute;
width: 0;
height: 0;
border-color: transparent;
border-style: solid;
top: 50%;
left: 1px;
margin-top: -9px;
border-width: 10px 7px 10px 0;
border-right-color: $white-light;
pointer-events: none;
}
.count-badge-count {
padding: 0 12px;
border-right: 0;
border-radius: $border-radius-base 0 0 $border-radius-base;
background: $gray-light;
}
.count {
@include btn-white;
display: inline-block;
background: $white-light;
border-radius: 2px;
border-width: 1px;
border-style: solid;
font-size: 13px;
font-weight: $gl-font-weight-bold;
line-height: 13px;
letter-spacing: 0.4px;
padding: 6px 14px;
text-align: center;
vertical-align: middle;
touch-action: manipulation;
background-image: none;
white-space: nowrap;
margin: 0 10px 0 4px;
.count-badge-button {
border-radius: 0 $border-radius-base $border-radius-base 0;
}
}
a {
color: inherit;
}
.project-clone-holder {
display: inline-block;
margin: $gl-padding $gl-padding-8 0 0;
&:hover {
background: $white-light;
}
input {
height: $input-height;
}
}
......@@ -333,6 +349,14 @@
min-width: 320px;
}
}
.mobile-git-clone {
margin-top: $gl-padding-8;
.dropdown-menu-inner-content {
@extend .monospace;
}
}
}
.split-one {
......@@ -511,7 +535,6 @@
.controls {
margin-left: auto;
}
}
.choose-template {
......@@ -574,7 +597,7 @@
flex-wrap: wrap;
.btn {
padding: 8px;
padding: $gl-padding-8;
margin-right: 10px;
}
......@@ -651,7 +674,7 @@
left: -10px;
top: 50%;
z-index: 10;
padding: 8px 0;
padding: $gl-padding-8 0;
text-align: center;
background-color: $white-light;
color: $gl-text-color-tertiary;
......@@ -665,7 +688,7 @@
left: 50%;
top: 0;
transform: translateX(-50%);
padding: 0 8px;
padding: 0 $gl-padding-8;
}
}
......@@ -699,17 +722,51 @@
.project-stats {
font-size: 0;
text-align: center;
max-width: 100%;
border-bottom: 1px solid $border-color;
.nav {
margin-top: $gl-padding-8;
margin-bottom: $gl-padding-8;
.scrolling-tabs-container {
.scrolling-tabs {
margin-top: $gl-padding-8;
margin-bottom: $gl-padding-8;
flex-wrap: wrap;
border-bottom: 0;
}
.fade-left,
.fade-right {
top: 0;
height: 100%;
.fa {
top: 50%;
margin-top: -$gl-padding-8;
}
}
.nav {
flex-basis: 100%;
+ .nav {
margin: $gl-padding-8 0;
}
}
@include media-breakpoint-down(md) {
flex-direction: column;
.nav {
flex-wrap: nowrap;
}
.nav:first-child {
margin-right: $gl-padding-8;
}
}
}
.nav {
> li {
display: inline-block;
margin-top: $gl-padding-4;
margin-bottom: $gl-padding-4;
&:not(:last-child) {
margin-right: $gl-padding;
......@@ -732,13 +789,17 @@
font-size: $gl-font-size;
line-height: $gl-btn-line-height;
color: $gl-text-color-secondary;
white-space: nowrap;
}
.stat-link {
border-bottom: 0;
&:hover,
&:focus {
color: $gl-text-color;
text-decoration: underline;
border-bottom: 0;
}
}
......@@ -868,7 +929,7 @@ pre.light-well {
}
.git-clone-holder {
width: 380px;
width: 320px;
.btn-clipboard {
border: 1px solid $border-color;
......
......@@ -61,7 +61,7 @@ module ButtonHelper
dropdown_description = http_dropdown_description(protocol)
append_url = project.http_url_to_repo if append_link
dropdown_item_with_description(protocol, dropdown_description, href: append_url)
dropdown_item_with_description(protocol, dropdown_description, href: append_url, data: { clone_type: 'http' })
end
def http_dropdown_description(protocol)
......@@ -80,16 +80,17 @@ module ButtonHelper
append_url = project.ssh_url_to_repo if append_link
dropdown_item_with_description('SSH', dropdown_description, href: append_url)
dropdown_item_with_description('SSH', dropdown_description, href: append_url, data: { clone_type: 'ssh' })
end
def dropdown_item_with_description(title, description, href: nil)
def dropdown_item_with_description(title, description, href: nil, data: nil)
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",
href: (href if href)
href: (href if href),
data: (data if data)
end
end
......@@ -86,7 +86,7 @@ module IconsHelper
end
end
def visibility_level_icon(level, fw: true)
def visibility_level_icon(level, fw: true, options: {})
name =
case level
when Gitlab::VisibilityLevel::PRIVATE
......@@ -99,7 +99,7 @@ module IconsHelper
name << " fw" if fw
icon(name)
icon(name, options)
end
def file_type_icon_class(type, mode, name)
......
......@@ -351,6 +351,10 @@ module ProjectsHelper
end
end
def default_clone_label
_("Copy %{protocol} clone URL") % { protocol: default_clone_protocol.upcase }
end
def default_clone_protocol
if allowed_protocols_present?
enabled_protocol
......
......@@ -138,7 +138,7 @@ module VisibilityLevelHelper
end
def project_visibility_icon_description(level)
"#{visibility_level_label(level)} - #{project_visibility_level_description(level)}"
"#{project_visibility_level_description(level)}"
end
def visibility_level_label(level)
......
......@@ -11,16 +11,18 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
presents :project
AnchorData = Struct.new(:enabled, :label, :link, :class_modifier)
MAX_TAGS_TO_SHOW = 3
def statistics_anchors(show_auto_devops_callout:)
[
readme_anchor_data,
changelog_anchor_data,
contribution_guide_anchor_data,
files_anchor_data,
commits_anchor_data,
branches_anchor_data,
tags_anchor_data,
readme_anchor_data,
changelog_anchor_data,
license_anchor_data,
contribution_guide_anchor_data,
gitlab_ci_anchor_data,
autodevops_anchor_data(show_auto_devops_callout: show_auto_devops_callout),
kubernetes_cluster_anchor_data
......@@ -31,7 +33,6 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
[
readme_anchor_data,
changelog_anchor_data,
license_anchor_data,
contribution_guide_anchor_data,
autodevops_anchor_data(show_auto_devops_callout: show_auto_devops_callout),
kubernetes_cluster_anchor_data,
......@@ -42,6 +43,10 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
def empty_repo_statistics_anchors
[
files_anchor_data,
commits_anchor_data,
branches_anchor_data,
tags_anchor_data,
autodevops_anchor_data,
kubernetes_cluster_anchor_data
].compact.select { |item| item.enabled }
......@@ -51,7 +56,6 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
[
new_file_anchor_data,
readme_anchor_data,
license_anchor_data,
autodevops_anchor_data,
kubernetes_cluster_anchor_data
].compact.reject { |item| item.enabled }
......@@ -182,95 +186,101 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
end
def files_anchor_data
OpenStruct.new(enabled: true,
label: _('Files (%{human_size})') % { human_size: storage_counter(statistics.total_repository_size) },
link: project_tree_path(project))
AnchorData.new(true,
_('Files (%{human_size})') % { human_size: storage_counter(statistics.total_repository_size) },
empty_repo? ? nil : project_tree_path(project))
end
def commits_anchor_data
OpenStruct.new(enabled: true,
label: n_('Commit (%{commit_count})', 'Commits (%{commit_count})', statistics.commit_count) % { commit_count: number_with_delimiter(statistics.commit_count) },
link: project_commits_path(project, repository.root_ref))
AnchorData.new(true,
n_('Commit (%{commit_count})', 'Commits (%{commit_count})', statistics.commit_count) % { commit_count: number_with_delimiter(statistics.commit_count) },
empty_repo? ? nil : project_commits_path(project, repository.root_ref))
end
def branches_anchor_data
OpenStruct.new(enabled: true,
label: n_('Branch (%{branch_count})', 'Branches (%{branch_count})', repository.branch_count) % { branch_count: number_with_delimiter(repository.branch_count) },
link: project_branches_path(project))
AnchorData.new(true,
n_('Branch (%{branch_count})', 'Branches (%{branch_count})', repository.branch_count) % { branch_count: number_with_delimiter(repository.branch_count) },
empty_repo? ? nil : project_branches_path(project))
end
def tags_anchor_data
OpenStruct.new(enabled: true,
label: n_('Tag (%{tag_count})', 'Tags (%{tag_count})', repository.tag_count) % { tag_count: number_with_delimiter(repository.tag_count) },
link: project_tags_path(project))
AnchorData.new(true,
n_('Tag (%{tag_count})', 'Tags (%{tag_count})', repository.tag_count) % { tag_count: number_with_delimiter(repository.tag_count) },
empty_repo? ? nil : project_tags_path(project))
end
def new_file_anchor_data
if current_user && can_current_user_push_to_default_branch?
OpenStruct.new(enabled: false,
label: _('New file'),
link: project_new_blob_path(project, default_branch || 'master'),
class_modifier: 'new')
AnchorData.new(false,
_('New file'),
project_new_blob_path(project, default_branch || 'master'),
'new')
end
end
def readme_anchor_data
if current_user && can_current_user_push_to_default_branch? && repository.readme.nil?
OpenStruct.new(enabled: false,
label: _('Add Readme'),
link: add_readme_path)
AnchorData.new(false,
_('Add Readme'),
add_readme_path)
elsif repository.readme
OpenStruct.new(enabled: true,
label: _('Readme'),
link: default_view != 'readme' ? readme_path : '#readme')
AnchorData.new(true,
_('Readme'),
default_view != 'readme' ? readme_path : '#readme')
end
end
def changelog_anchor_data
if current_user && can_current_user_push_to_default_branch? && repository.changelog.blank?
OpenStruct.new(enabled: false,
label: _('Add Changelog'),
link: add_changelog_path)
AnchorData.new(false,
_('Add Changelog'),
add_changelog_path)
elsif repository.changelog.present?
OpenStruct.new(enabled: true,
label: _('Changelog'),
link: changelog_path)
AnchorData.new(true,
_('Changelog'),
changelog_path)
end
end
def license_anchor_data
if current_user && can_current_user_push_to_default_branch? && repository.license_blob.blank?
OpenStruct.new(enabled: false,
label: _('Add License'),
link: add_license_path)
elsif repository.license_blob.present?
OpenStruct.new(enabled: true,
label: license_short_name,
link: license_path)
if repository.license_blob.present?
AnchorData.new(true,
license_short_name,
license_path)
else
if current_user && can_current_user_push_to_default_branch?
AnchorData.new(false,
_('Add license'),
add_license_path)
else
AnchorData.new(false,
_('No license. All rights reserved'),
nil)
end
end
end
def contribution_guide_anchor_data
if current_user && can_current_user_push_to_default_branch? && repository.contribution_guide.blank?
OpenStruct.new(enabled: false,
label: _('Add Contribution guide'),
link: add_contribution_guide_path)
AnchorData.new(false,
_('Add Contribution guide'),
add_contribution_guide_path)
elsif repository.contribution_guide.present?
OpenStruct.new(enabled: true,
label: _('Contribution guide'),
link: contribution_guide_path)
AnchorData.new(true,
_('Contribution guide'),
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
OpenStruct.new(enabled: auto_devops_enabled?,
label: auto_devops_enabled? ? _('Auto DevOps enabled') : _('Enable Auto DevOps'),
link: project_settings_ci_cd_path(project, anchor: 'autodevops-settings'))
AnchorData.new(auto_devops_enabled?,
auto_devops_enabled? ? _('Auto DevOps enabled') : _('Enable Auto DevOps'),
project_settings_ci_cd_path(project, anchor: 'autodevops-settings'))
elsif auto_devops_enabled?
OpenStruct.new(enabled: true,
label: _('Auto DevOps enabled'),
link: nil)
AnchorData.new(true,
_('Auto DevOps enabled'),
nil)
end
end
......@@ -282,32 +292,48 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
cluster_link = new_project_cluster_path(project)
end
OpenStruct.new(enabled: !clusters.empty?,
label: clusters.empty? ? _('Add Kubernetes cluster') : _('Kubernetes configured'),
link: cluster_link)
AnchorData.new(!clusters.empty?,
clusters.empty? ? _('Add Kubernetes cluster') : _('Kubernetes configured'),
cluster_link)
end
end
def gitlab_ci_anchor_data
if current_user && can_current_user_push_code? && repository.gitlab_ci_yml.blank? && !auto_devops_enabled?
OpenStruct.new(enabled: false,
label: _('Set up CI/CD'),
link: add_ci_yml_path)
AnchorData.new(false,
_('Set up CI/CD'),
add_ci_yml_path)
elsif repository.gitlab_ci_yml.present?
OpenStruct.new(enabled: true,
label: _('CI/CD configuration'),
link: ci_configuration_path)
AnchorData.new(true,
_('CI/CD configuration'),
ci_configuration_path)
end
end
def koding_anchor_data
if current_user && can_current_user_push_code? && koding_enabled? && repository.koding_yml.blank?
OpenStruct.new(enabled: false,
label: _('Set up Koding'),
link: add_koding_stack_path)
AnchorData.new(false,
_('Set up Koding'),
add_koding_stack_path)
end
end
def tags_to_show
project.tag_list.take(MAX_TAGS_TO_SHOW)
end
def count_of_extra_tags_not_shown
if project.tag_list.count > MAX_TAGS_TO_SHOW
project.tag_list.count - MAX_TAGS_TO_SHOW
else
0
end
end
def has_extra_tags?
count_of_extra_tags_not_shown > 0
end
private
def filename_path(filename)
......
- empty_repo = @project.empty_repo?
.project-home-panel.text-center{ class: ("empty-project" if empty_repo) }
- license = @project.license_anchor_data
.project-home-panel{ class: ("empty-project" if empty_repo) }
.limit-container-width{ class: container_class }
.avatar-container.s70.project-avatar
= project_icon(@project, alt: @project.name, class: 'avatar s70 avatar-tile', width: 70, height: 70)
%h1.project-title.qa-project-name
= @project.name
%span.visibility-icon.has-tooltip{ data: { container: 'body' }, title: visibility_icon_description(@project) }
= visibility_level_icon(@project.visibility_level, fw: false)
.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')
%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-home-desc
- if @project.description.present?
= markdown_field(@project, :description)
.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 }
......@@ -25,34 +44,42 @@
- deleted_message = s_('ForkedFromProjectPath|Forked from %{project_name} (deleted)')
= deleted_message % { project_name: fork_source_name(@project) }
.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-repo-buttons
.count-buttons
- 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.d-inline-flex.flex-wrap
.count-buttons.d-inline-flex
= render 'projects/buttons/star'
= render 'projects/buttons/fork'
%span.d-none.d-sm-inline
- if can?(current_user, :download_code, @project)
.project-clone-holder
= render "shared/clone_panel"
- if can?(current_user, :download_code, @project)
.project-clone-holder.d-inline-flex.d-sm-none
= render "shared/mobile_clone_panel"
- if show_xcode_link?(@project)
.project-action-button.project-xcode.inline
= render "projects/buttons/xcode_link"
.project-clone-holder.d-none.d-sm-inline-flex
= render "shared/clone_panel"
- if current_user
- if can?(current_user, :download_code, @project)
- if show_xcode_link?(@project)
.project-action-button.project-xcode.inline
= render "projects/buttons/xcode_link"
- 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'
.d-none.d-sm-inline-flex
= render 'projects/buttons/koding'
.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
- anchors = local_assigns.fetch(:anchors, [])
- return unless anchors.any?
%ul.nav.justify-content-center
%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
......
- unless @project.empty_repo?
- if current_user && can?(current_user, :fork_project, @project)
- 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: _('Go to your fork'), class: 'btn has-tooltip' do
= custom_icon('icon_fork')
%span= s_('GoToYourFork|Fork')
- else
- can_create_fork = current_user.can?(:create_fork)
= link_to new_project_fork_path(@project),
class: "btn btn-default #{'has-tooltip disabled' unless can_create_fork}",
title: (_('You have reached your project limit') unless can_create_fork) do
= custom_icon('icon_fork')
%span= s_('CreateNewFork|Fork')
.count-with-arrow
%span.arrow
= link_to project_forks_path(@project), title: n_('Fork', 'Forks', @project.forks_count), class: 'count' do
= @project.forks_count
.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' })
%span= s_('ProjectOverview|Fork')
- else
- can_create_fork = current_user.can?(:create_fork)
= link_to new_project_fork_path(@project),
class: "btn btn-default has-tooltip count-badge-button d-flex align-items-center fork-btn #{'has-tooltip disabled' unless can_create_fork}",
title: (s_('ProjectOverview|You have reached your project limit') unless can_create_fork) do
= sprite_icon('fork', { css_class: 'icon' })
%span= s_('ProjectOverview|Fork')
- if current_user
%button.btn.btn-default.star-btn.toggle-star{ type: "button", data: { endpoint: toggle_star_project_path(@project, :json) } }>
- if current_user.starred?(@project)
= sprite_icon('star')
%span.starred= _('Unstar')
- else
= sprite_icon('star-o')
%span= s_('StarProject|Star')
.count-with-arrow
%span.arrow
%span.count.star-count
.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) } }
- 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')
- else
= link_to new_user_session_path, class: 'btn has-tooltip star-btn', title: _('You must sign in to star a project') do
= sprite_icon('star')
#{ s_('StarProject|Star') }
.count-with-arrow
%span.arrow
%span.count
.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
= sprite_icon('star-o', { css_class: 'icon' })
%span= s_('ProjectOverview|Star')
......@@ -32,9 +32,13 @@
= _('Otherwise it is recommended you start with one of the options below.')
.prepend-top-20
%nav.project-stats{ class: container_class }
= render 'stat_anchor_list', anchors: @project.empty_repo_statistics_anchors
= render 'stat_anchor_list', anchors: @project.empty_repo_statistics_buttons
%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
= render 'stat_anchor_list', anchors: @project.empty_repo_statistics_anchors
= 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)] }
......@@ -42,7 +46,7 @@
.empty_wrapper
%h3#repo-command-line-instructions.page-title-empty
Command line instructions
.git-empty
.git-empty.js-git-empty
%fieldset
%h5 Git global setup
%pre.bg-light
......@@ -54,7 +58,7 @@
%h5 Create a new repository
%pre.bg-light
:preserve
git clone #{ content_tag(:span, default_url_to_repo, class: 'clone')}
git clone #{ content_tag(:span, default_url_to_repo, class: 'js-clone')}
cd #{h @project.path}
touch README.md
git add README.md
......@@ -69,7 +73,7 @@
:preserve
cd existing_folder
git init
git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'clone')}
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?
......@@ -82,7 +86,7 @@
:preserve
cd existing_repo
git remote rename origin old-origin
git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'clone')}
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
......
......@@ -19,8 +19,13 @@
- if can?(current_user, :download_code, @project)
%nav.project-stats{ class: [container_class, ("limit-container-width" unless fluid_layout)] }
= 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)
.scrolling-tabs-container.inner-page-scroll-tabs.is-smaller
.fade-left= icon('angle-left')
.fade-right= icon('angle-right')
.nav-links.scrolling-tabs
= 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)
= repository_languages_bar(@project.repository_languages)
%div{ class: [container_class, ("limit-container-width" unless fluid_layout)] }
......
- project = project || @project
.git-clone-holder.input-group
.git-clone-holder.js-git-clone-holder.input-group
.input-group-prepend
- if allowed_protocols_present?
.input-group-text.clone-dropdown-btn.btn
%span
%span.js-clone-dropdown-label
= enabled_project_button(project, enabled_protocol)
- else
%a#clone-dropdown.input-group-text.btn.clone-dropdown-btn.qa-clone-dropdown{ href: '#', data: { toggle: 'dropdown' } }
%span
%span.js-clone-dropdown-label
= default_clone_protocol.upcase
= icon('caret-down')
%ul.dropdown-menu.dropdown-menu-selectable.clone-options-dropdown
......
- project = project || @project
- ssh_copy_label = _("Copy SSH clone URL")
- http_copy_label = _("Copy HTTPS clone URL")
.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")
%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' })
%li
= dropdown_item_with_description(http_copy_label, project.http_url_to_repo, href: project.http_url_to_repo, data: { clone_type: 'http' })
---
title: Update design of project overview page
merge_request: 20536
author:
type: changed
......@@ -150,6 +150,9 @@ msgstr ""
msgid "%{unstaged} unstaged and %{staged} staged changes"
msgstr ""
msgid "+ %{count} more"
msgstr ""
msgid "+ %{moreCount} more"
msgstr ""
......@@ -319,10 +322,10 @@ msgstr ""
msgid "Add Kubernetes cluster"
msgstr ""
msgid "Add License"
msgid "Add Readme"
msgstr ""
msgid "Add Readme"
msgid "Add license"
msgstr ""
msgid "Add new application"
......@@ -1897,6 +1900,15 @@ msgstr ""
msgid "ConvDev Index"
msgstr ""
msgid "Copy %{protocol} clone URL"
msgstr ""
msgid "Copy HTTPS clone URL"
msgstr ""
msgid "Copy SSH clone URL"
msgstr ""
msgid "Copy URL to clipboard"
msgstr ""
......@@ -1990,9 +2002,6 @@ msgstr ""
msgid "Create project label"
msgstr ""
msgid "CreateNewFork|Fork"
msgstr ""
msgid "CreateTag|Tag"
msgstr ""
......@@ -2730,11 +2739,6 @@ msgstr ""
msgid "For public projects, anyone can view pipelines and access job details (output logs and artifacts)"
msgstr ""
msgid "Fork"
msgid_plural "Forks"
msgstr[0] ""
msgstr[1] ""
msgid "ForkedFromProjectPath|Forked from"
msgstr ""
......@@ -2858,12 +2862,6 @@ msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
msgid "Go to your fork"
msgstr ""
msgid "GoToYourFork|Fork"
msgstr ""
msgid "Google Code import"
msgstr ""
......@@ -3895,6 +3893,9 @@ msgstr ""
msgid "No labels with such name or description"
msgstr ""
msgid "No license. All rights reserved"
msgstr ""
msgid "No merge requests found"
msgstr ""
......@@ -4554,6 +4555,27 @@ msgstr ""
msgid "ProjectLifecycle|Stage"
msgstr ""
msgid "ProjectOverview|Fork"
msgstr ""
msgid "ProjectOverview|Forks"
msgstr ""
msgid "ProjectOverview|Go to your fork"
msgstr ""
msgid "ProjectOverview|Star"
msgstr ""
msgid "ProjectOverview|Unstar"
msgstr ""
msgid "ProjectOverview|You have reached your project limit"
msgstr ""
msgid "ProjectOverview|You must sign in to star a project"
msgstr ""
msgid "ProjectPage|Project ID: %{project_id}"
msgstr ""
......@@ -6516,9 +6538,6 @@ msgstr ""
msgid "You must have maintainer access to force delete a lock"
msgstr ""
msgid "You must sign in to star a project"
msgstr ""
msgid "You need permission."
msgstr ""
......
......@@ -23,7 +23,7 @@ module QA
end
view 'app/views/projects/buttons/_fork.html.haml' do
element :fork_label, "%span= s_('GoToYourFork|Fork')"
element :fork_label, "%span= s_('ProjectOverview|Fork')"
element :fork_link, "link_to new_project_fork_path(@project)"
end
......@@ -32,7 +32,7 @@ module QA
end
view 'app/presenters/project_presenter.rb' do
element :new_file_button, "label: _('New file'),"
element :new_file_button, "_('New file'),"
end
def project_name
......
......@@ -36,7 +36,7 @@ describe 'Projects > Files > Project owner creates a license file', :js do
end
it 'project maintainer creates a license file from the "Add license" link' do
click_link 'Add License'
click_link 'Add license'
expect(page).to have_content('New file')
expect(current_path).to eq(
......
......@@ -10,7 +10,7 @@ describe 'Projects > Files > Project owner sees a link to create a license file
it 'project maintainer creates a license file from a template' do
visit project_path(project)
click_on 'Add License'
click_on 'Add license'
expect(page).to have_content('New file')
expect(current_path).to eq(
......
require 'spec_helper'
describe 'Projects > Show > User sees setup shortcut buttons' do
# For "New file", "Add License" functionality,
# For "New file", "Add license" functionality,
# see spec/features/projects/files/project_owner_creates_license_file_spec.rb
# see spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb
......@@ -58,9 +58,9 @@ describe 'Projects > Show > User sees setup shortcut buttons' do
end
end
it '"Add License" button linked to new file populated for a license' do
page.within('.project-stats') do
expect(page).to have_link('Add License', href: presenter.add_license_path)
it '"Add license" button linked to new file populated for a license' do
page.within('.project-metadata') do
expect(page).to have_link('Add license', href: presenter.add_license_path)
end
end
......@@ -201,13 +201,13 @@ describe 'Projects > Show > User sees setup shortcut buttons' do
end
end
it 'no "Add License" button if the project already has a license' do
it 'no "Add license" button if the project already has a license' do
visit project_path(project)
expect(project.repository.license_blob).not_to be_nil
page.within('.project-stats') do
expect(page).not_to have_link('Add License')
expect(page).not_to have_link('Add license')
end
end
......
......@@ -2,6 +2,7 @@ require 'spec_helper'
describe 'Project' do
include ProjectForksHelper
include MobileHelpers
describe 'creating from template' do
let(:user) { create(:user) }
......@@ -54,25 +55,72 @@ describe 'Project' do
it 'parses Markdown' do
project.update_attribute(:description, 'This is **my** project')
visit path
expect(page).to have_css('.project-home-desc > p > strong')
expect(page).to have_css('.project-description > .project-description-markdown > p > strong')
end
it 'passes through html-pipeline' do
project.update_attribute(:description, 'This project is the :poop:')
visit path
expect(page).to have_css('.project-home-desc > p > gl-emoji')
expect(page).to have_css('.project-description > .project-description-markdown > p > gl-emoji')
end
it 'sanitizes unwanted tags' do
project.update_attribute(:description, "```\ncode\n```")
visit path
expect(page).not_to have_css('.project-home-desc code')
expect(page).not_to have_css('.project-description code')
end
it 'permits `rel` attribute on links' do
project.update_attribute(:description, 'https://google.com/')
visit path
expect(page).to have_css('.project-home-desc a[rel]')
expect(page).to have_css('.project-description a[rel]')
end
context 'read more', :js do
let(:read_more_selector) { '.read-more-container' }
let(:read_more_trigger_selector) { '.project-home-desc .js-read-more-trigger' }
it 'does not display "read more" link on desktop breakpoint' do
project.update_attribute(:description, 'This is **my** project')
visit path
expect(find(read_more_trigger_selector, visible: false)).not_to be_visible
end
it 'displays "read more" link on mobile breakpoint' do
project.update_attribute(:description, 'This is **my** project')
visit path
resize_screen_xs
find(read_more_trigger_selector).click
expect(page).to have_css('.project-description .is-expanded')
end
end
end
describe 'copy clone URL to clipboard', :js do
let(:project) { create(:project, :repository) }
let(:path) { project_path(project) }
before do
sign_in(create(:admin))
visit path
end
context 'desktop component' do
it 'shows on md and larger breakpoints' do
expect(find('.git-clone-holder')).to be_visible
expect(find('.mobile-git-clone', visible: false)).not_to be_visible
end
end
context 'mobile component' do
it 'shows mobile component on sm and smaller breakpoints' do
resize_screen_xs
expect(find('.mobile-git-clone')).to be_visible
expect(find('.git-clone-holder', visible: false)).not_to be_visible
end
end
end
......
......@@ -6,6 +6,7 @@ describe 'Projects (JavaScript fixtures)', type: :controller do
let(:admin) { create(:admin) }
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
let(:project) { create(:project, namespace: namespace, path: 'builds-project') }
let(:project_with_repo) { create(:project, :repository, description: 'Code and stuff') }
let(:project_variable_populated) { create(:project, namespace: namespace, path: 'builds-project2') }
let!(:variable1) { create(:ci_variable, project: project_variable_populated) }
let!(:variable2) { create(:ci_variable, project: project_variable_populated) }
......@@ -35,6 +36,15 @@ describe 'Projects (JavaScript fixtures)', type: :controller do
store_frontend_fixture(response, example.description)
end
it 'projects/overview.html.raw' do |example|
get :show,
namespace_id: project_with_repo.namespace.to_param,
id: project_with_repo
expect(response).to be_success
store_frontend_fixture(response, example.description)
end
it 'projects/edit.html.raw' do |example|
get :edit,
namespace_id: project.namespace.to_param,
......
import initReadMore from '~/read_more';
describe('Read more click-to-expand functionality', () => {
const fixtureName = 'projects/overview.html.raw';
preloadFixtures(fixtureName);
beforeEach(() => {
loadFixtures(fixtureName);
});
describe('expands target element', () => {
it('adds "is-expanded" class to target element', () => {
const target = document.querySelector('.read-more-container');
const trigger = document.querySelector('.js-read-more-trigger');
initReadMore();
trigger.click();
expect(target.classList.contains('is-expanded')).toEqual(true);
});
});
});
......@@ -159,39 +159,76 @@ describe ProjectPresenter do
end
end
context 'statistics anchors (empty repo)' do
let(:project) { create(:project, :empty_repo) }
let(:presenter) { described_class.new(project, current_user: user) }
describe '#files_anchor_data' do
it 'returns files data' do
expect(presenter.files_anchor_data).to have_attributes(enabled: true,
label: 'Files (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)',
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)",
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)",
link: nil)
end
end
end
context 'statistics anchors' do
let(:project) { create(:project, :repository) }
let(:presenter) { described_class.new(project, current_user: user) }
describe '#files_anchor_data' do
it 'returns files data' do
expect(presenter.files_anchor_data).to eq(OpenStruct.new(enabled: true,
label: 'Files (0 Bytes)',
link: presenter.project_tree_path(project)))
expect(presenter.files_anchor_data).to have_attributes(enabled: true,
label: 'Files (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 eq(OpenStruct.new(enabled: true,
label: 'Commits (0)',
link: presenter.project_commits_path(project, project.repository.root_ref)))
expect(presenter.commits_anchor_data).to have_attributes(enabled: true,
label: 'Commits (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 eq(OpenStruct.new(enabled: true,
label: "Branches (#{project.repository.branches.size})",
link: presenter.project_branches_path(project)))
expect(presenter.branches_anchor_data).to have_attributes(enabled: true,
label: "Branches (#{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 eq(OpenStruct.new(enabled: true,
label: "Tags (#{project.repository.tags.size})",
link: presenter.project_tags_path(project)))
expect(presenter.tags_anchor_data).to have_attributes(enabled: true,
label: "Tags (#{project.repository.tags.size})",
link: presenter.project_tags_path(project))
end
end
......@@ -199,10 +236,10 @@ describe ProjectPresenter do
it 'returns new file data if user can push' do
project.add_developer(user)
expect(presenter.new_file_anchor_data).to eq(OpenStruct.new(enabled: false,
label: "New file",
link: presenter.project_new_blob_path(project, 'master'),
class_modifier: 'new'))
expect(presenter.new_file_anchor_data).to have_attributes(enabled: false,
label: "New file",
link: presenter.project_new_blob_path(project, 'master'),
class_modifier: 'new')
end
it 'returns nil if user cannot push' do
......@@ -227,9 +264,9 @@ describe ProjectPresenter do
project.add_developer(user)
allow(project.repository).to receive(:readme).and_return(nil)
expect(presenter.readme_anchor_data).to eq(OpenStruct.new(enabled: false,
label: 'Add Readme',
link: presenter.add_readme_path))
expect(presenter.readme_anchor_data).to have_attributes(enabled: false,
label: 'Add Readme',
link: presenter.add_readme_path)
end
end
......@@ -237,9 +274,9 @@ 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 eq(OpenStruct.new(enabled: true,
label: 'Readme',
link: presenter.readme_path))
expect(presenter.readme_anchor_data).to have_attributes(enabled: true,
label: 'Readme',
link: presenter.readme_path)
end
end
end
......@@ -250,9 +287,9 @@ describe ProjectPresenter do
project.add_developer(user)
allow(project.repository).to receive(:changelog).and_return(nil)
expect(presenter.changelog_anchor_data).to eq(OpenStruct.new(enabled: false,
label: 'Add Changelog',
link: presenter.add_changelog_path))
expect(presenter.changelog_anchor_data).to have_attributes(enabled: false,
label: 'Add Changelog',
link: presenter.add_changelog_path)
end
end
......@@ -260,9 +297,9 @@ 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 eq(OpenStruct.new(enabled: true,
label: 'Changelog',
link: presenter.changelog_path))
expect(presenter.changelog_anchor_data).to have_attributes(enabled: true,
label: 'Changelog',
link: presenter.changelog_path)
end
end
end
......@@ -273,9 +310,9 @@ describe ProjectPresenter do
project.add_developer(user)
allow(project.repository).to receive(:license_blob).and_return(nil)
expect(presenter.license_anchor_data).to eq(OpenStruct.new(enabled: false,
label: 'Add License',
link: presenter.add_license_path))
expect(presenter.license_anchor_data).to have_attributes(enabled: false,
label: 'Add license',
link: presenter.add_license_path)
end
end
......@@ -283,9 +320,9 @@ 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 eq(OpenStruct.new(enabled: true,
label: presenter.license_short_name,
link: presenter.license_path))
expect(presenter.license_anchor_data).to have_attributes(enabled: true,
label: presenter.license_short_name,
link: presenter.license_path)
end
end
end
......@@ -296,9 +333,9 @@ describe ProjectPresenter do
project.add_developer(user)
allow(project.repository).to receive(:contribution_guide).and_return(nil)
expect(presenter.contribution_guide_anchor_data).to eq(OpenStruct.new(enabled: false,
label: 'Add Contribution guide',
link: presenter.add_contribution_guide_path))
expect(presenter.contribution_guide_anchor_data).to have_attributes(enabled: false,
label: 'Add Contribution guide',
link: presenter.add_contribution_guide_path)
end
end
......@@ -306,9 +343,9 @@ 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 eq(OpenStruct.new(enabled: true,
label: 'Contribution guide',
link: presenter.contribution_guide_path))
expect(presenter.contribution_guide_anchor_data).to have_attributes(enabled: true,
label: 'Contribution guide',
link: presenter.contribution_guide_path)
end
end
end
......@@ -318,9 +355,9 @@ describe ProjectPresenter do
it 'returns anchor data' do
allow(project).to receive(:auto_devops_enabled?).and_return(true)
expect(presenter.autodevops_anchor_data).to eq(OpenStruct.new(enabled: true,
label: 'Auto DevOps enabled',
link: nil))
expect(presenter.autodevops_anchor_data).to have_attributes(enabled: true,
label: 'Auto DevOps enabled',
link: nil)
end
end
......@@ -330,9 +367,9 @@ describe ProjectPresenter do
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 eq(OpenStruct.new(enabled: false,
label: 'Enable Auto DevOps',
link: presenter.project_settings_ci_cd_path(project, anchor: 'autodevops-settings')))
expect(presenter.autodevops_anchor_data).to have_attributes(enabled: false,
label: 'Enable Auto DevOps',
link: presenter.project_settings_ci_cd_path(project, anchor: 'autodevops-settings'))
end
end
end
......@@ -343,9 +380,9 @@ describe ProjectPresenter do
project.add_maintainer(user)
cluster = create(:cluster, projects: [project])
expect(presenter.kubernetes_cluster_anchor_data).to eq(OpenStruct.new(enabled: true,
label: 'Kubernetes configured',
link: presenter.project_cluster_path(project, cluster)))
expect(presenter.kubernetes_cluster_anchor_data).to have_attributes(enabled: true,
label: 'Kubernetes configured',
link: presenter.project_cluster_path(project, cluster))
end
it 'returns link to clusters page if more than one exists' do
......@@ -353,17 +390,17 @@ describe ProjectPresenter do
create(:cluster, :production_environment, projects: [project])
create(:cluster, projects: [project])
expect(presenter.kubernetes_cluster_anchor_data).to eq(OpenStruct.new(enabled: true,
label: 'Kubernetes configured',
link: presenter.project_clusters_path(project)))
expect(presenter.kubernetes_cluster_anchor_data).to have_attributes(enabled: true,
label: '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 eq(OpenStruct.new(enabled: false,
label: 'Add Kubernetes cluster',
link: presenter.new_project_cluster_path(project)))
expect(presenter.kubernetes_cluster_anchor_data).to have_attributes(enabled: false,
label: 'Add Kubernetes cluster',
link: presenter.new_project_cluster_path(project))
end
end
......@@ -380,9 +417,9 @@ describe ProjectPresenter do
allow(project.repository).to receive(:koding_yml).and_return(nil)
allow(Gitlab::CurrentSettings).to receive(:koding_enabled?).and_return(true)
expect(presenter.koding_anchor_data).to eq(OpenStruct.new(enabled: false,
label: 'Set up Koding',
link: presenter.add_koding_stack_path))
expect(presenter.koding_anchor_data).to have_attributes(enabled: false,
label: 'Set up Koding',
link: presenter.add_koding_stack_path)
end
it 'returns nil if user cannot push' do
......
......@@ -9,6 +9,7 @@ describe 'projects/_home_panel' do
allow(view).to receive(:current_user).and_return(user)
allow(view).to receive(:can?).with(user, :read_project, project).and_return(false)
allow(project).to receive(:license_anchor_data).and_return(false)
end
context 'when user is signed in' do
......@@ -63,6 +64,7 @@ describe 'projects/_home_panel' do
allow(view).to receive(:current_user).and_return(user)
allow(view).to receive(:can?).with(user, :read_project, project).and_return(false)
allow(project).to receive(:license_anchor_data).and_return(false)
end
context 'has no badges' do
......@@ -71,8 +73,7 @@ describe 'projects/_home_panel' do
it 'should not render any badge' do
render
expect(rendered).to have_selector('.project-badges')
expect(rendered).not_to have_selector('.project-badges > a')
expect(rendered).not_to have_selector('.project-badges')
end
end
......@@ -118,6 +119,7 @@ describe 'projects/_home_panel' do
assign(:project, project)
allow(view).to receive(:current_user).and_return(user)
allow(project).to receive(:license_anchor_data).and_return(false)
end
context 'user can read project' do
......
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