Commit b373c56c authored by DJ Mountney's avatar DJ Mountney

Merge remote-tracking branch 'origin/master' into dev-master

parents ac38f36a 21935d85
......@@ -43,6 +43,7 @@ stages:
# Predefined scopes
.dedicated-runner: &dedicated-runner
retry: 1
tags:
- gitlab-org
......@@ -411,7 +412,6 @@ db:migrate:reset-mysql:
.migration-paths: &migration-paths
<<: *dedicated-runner
<<: *only-canonical-masters
<<: *pull-cache
stage: test
variables:
......
......@@ -199,26 +199,8 @@ available in the package repositories.
## Release retrospective and kickoff
### Retrospective
After each release, we have a retrospective call where we discuss what went well,
what went wrong, and what we can improve for the next release. The
[retrospective notes] are public and you are invited to comment on them.
If you're interested, you can even join the
[retrospective call][retro-kickoff-call], on the first working day after the
22nd at 6pm CET / 9am PST.
### Kickoff
Before working on the next release, we have a
kickoff call to explain what we expect to ship in the next release. The
[kickoff notes] are public and you are invited to comment on them.
If you're interested, you can even join the [kickoff call][retro-kickoff-call],
on the first working day after the 7th at 6pm CET / 9am PST..
[retrospective notes]: https://docs.google.com/document/d/1nEkM_7Dj4bT21GJy0Ut3By76FZqCfLBmFQNVThmW2TY/edit?usp=sharing
[kickoff notes]: https://docs.google.com/document/d/1ElPkZ90A8ey_iOkTvUs_ByMlwKK6NAB2VOK5835wYK0/edit?usp=sharing
[retro-kickoff-call]: https://gitlab.zoom.us/j/918821206
- [Retrospective](https://about.gitlab.com/handbook/engineering/workflow/#retrospective)
- [Kickoff](https://about.gitlab.com/handbook/engineering/workflow/#kickoff)
## Copy & paste responses
......
export const addTooltipToEl = (el) => {
const textEl = el.querySelector('.js-breadcrumb-item-text');
if (textEl && textEl.scrollWidth > textEl.offsetWidth) {
el.setAttribute('title', el.textContent);
el.setAttribute('data-container', 'body');
el.classList.add('has-tooltip');
}
};
export default () => {
const breadcrumbs = document.querySelector('.js-breadcrumbs-list');
if (breadcrumbs) {
const topLevelLinks = [...breadcrumbs.children].filter(el => !el.classList.contains('dropdown'))
.map(el => el.querySelector('a'))
.filter(el => el);
const $expander = $('.js-breadcrumbs-collapsed-expander');
topLevelLinks.forEach(el => addTooltipToEl(el));
$expander.closest('.dropdown')
.on('show.bs.dropdown hide.bs.dropdown', (e) => {
$('.js-breadcrumbs-collapsed-expander', e.currentTarget).toggleClass('open')
.tooltip('hide');
});
}
};
......@@ -41,7 +41,6 @@ import Issue from './issue';
import BindInOut from './behaviors/bind_in_out';
import DeleteModal from './branches/branches_delete_modal';
import Group from './group';
import GroupName from './group_name';
import GroupsList from './groups_list';
import ProjectsList from './projects_list';
import setupProjectEdit from './project_edit';
......@@ -489,6 +488,8 @@ import initChangesDropdown from './init_changes_dropdown';
initSettingsPanels();
break;
case 'projects:settings:ci_cd:show':
// Initialize expandable settings panels
initSettingsPanels();
case 'groups:settings:ci_cd:show':
new gl.ProjectVariables();
break;
......@@ -554,9 +555,6 @@ import initChangesDropdown from './init_changes_dropdown';
case 'root':
new UserCallout();
break;
case 'groups':
new GroupName();
break;
case 'profiles':
new NotificationsForm();
new NotificationsDropdown();
......@@ -564,7 +562,6 @@ import initChangesDropdown from './init_changes_dropdown';
case 'projects':
new Project();
new ProjectAvatar();
new GroupName();
switch (path[1]) {
case 'compare':
new CompareAutocomplete();
......
import Cookies from 'js-cookie';
import _ from 'underscore';
export default class GroupName {
constructor() {
this.titleContainer = document.querySelector('.js-title-container');
this.title = this.titleContainer.querySelector('.title');
if (this.title) {
this.titleWidth = this.title.offsetWidth;
this.groupTitle = this.titleContainer.querySelector('.group-title');
this.groups = this.titleContainer.querySelectorAll('.group-path');
this.toggle = null;
this.isHidden = false;
this.init();
}
}
init() {
if (this.groups.length > 0) {
this.groups[this.groups.length - 1].classList.remove('hidable');
this.toggleHandler();
window.addEventListener('resize', _.debounce(this.toggleHandler.bind(this), 100));
}
this.render();
}
toggleHandler() {
if (this.titleWidth > this.titleContainer.offsetWidth) {
if (!this.toggle) this.createToggle();
this.showToggle();
} else if (this.toggle) {
this.hideToggle();
}
}
createToggle() {
this.toggle = document.createElement('button');
this.toggle.setAttribute('type', 'button');
this.toggle.className = 'text-expander group-name-toggle';
this.toggle.setAttribute('aria-label', 'Toggle full path');
if (Cookies.get('new_nav') === 'true') {
this.toggle.innerHTML = '<i class="fa fa-ellipsis-h" aria-hidden="true"></i>';
} else {
this.toggle.innerHTML = '...';
}
this.toggle.addEventListener('click', this.toggleGroups.bind(this));
if (Cookies.get('new_nav') === 'true') {
this.title.insertBefore(this.toggle, this.groupTitle);
} else {
this.titleContainer.insertBefore(this.toggle, this.title);
}
this.toggleGroups();
}
showToggle() {
this.title.classList.add('wrap');
this.toggle.classList.remove('hidden');
if (this.isHidden) this.groupTitle.classList.add('hidden');
}
hideToggle() {
this.title.classList.remove('wrap');
this.toggle.classList.add('hidden');
if (this.isHidden) this.groupTitle.classList.remove('hidden');
}
toggleGroups() {
this.isHidden = !this.isHidden;
this.groupTitle.classList.toggle('hidden');
}
render() {
this.title.classList.remove('initializing');
}
}
......@@ -5,7 +5,6 @@
/* global SubscriptionSelect */
import IssuableBulkUpdateActions from './issuable_bulk_update_actions';
import SidebarHeightManager from './sidebar_height_manager';
const HIDDEN_CLASS = 'hidden';
const DISABLED_CONTENT_CLASS = 'disabled-content';
......@@ -50,13 +49,6 @@ export default class IssuableBulkUpdateSidebar {
new SubscriptionSelect();
}
getNavHeight() {
const navbarHeight = $('.navbar-gitlab').outerHeight();
const layoutNavHeight = $('.layout-nav').outerHeight();
const subNavScroll = $('.sub-nav-scroll').outerHeight();
return navbarHeight + layoutNavHeight + subNavScroll;
}
setupBulkUpdateActions() {
IssuableBulkUpdateActions.setOriginalDropdownData();
}
......@@ -84,23 +76,6 @@ export default class IssuableBulkUpdateSidebar {
this.toggleBulkEditButtonDisabled(enable);
this.toggleOtherFiltersDisabled(enable);
this.toggleCheckboxDisplay(enable);
if (enable) {
this.initAffix();
SidebarHeightManager.init();
}
}
initAffix() {
if (!this.$sidebar.hasClass('affix-top')) {
const offsetTop = $('.scrolling-tabs-container').outerHeight() + $('.sub-nav-scroll').outerHeight();
this.$sidebar.affix({
offset: {
top: offsetTop,
},
});
}
}
updateSelectedIssuableIds() {
......
......@@ -50,19 +50,10 @@ import initFlyOutNav from './fly_out_nav';
});
});
function applyScrollNavClass() {
const scrollOpacityHeight = 40;
$('.navbar-border').css('opacity', Math.min($(window).scrollTop() / scrollOpacityHeight, 1));
}
$(() => {
if (Cookies.get('new_nav') === 'true') {
const newNavSidebar = new NewNavSidebar();
newNavSidebar.bindEvents();
initFlyOutNav();
}
const newNavSidebar = new NewNavSidebar();
newNavSidebar.bindEvents();
$(window).on('scroll', _.throttle(applyScrollNavClass, 100));
initFlyOutNav();
});
}).call(window);
......@@ -144,6 +144,7 @@ import './smart_interval';
import './star';
import './subscription';
import './subscription_select';
import initBreadcrumbs from './breadcrumb';
import './dispatcher';
......@@ -181,6 +182,8 @@ $(function () {
var bootstrapBreakpoint = bp.getBreakpointSize();
var fitSidebarForSize;
initBreadcrumbs();
// Set the default path for all cookies to GitLab's root directory
Cookies.defaults.path = gon.relative_url_root || '/';
......
......@@ -63,7 +63,7 @@ export default class NewNavSidebar {
if (breakpoint === 'sm' || breakpoint === 'md') {
this.toggleCollapsedSidebar(true);
} else if (breakpoint === 'lg') {
const collapse = Cookies.get('sidebar_collapsed') === 'true';
const collapse = this.$sidebar.hasClass('sidebar-icons-only');
this.toggleCollapsedSidebar(collapse);
}
}
......
......@@ -2,7 +2,6 @@
import _ from 'underscore';
import Cookies from 'js-cookie';
import SidebarHeightManager from './sidebar_height_manager';
(function() {
this.Sidebar = (function() {
......@@ -23,7 +22,6 @@ import SidebarHeightManager from './sidebar_height_manager';
};
Sidebar.prototype.addEventListeners = function() {
SidebarHeightManager.init();
const $document = $(document);
this.sidebar.on('click', '.sidebar-collapsed-icon', this, this.sidebarCollapseClicked);
......
import _ from 'underscore';
import Cookies from 'js-cookie';
export default {
init() {
if (!this.initialized) {
if (Cookies.get('new_nav') === 'true' && $('.js-issuable-sidebar').length) return;
this.$window = $(window);
this.$rightSidebar = $('.js-right-sidebar');
this.$navHeight = $('.navbar-gitlab').outerHeight() +
$('.layout-nav').outerHeight() +
$('.sub-nav-scroll').outerHeight();
const throttledSetSidebarHeight = _.throttle(() => this.setSidebarHeight(), 20);
const debouncedSetSidebarHeight = _.debounce(() => this.setSidebarHeight(), 200);
this.$window.on('scroll', throttledSetSidebarHeight);
this.$window.on('resize', debouncedSetSidebarHeight);
this.initialized = true;
}
},
setSidebarHeight() {
const currentScrollDepth = window.pageYOffset || 0;
const diff = this.$navHeight - currentScrollDepth;
if (diff > 0) {
const newSidebarHeight = window.innerHeight - diff;
this.$rightSidebar.outerHeight(newSidebarHeight);
this.sidebarHeightIsCustom = true;
} else if (this.sidebarHeightIsCustom) {
this.$rightSidebar.outerHeight('100%');
this.sidebarHeightIsCustom = false;
}
},
};
......@@ -829,6 +829,7 @@
}
}
@include new-style-dropdown('.breadcrumbs-list .dropdown ');
@include new-style-dropdown('.js-namespace-select + ');
header.navbar-gitlab-new .header-content .dropdown-menu.projects-dropdown-menu {
......
@import "framework/variables";
@import 'framework/tw_bootstrap_variables';
@import "bootstrap/variables";
@import "framework/mixins";
.content-wrapper.page-with-new-nav {
margin-top: $new-navbar-height;
......@@ -422,109 +423,38 @@ header.navbar-gitlab-new {
.breadcrumbs {
display: flex;
min-height: 61px;
min-height: 48px;
color: $gl-text-color;
border-bottom: 1px solid $border-color;
.dropdown-toggle-caret {
position: relative;
top: -1px;
padding: 0 5px;
color: $gl-text-color-secondary;
font-size: 10px;
line-height: 1;
background: none;
border: 0;
&:focus {
outline: 0;
}
}
// TODO: fallback to global style
.dropdown-menu {
.divider {
margin: 6px 0;
}
li {
padding: 0 1px;
a {
border-radius: 0;
padding: 8px 16px;
&.is-focused,
&:hover,
&:active,
&:focus {
background-color: $gray-darker;
}
}
}
}
}
.breadcrumbs-container {
display: -webkit-flex;
display: flex;
width: 100%;
position: relative;
padding-top: $gl-padding;
padding-bottom: $gl-padding;
align-items: center;
.dropdown-menu-projects {
margin-top: -$gl-padding;
margin-left: $gl-padding;
}
border-bottom: 1px solid $border-color;
}
.breadcrumbs-links {
-webkit-flex: 1;
flex: 1;
min-width: 0;
align-self: center;
color: $gl-text-color-quaternary;
a {
color: $gl-text-color-secondary;
&:not(:first-child),
&.group-path {
margin-left: 4px;
}
&:not(:last-of-type),
&.group-path {
margin-right: 3px;
}
}
.title {
display: inline-block;
> a {
&:last-of-type:not(:first-child) {
font-weight: $gl-font-weight-bold;
}
}
}
color: $gl-text-color-secondary;
.avatar-tile {
margin-right: 5px;
margin-right: 4px;
border: 1px solid $border-color;
border-radius: 50%;
vertical-align: sub;
&.identicon {
float: left;
width: 16px;
height: 16px;
margin-top: 2px;
font-size: 10px;
}
}
.text-expander {
margin-left: 4px;
margin-right: 4px;
margin-left: 0;
margin-right: 2px;
> i {
position: relative;
......@@ -533,37 +463,52 @@ header.navbar-gitlab-new {
}
}
.breadcrumbs-extra {
.breadcrumbs-list {
display: -webkit-flex;
display: flex;
flex: 0 0 auto;
margin-left: auto;
}
flex-wrap: wrap;
margin-bottom: 0;
line-height: 16px;
.breadcrumbs-sub-title {
margin: 2px 0;
font-size: 16px;
font-weight: $gl-font-weight-normal;
line-height: 1;
ul {
margin: 0;
}
li {
display: inline-block;
> li {
display: flex;
align-items: center;
position: relative;
&:not(:last-child) {
&::after {
content: "/";
margin: 0 2px 0 5px;
color: rgba($black, .65);
}
margin-right: 20px;
}
&:last-child a {
font-weight: $gl-font-weight-bold;
> a {
font-size: 12px;
color: currentColor;
}
}
}
.breadcrumb-item-text {
@include str-truncated(128px);
}
.breadcrumbs-list-angle {
position: absolute;
right: -12px;
top: 50%;
color: $gl-text-color-tertiary;
transform: translateY(-50%);
}
.breadcrumbs-extra {
display: flex;
flex: 0 0 auto;
margin-left: auto;
}
.breadcrumbs-sub-title {
margin: 0;
font-size: 12px;
font-weight: 600;
line-height: 1;
a {
color: $gl-text-color;
......
......@@ -45,7 +45,6 @@ $new-sidebar-collapsed-width: 50px;
margin-right: 2px;
a {
border-bottom: 1px solid $border-color;
font-weight: $gl-font-weight-bold;
display: flex;
align-items: center;
......@@ -389,6 +388,10 @@ $new-sidebar-collapsed-width: 50px;
}
}
.nav-icon-container {
margin-right: 0;
}
.toggle-sidebar-button {
width: $new-sidebar-collapsed-width - 2px;
padding: 16px 18px;
......
......@@ -440,6 +440,7 @@
&.right-sidebar {
top: 0;
bottom: 0;
height: 100%;
}
.issuable-sidebar-header {
......
......@@ -141,17 +141,17 @@
display: inline-block;
background: $white-light;
color: $gl-text-color-secondary;
padding: 0 5px;
padding: 0 4px;
cursor: pointer;
border: 1px solid $border-gray-dark;
border-radius: $border-radius-default;
margin-left: 5px;
font-size: $gl-font-size;
font-size: 12px;
line-height: $gl-font-size;
outline: none;
&.open {
background: $gray-light;
background-color: darken($gray-light, 10%);
box-shadow: inset 0 0 2px rgba($black, 0.2);
}
......
......@@ -516,7 +516,7 @@ ul.notes {
}
.note-actions-item {
margin-left: 15px;
margin-left: 12px;
display: flex;
align-items: center;
......@@ -620,15 +620,25 @@ ul.notes {
.note-role {
position: relative;
padding: 0 7px;
display: inline-block;
color: $notes-role-color;
font-size: 12px;
line-height: 20px;
border: 1px solid $border-color;
border-radius: $label-border-radius;
margin: 0 3px;
&.note-role-access {
padding: 0 7px;
border: 1px solid $border-color;
border-radius: $label-border-radius;
}
&.note-role-special {
text-shadow: 0 0 15px $gl-text-color-inverted;
}
}
/**
* Line note button on the side of diffs
*/
......
class Admin::LogsController < Admin::ApplicationController
before_action :loggers
def show
@loggers = [
end
private
def loggers
@loggers ||= [
Gitlab::AppLogger,
Gitlab::GitLogger,
Gitlab::EnvironmentLogger,
......
module RendersCommits
def prepare_commits_for_rendering(commits)
Banzai::CommitRenderer.render(commits, @project, current_user)
commits
end
end
module RendersNotes
def prepare_notes_for_rendering(notes)
def prepare_notes_for_rendering(notes, noteable = nil)
preload_noteable_for_regular_notes(notes)
preload_max_access_for_authors(notes, @project)
preload_first_time_contribution_for_authors(noteable, notes)
Banzai::NoteRenderer.render(notes, @project, current_user)
notes
......@@ -19,4 +20,10 @@ module RendersNotes
def preload_noteable_for_regular_notes(notes)
ActiveRecord::Associations::Preloader.new.preload(notes.reject(&:for_commit?), :noteable)
end
def preload_first_time_contribution_for_authors(noteable, notes)
return unless noteable.is_a?(Issuable) && noteable.first_contribution?
notes.each {|n| n.specialize_for_first_contribution!(noteable)}
end
end
......@@ -9,8 +9,6 @@ class ProfilesController < Profiles::ApplicationController
end
def update
user_params.except!(:email) if @user.external_email?
respond_to do |format|
result = Users::UpdateService.new(@user, user_params).execute
......
......@@ -7,7 +7,7 @@ class Projects::ArtifactsController < Projects::ApplicationController
before_action :authorize_update_build!, only: [:keep]
before_action :extract_ref_name_and_path
before_action :validate_artifacts!
before_action :set_path_and_entry, only: [:file, :raw]
before_action :entry, only: [:file]
def download
if artifacts_file.file_storage?
......@@ -41,7 +41,10 @@ class Projects::ArtifactsController < Projects::ApplicationController
end
def raw
send_artifacts_entry(build, @entry)
path = Gitlab::Ci::Build::Artifacts::Path
.new(params[:path])
send_artifacts_entry(build, path)
end
def keep
......@@ -93,9 +96,8 @@ class Projects::ArtifactsController < Projects::ApplicationController
@artifacts_file ||= build.artifacts_file
end
def set_path_and_entry
@path = params[:path]
@entry = build.artifacts_metadata_entry(@path)
def entry
@entry = build.artifacts_metadata_entry(params[:path])
render_404 unless @entry.exists?
end
......
......@@ -127,7 +127,7 @@ class Projects::CommitController < Projects::ApplicationController
@discussions = commit.discussions
@notes = (@grouped_diff_discussions.values.flatten + @discussions).flat_map(&:notes)
@notes = prepare_notes_for_rendering(@notes)
@notes = prepare_notes_for_rendering(@notes, @commit)
end
def assign_change_commit_vars
......
......@@ -2,6 +2,7 @@ require "base64"
class Projects::CommitsController < Projects::ApplicationController
include ExtractsPath
include RendersCommits
before_action :require_non_empty_project
before_action :assign_ref_vars
......@@ -56,5 +57,7 @@ class Projects::CommitsController < Projects::ApplicationController
else
@repository.commits(@ref, path: @path, limit: @limit, offset: @offset)
end
@commits = prepare_commits_for_rendering(@commits)
end
end
......@@ -3,6 +3,7 @@ require 'addressable/uri'
class Projects::CompareController < Projects::ApplicationController
include DiffForPath
include DiffHelper
include RendersCommits
# Authorize
before_action :require_non_empty_project
......@@ -50,7 +51,7 @@ class Projects::CompareController < Projects::ApplicationController
.execute(@project, @start_ref)
if @compare
@commits = @compare.commits
@commits = prepare_commits_for_rendering(@compare.commits)
@diffs = @compare.diffs(diff_options)
environment_params = @repository.branch_exists?(@head_ref) ? { ref: @head_ref } : { commit: @compare.commit }
......
......@@ -85,7 +85,7 @@ class Projects::IssuesController < Projects::ApplicationController
@note = @project.notes.new(noteable: @issue)
@discussions = @issue.discussions
@notes = prepare_notes_for_rendering(@discussions.flat_map(&:notes))
@notes = prepare_notes_for_rendering(@discussions.flat_map(&:notes), @noteable)
respond_to do |format|
format.html
......
class Projects::MergeRequests::CreationsController < Projects::MergeRequests::ApplicationController
include DiffForPath
include DiffHelper
include RendersCommits
skip_before_action :merge_request
skip_before_action :ensure_ref_fetched
......@@ -107,7 +108,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
@target_project = @merge_request.target_project
@source_project = @merge_request.source_project
@commits = @merge_request.commits
@commits = prepare_commits_for_rendering(@merge_request.commits)
@commit = @merge_request.diff_head_commit
@note_counts = Note.where(commit_id: @commits.map(&:id))
......
......@@ -61,6 +61,6 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
@use_legacy_diff_notes = !@merge_request.has_complete_diff_refs?
@grouped_diff_discussions = @merge_request.grouped_diff_discussions(@compare.diff_refs)
@notes = prepare_notes_for_rendering(@grouped_diff_discussions.values.flatten.flat_map(&:notes))
@notes = prepare_notes_for_rendering(@grouped_diff_discussions.values.flatten.flat_map(&:notes), @merge_request)
end
end
......@@ -2,6 +2,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
include ToggleSubscriptionAction
include IssuableActions
include RendersNotes
include RendersCommits
include ToggleAwardEmoji
include IssuableCollections
......@@ -60,12 +61,12 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
# Build a note object for comment form
@note = @project.notes.new(noteable: @merge_request)
@discussions = @merge_request.discussions
@notes = prepare_notes_for_rendering(@discussions.flat_map(&:notes))
@noteable = @merge_request
@commits_count = @merge_request.commits_count
@discussions = @merge_request.discussions
@notes = prepare_notes_for_rendering(@discussions.flat_map(&:notes), @noteable)
labels
set_pipeline_variables
......@@ -94,7 +95,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
def commits
# Get commits from repository
# or from cache if already merged
@commits = @merge_request.commits
@commits = prepare_commits_for_rendering(@merge_request.commits)
@note_counts = Note.where(commit_id: @commits.map(&:id))
.group(:commit_id).count
......
......@@ -64,7 +64,7 @@ class Projects::SnippetsController < Projects::ApplicationController
@noteable = @snippet
@discussions = @snippet.discussions
@notes = prepare_notes_for_rendering(@discussions.flat_map(&:notes))
@notes = prepare_notes_for_rendering(@discussions.flat_map(&:notes), @noteable)
render 'show'
end
......
......@@ -323,6 +323,7 @@ class ProjectsController < Projects::ApplicationController
:build_allow_git_fetch,
:build_coverage_regex,
:build_timeout_in_minutes,
:resolve_outdated_diff_discussions,
:container_registry_enabled,
:default_branch,
:description,
......
......@@ -2,6 +2,7 @@ class SearchController < ApplicationController
skip_before_action :authenticate_user!
include SearchHelper
include RendersCommits
layout 'search'
......@@ -20,6 +21,8 @@ class SearchController < ApplicationController
@search_results = search_service.search_results
@search_objects = search_service.search_objects
render_commits if @scope == 'commits'
check_single_commit_result
end
......@@ -38,6 +41,10 @@ class SearchController < ApplicationController
private
def render_commits
@search_objects = prepare_commits_for_rendering(@search_objects)
end
def check_single_commit_result
if @search_results.single_commit_result?
only_commit = @search_results.objects('commits').first
......
......@@ -66,7 +66,7 @@ class SnippetsController < ApplicationController
@noteable = @snippet
@discussions = @snippet.discussions
@notes = prepare_notes_for_rendering(@discussions.flat_map(&:notes))
@notes = prepare_notes_for_rendering(@discussions.flat_map(&:notes), @noteable)
respond_to do |format|
format.html do
......
......@@ -302,10 +302,6 @@ module ApplicationHelper
end
end
def show_new_nav?
true
end
def collapsed_sidebar?
cookies["sidebar_collapsed"] == "true"
end
......
......@@ -11,11 +11,15 @@ module BlameHelper
end
def age_map_class(commit_date, duration)
commit_date_days_ago = (duration[:now] - commit_date).to_i / 1.day
# Numbers 0 to 10 come from this calculation, but only commits on the oldest
# day get number 10 (all other numbers can be multiple days), so the range
# is normalized to 0-9
age_group = [(10 * commit_date_days_ago) / duration[:started_days_ago], 9].min
"blame-commit-age-#{age_group}"
if duration[:started_days_ago] == 0
"blame-commit-age-0"
else
commit_date_days_ago = (duration[:now] - commit_date).to_i / 1.day
# Numbers 0 to 10 come from this calculation, but only commits on the oldest
# day get number 10 (all other numbers can be multiple days), so the range
# is normalized to 0-9
age_group = [(10 * commit_date_days_ago) / duration[:started_days_ago], 9].min
"blame-commit-age-#{age_group}"
end
end
end
......@@ -22,4 +22,16 @@ module BreadcrumbsHelper
@breadcrumb_title = title
end
def breadcrumb_list_item(link)
content_tag "li" do
link + icon("angle-right", class: "breadcrumbs-list-angle")
end
end
def add_to_breadcrumb_dropdown(link, location: :before)
@breadcrumb_dropdown_links ||= {}
@breadcrumb_dropdown_links[location] ||= []
@breadcrumb_dropdown_links[location] << link
end
end
......@@ -15,18 +15,20 @@ module GroupsHelper
@has_group_title = true
full_title = ''
group.ancestors.reverse.each do |parent|
full_title += group_title_link(parent, hidable: true)
full_title += '<span class="hidable"> / </span>'.html_safe
group.ancestors.reverse.each_with_index do |parent, index|
if index > 0
add_to_breadcrumb_dropdown(group_title_link(parent, hidable: false, show_avatar: true), location: :before)
else
full_title += breadcrumb_list_item group_title_link(parent, hidable: false)
end
end
full_title += group_title_link(group)
full_title += ' &middot; '.html_safe + link_to(simple_sanitize(name), url, class: 'group-path') if name
full_title += render "layouts/nav/breadcrumbs/collapsed_dropdown", location: :before, title: _("Show parent subgroups")
content_tag :span, class: 'group-title' do
full_title.html_safe
end
full_title += breadcrumb_list_item group_title_link(group)
full_title += ' &middot; '.html_safe + link_to(simple_sanitize(name), url, class: 'group-path breadcrumb-item-text js-breadcrumb-item-text') if name
full_title.html_safe
end
def projects_lfs_status(group)
......@@ -65,11 +67,11 @@ module GroupsHelper
private
def group_title_link(group, hidable: false)
link_to(group_path(group), class: "group-path #{'hidable' if hidable}") do
def group_title_link(group, hidable: false, show_avatar: false)
link_to(group_path(group), class: "group-path breadcrumb-item-text js-breadcrumb-item-text #{'hidable' if hidable}") do
output =
if show_new_nav? && !Rails.env.test?
image_tag(group_icon(group), class: "avatar-tile", width: 16, height: 16)
if (group.try(:avatar_url) || show_avatar) && !Rails.env.test?
image_tag(group_icon(group), class: "avatar-tile", width: 15, height: 15)
else
""
end
......
......@@ -126,22 +126,20 @@ module IssuablesHelper
end
def issuable_meta(issuable, project, text)
output = content_tag(:strong, class: "identifier") do
concat("#{text} ")
concat(to_url_reference(issuable))
end
output << " opened #{time_ago_with_tooltip(issuable.created_at)} by ".html_safe
output = ""
output << "Opened #{time_ago_with_tooltip(issuable.created_at)} by ".html_safe
output << content_tag(:strong) do
author_output = link_to_member(project, issuable.author, size: 24, mobile_classes: "hidden-xs", tooltip: true)
author_output << link_to_member(project, issuable.author, size: 24, by_username: true, avatar: false, mobile_classes: "hidden-sm hidden-md hidden-lg")
end
output << "&ensp;".html_safe
output << content_tag(:span, (issuable_first_contribution_icon if issuable.first_contribution?), class: 'has-tooltip', title: _('1st contribution!'))
output << content_tag(:span, (issuable.task_status if issuable.tasks?), id: "task_status", class: "hidden-xs hidden-sm")
output << content_tag(:span, (issuable.task_status_short if issuable.tasks?), id: "task_status_short", class: "hidden-md hidden-lg")
output
output.html_safe
end
def issuable_todo(issuable)
......@@ -173,6 +171,13 @@ module IssuablesHelper
html.html_safe
end
def issuable_first_contribution_icon
content_tag(:span, class: 'fa-stack') do
concat(icon('certificate', class: "fa-stack-2x"))
concat(content_tag(:strong, '1', class: 'fa-inverse fa-stack-1x'))
end
end
def assigned_issuables_count(issuable_type)
case issuable_type
when :issues
......
......@@ -21,25 +21,28 @@ module MarkupHelper
end
# Use this in places where you would normally use link_to(gfm(...), ...).
#
def link_to_markdown(body, url, html_options = {})
return '' if body.blank?
link_to_html(markdown(body, pipeline: :single_line), url, html_options)
end
def link_to_markdown_field(object, field, url, html_options = {})
rendered_field = markdown_field(object, field)
link_to_html(rendered_field, url, html_options)
end
# It solves a problem occurring with nested links (i.e.
# "<a>outer text <a>gfm ref</a> more outer text</a>"). This will not be
# interpreted as intended. Browsers will parse something like
# "<a>outer text </a><a>gfm ref</a> more outer text" (notice the last part is
# not linked any more). link_to_gfm corrects that. It wraps all parts to
# not linked any more). link_to_html corrects that. It wraps all parts to
# explicitly produce the correct linking behavior (i.e.
# "<a>outer text </a><a>gfm ref</a><a> more outer text</a>").
def link_to_gfm(body, url, html_options = {})
return '' if body.blank?
def link_to_html(redacted, url, html_options = {})
fragment = Nokogiri::HTML::DocumentFragment.parse(redacted)
context = {
project: @project,
current_user: (current_user if defined?(current_user)),
pipeline: :single_line
}
gfm_body = Banzai.render(body, context)
fragment = Nokogiri::HTML::DocumentFragment.parse(gfm_body)
if fragment.children.size == 1 && fragment.children[0].name == 'a'
# Fragment has only one node, and it's a link generated by `gfm`.
# Replace it with our requested link.
......@@ -82,7 +85,10 @@ module MarkupHelper
def markdown_field(object, field)
object = object.for_display if object.respond_to?(:for_display)
redacted_field_html = object.try(:"redacted_#{field}_html")
return '' unless object.present?
return redacted_field_html if redacted_field_html
html = Banzai.render_field(object, field)
prepare_for_rendering(html, object.banzai_render_context(field))
......
module NavHelper
def page_with_sidebar_class
class_name = page_gutter_class
class_name << 'page-with-new-sidebar' if defined?(@new_sidebar) && @new_sidebar
class_name << 'page-with-icon-sidebar' if collapsed_sidebar? && @new_sidebar
class_name << 'page-with-new-sidebar' if defined?(@left_sidebar) && @left_sidebar
class_name << 'page-with-icon-sidebar' if collapsed_sidebar? && @left_sidebar
class_name
end
......@@ -30,23 +30,6 @@ module NavHelper
end
end
def nav_header_class
class_names = []
class_names << 'with-horizontal-nav' if defined?(nav) && nav
class_names
end
def layout_nav_class
return 'page-with-new-nav' if show_new_nav?
class_names = []
class_names << 'page-with-layout-nav' if defined?(nav) && nav
class_names << 'page-with-sub-nav' if content_for?(:sub_nav)
class_names
end
def nav_control_class
"nav-control" if current_user
end
......
......@@ -73,7 +73,7 @@ module NotesHelper
end
def note_max_access_for_user(note)
note.project.team.human_max_access(note.author_id)
note.project.team.max_member_access(note.author_id)
end
def discussion_path(discussion)
......@@ -146,4 +146,8 @@ module NotesHelper
autocomplete: autocomplete
}
end
def discussion_resolved_intro(discussion)
discussion.resolved_by_push? ? 'Automatically resolved' : 'Resolved'
end
end
......@@ -4,7 +4,7 @@ module PageLayoutHelper
@page_title.push(*titles.compact) if titles.any?
if show_new_nav? && titles.any? && !defined?(@breadcrumb_title)
if titles.any? && !defined?(@breadcrumb_title)
@breadcrumb_title = @page_title.last
end
......@@ -80,7 +80,9 @@ module PageLayoutHelper
@header_title = title
@header_title_url = title_url
else
@header_title_url ? link_to(@header_title, @header_title_url) : @header_title
return @header_title unless @header_title_url
breadcrumb_list_item(link_to(@header_title, @header_title_url))
end
end
......
module ProfilesHelper
def email_provider_label
return unless current_user.external_email?
current_user.email_provider.present? ? Gitlab::OAuth::Provider.label_for(current_user.email_provider) : "LDAP"
def attribute_provider_label(attribute)
user_synced_attributes_metadata = current_user.user_synced_attributes_metadata
if user_synced_attributes_metadata&.synced?(attribute)
if user_synced_attributes_metadata.provider
Gitlab::OAuth::Provider.label_for(user_synced_attributes_metadata.provider)
else
'LDAP'
end
end
end
end
......@@ -54,25 +54,28 @@ module ProjectsHelper
def project_title(project)
namespace_link =
if project.group
group_title(project.group)
group_title(project.group, nil, nil)
else
owner = project.namespace.owner
link_to(simple_sanitize(owner.name), user_path(owner))
end
project_link = link_to project_path(project), { class: "project-item-select-holder" } do
project_link = link_to project_path(project) do
output =
if show_new_nav? && !Rails.env.test?
project_icon(project, alt: project.name, class: 'avatar-tile', width: 16, height: 16)
if project.avatar_url && !Rails.env.test?
project_icon(project, alt: project.name, class: 'avatar-tile', width: 15, height: 15)
else
""
end
output << simple_sanitize(project.name)
output << content_tag("span", simple_sanitize(project.name), class: "breadcrumb-item-text js-breadcrumb-item-text")
output.html_safe
end
"#{namespace_link} / #{project_link}".html_safe
namespace_link = breadcrumb_list_item(namespace_link) unless project.group
project_link = breadcrumb_list_item project_link
"#{namespace_link} #{project_link}".html_safe
end
def remove_project_message(project)
......
......@@ -10,6 +10,7 @@ module SearchHelper
search_pattern = Regexp.new(Regexp.escape(term), "i")
generic_results = project_autocomplete + default_autocomplete + help_autocomplete
generic_results.concat(default_autocomplete_admin) if current_user.admin?
generic_results.select! { |result| result[:label] =~ search_pattern }
[
......@@ -41,8 +42,14 @@ module SearchHelper
[
{ category: "Settings", label: "User settings", url: profile_path },
{ category: "Settings", label: "SSH Keys", url: profile_keys_path },
{ category: "Settings", label: "Dashboard", url: root_path },
{ category: "Settings", label: "Admin Section", url: admin_root_path }
{ category: "Settings", label: "Dashboard", url: root_path }
]
end
# Autocomplete results for settings pages, for admins
def default_autocomplete_admin
[
{ category: "Settings", label: "Admin Section", url: admin_root_path }
]
end
......
......@@ -10,4 +10,15 @@ module WikiHelper
.map { |dir_or_page| WikiPage.unhyphenize(dir_or_page).capitalize }
.join(' / ')
end
def wiki_breadcrumb_dropdown_links(page_slug)
page_slug_split = page_slug.split('/')
page_slug_split.pop(1)
current_slug = ""
page_slug_split
.map do |dir_or_page|
current_slug = "#{current_slug}#{dir_or_page}/"
add_to_breadcrumb_dropdown link_to(WikiPage.unhyphenize(dir_or_page).capitalize, project_wiki_path(@project, current_slug)), location: :after
end
end
end
......@@ -27,7 +27,6 @@ module Ci
validates :coverage, numericality: true, allow_blank: true
validates :ref, presence: true
validates :protected, inclusion: { in: [true, false], unless: :importing? }, on: :create
scope :unstarted, ->() { where(runner_id: nil) }
scope :ignore_failures, ->() { where(allow_failure: false) }
......@@ -451,6 +450,10 @@ module Ci
trace
end
def serializable_hash(options = {})
super(options).merge(when: read_attribute(:when))
end
private
def update_artifacts_size
......
......@@ -36,7 +36,6 @@ module Ci
validates :sha, presence: { unless: :importing? }
validates :ref, presence: { unless: :importing? }
validates :status, presence: { unless: :importing? }
validates :protected, inclusion: { in: [true, false], unless: :importing? }, on: :create
validate :valid_commit_sha, unless: :importing?
after_create :keep_around_commits, unless: :importing?
......
......@@ -16,6 +16,8 @@ class Commit
participant :notes_with_associations
attr_accessor :project, :author
attr_accessor :redacted_description_html
attr_accessor :redacted_title_html
DIFF_SAFE_LINES = Gitlab::Git::DiffCollection::DEFAULT_LIMITS[:max_lines]
......@@ -26,6 +28,13 @@ class Commit
# The SHA can be between 7 and 40 hex characters.
COMMIT_SHA_PATTERN = '\h{7,40}'.freeze
def banzai_render_context(field)
context = { pipeline: :single_line, project: self.project }
context[:author] = self.author if self.author
context
end
class << self
def decorate(commits, project)
commits.map do |commit|
......
......@@ -334,4 +334,11 @@ module Issuable
metrics = self.metrics || create_metrics
metrics.record!
end
##
# Override in issuable specialization
#
def first_contribution?
false
end
end
......@@ -24,6 +24,7 @@ module ResolvableDiscussion
delegate :resolved_at,
:resolved_by,
:resolved_by_push?,
to: :last_resolved_note,
allow_nil: true
......
......@@ -51,22 +51,34 @@ module ResolvableNote
end
# If you update this method remember to also update `.resolve!`
def resolve!(current_user)
return unless resolvable?
return if resolved?
def resolve_without_save(current_user, resolved_by_push: false)
return false unless resolvable?
return false if resolved?
self.resolved_at = Time.now
self.resolved_by = current_user
save!
self.resolved_by_push = resolved_by_push
true
end
# If you update this method remember to also update `.unresolve!`
def unresolve!
return unless resolvable?
return unless resolved?
def unresolve_without_save
return false unless resolvable?
return false unless resolved?
self.resolved_at = nil
self.resolved_by = nil
save!
true
end
def resolve!(current_user, resolved_by_push: false)
resolve_without_save(current_user, resolved_by_push: resolved_by_push) &&
save!
end
def unresolve!
unresolve_without_save && save!
end
end
......@@ -918,6 +918,12 @@ class MergeRequest < ActiveRecord::Base
active_diff_discussions.each do |discussion|
service.execute(discussion)
end
if project.resolve_outdated_diff_discussions?
MergeRequests::ResolvedDiscussionNotificationService
.new(project, current_user)
.execute(self)
end
end
def keep_around_commit
......@@ -954,6 +960,12 @@ class MergeRequest < ActiveRecord::Base
Projects::OpenMergeRequestsCountService.new(target_project).refresh_cache
end
def first_contribution?
return false if project.team.max_member_access(author_id) > Gitlab::Access::GUEST
project.merge_requests.merged.where(author_id: author_id).empty?
end
private
def write_ref
......
......@@ -15,6 +15,16 @@ class Note < ActiveRecord::Base
include IgnorableColumn
include Editable
module SpecialRole
FIRST_TIME_CONTRIBUTOR = :first_time_contributor
class << self
def values
constants.map {|const| self.const_get(const)}
end
end
end
ignore_column :original_discussion_id
cache_markdown_field :note, pipeline: :note, issuable_state_filter_enabled: true
......@@ -32,9 +42,12 @@ class Note < ActiveRecord::Base
# Banzai::ObjectRenderer
attr_accessor :user_visible_reference_count
# Attribute used to store the attributes that have ben changed by quick actions.
# Attribute used to store the attributes that have been changed by quick actions.
attr_accessor :commands_changes
# A special role that may be displayed on issuable's discussions
attr_accessor :special_role
default_value_for :system, false
attr_mentionable :note, pipeline: :note
......@@ -141,6 +154,10 @@ class Note < ActiveRecord::Base
.group(:noteable_id)
.where(noteable_type: type, noteable_id: ids)
end
def has_special_role?(role, note)
note.special_role == role
end
end
def cross_reference?
......@@ -206,6 +223,22 @@ class Note < ActiveRecord::Base
super(noteable_type.to_s.classify.constantize.base_class.to_s)
end
def special_role=(role)
raise "Role is undefined, #{role} not found in #{SpecialRole.values}" unless SpecialRole.values.include?(role)
@special_role = role
end
def has_special_role?(role)
self.class.has_special_role?(role, self)
end
def specialize_for_first_contribution!(noteable)
return unless noteable.author_id == self.author_id
self.special_role = Note::SpecialRole::FIRST_TIME_CONTRIBUTOR
end
def editable?
!system?
end
......
......@@ -37,6 +37,7 @@ class Project < ActiveRecord::Base
default_value_for :archived, false
default_value_for :visibility_level, gitlab_config_features.visibility_level
default_value_for :resolve_outdated_diff_discussions, false
default_value_for :container_registry_enabled, gitlab_config_features.container_registry
default_value_for(:repository_storage) { current_application_settings.pick_repository_storage }
default_value_for(:shared_runners_enabled) { current_application_settings.shared_runners_enabled }
......
......@@ -150,7 +150,7 @@ class ProjectTeam
end
def human_max_access(user_id)
Gitlab::Access.options_with_owner.key(max_member_access(user_id))
Gitlab::Access.human_access(max_member_access(user_id))
end
# Determine the maximum access level for a group of users in bulk.
......
......@@ -15,10 +15,12 @@ class User < ActiveRecord::Base
include IgnorableColumn
include FeatureGate
include CreatedAtFilterable
include IgnorableColumn
DEFAULT_NOTIFICATION_LEVEL = :participating
ignore_column :authorized_projects_populated
ignore_column :external_email
ignore_column :email_provider
add_authentication_token_field :authentication_token
add_authentication_token_field :incoming_email_token
......@@ -85,6 +87,7 @@ class User < ActiveRecord::Base
has_many :identities, dependent: :destroy, autosave: true # rubocop:disable Cop/ActiveRecordDependent
has_many :u2f_registrations, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :chat_names, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_one :user_synced_attributes_metadata, autosave: true
# Groups
has_many :members, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
......@@ -161,6 +164,7 @@ class User < ActiveRecord::Base
after_update :update_emails_with_primary_email, if: :email_changed?
before_save :ensure_authentication_token, :ensure_incoming_email_token
before_save :ensure_user_rights_and_limits, if: :external_changed?
before_save :skip_reconfirmation!, if: ->(user) { user.email_changed? && user.read_only_attribute?(:email) }
after_save :ensure_namespace_correct
after_commit :update_invalid_gpg_signatures, on: :update, if: -> { previous_changes.key?('email') }
after_initialize :set_projects_limit
......@@ -1045,6 +1049,22 @@ class User < ActiveRecord::Base
self.email == email
end
def sync_attribute?(attribute)
return true if ldap_user? && attribute == :email
attributes = Gitlab.config.omniauth.sync_profile_attributes
if attributes.is_a?(Array)
attributes.include?(attribute.to_s)
else
attributes
end
end
def read_only_attribute?(attribute)
user_synced_attributes_metadata&.read_only?(attribute)
end
protected
# override, from Devise::Validatable
......
class UserSyncedAttributesMetadata < ActiveRecord::Base
belongs_to :user
validates :user, presence: true
SYNCABLE_ATTRIBUTES = %i[name email location].freeze
def read_only?(attribute)
Gitlab.config.omniauth.sync_profile_from_provider && synced?(attribute)
end
def read_only_attributes
return [] unless Gitlab.config.omniauth.sync_profile_from_provider
SYNCABLE_ATTRIBUTES.select { |key| synced?(key) }
end
def synced?(attribute)
read_attribute("#{attribute}_synced")
end
def set_attribute_synced(attribute, value)
write_attribute("#{attribute}_synced", value)
end
end
......@@ -10,6 +10,10 @@ module Discussions
discussion.notes.each do |note|
if outdated
note.change_position = position
if project.resolve_outdated_diff_discussions?
note.resolve_without_save(current_user, resolved_by_push: true)
end
else
note.position = position
note.change_position = nil
......
......@@ -34,6 +34,10 @@ module Users
private
def assign_attributes(&block)
if @user.user_synced_attributes_metadata
params.except!(*@user.user_synced_attributes_metadata.read_only_attributes)
end
@user.assign_attributes(params) if params.any?
end
end
......
- add_to_breadcrumbs "Applications", admin_applications_path
- breadcrumb_title @application.name
- page_title "Edit", @application.name, "Applications"
%h3.page-title Edit application
......
- breadcrumb_title "Cohorts"
- @no_container = true
= render "admin/dashboard/head"
......
- @no_container = true
- breadcrumb_title "Dashboard"
= render "admin/dashboard/head"
%div{ class: container_class }
......
- add_to_breadcrumbs "Groups", admin_groups_path
- breadcrumb_title @group.name
- page_title @group.name, "Groups"
%h3.page-title
Group: #{@group.full_name}
......
- add_to_breadcrumbs "System Hooks", admin_hooks_path
- page_title 'Edit System Hook'
%h3.page-title
Edit System Hook
......
- breadcrumb_title "Jobs"
- @no_container = true
= render "admin/dashboard/head"
......
- add_to_breadcrumbs "Labels", admin_labels_path
- breadcrumb_title "Edit Label"
- page_title "Edit", @label.name, "Labels"
%h3.page-title
Edit Label
......
- add_to_breadcrumbs "Projects", admin_projects_path
- breadcrumb_title @project.name_with_namespace
- page_title @project.name_with_namespace, "Projects"
%h3.page-title
Project: #{@project.name_with_namespace}
......
- breadcrumb_title "Runners"
- @no_container = true
= render "admin/dashboard/head"
......
- add_to_breadcrumbs "Service Templates", admin_application_settings_services_path
- breadcrumb_title @service.title
- page_title @service.title, "Service Templates"
= render 'form'
- add_to_breadcrumbs "Users", admin_users_path
- breadcrumb_title @user.name
- page_title @user.name, "Users"
= render 'admin/users/head'
......
%h4.prepend-top-0
Secret variables
= link_to icon('question-circle'), help_page_path('ci/variables/README', anchor: 'secret-variables'), target: '_blank'
%p
These variables will be set to environment by the runner, and could be protected by exposing only to protected branches or tags.
%p
So you can use them for passwords, secret keys or whatever you want.
%p
The value of the variable can be visible in job log if explicitly asked to do so.
%p.append-bottom-default
Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags.
You can use variables for passwords, secret keys, or whatever you want.
.row.prepend-top-default.append-bottom-default
.col-lg-4
= render "ci/variables/content"
.col-lg-8
.col-lg-12
%h5.prepend-top-0
Add a variable
= render "ci/variables/form", btn_text: "Add new variable"
......
......@@ -4,6 +4,6 @@
.col-lg-3
= render "ci/variables/content"
.col-lg-9
%h5.prepend-top-0
%h4.prepend-top-0
Update variable
= render "ci/variables/form", btn_text: "Save variable"
- if show_new_nav? && current_user.can_create_group?
- if current_user.can_create_group?
- content_for :breadcrumbs_extra do
= link_to "New group", new_group_path, class: "btn btn-new"
......@@ -10,8 +10,8 @@
= nav_link(page: explore_groups_path) do
= link_to explore_groups_path, title: 'Explore public groups' do
Explore public groups
.nav-controls{ class: ("nav-controls-new-nav" if show_new_nav?) }
.nav-controls.nav-controls-new-nav
= render 'shared/groups/search_form'
= render 'shared/groups/dropdown'
- if current_user.can_create_group?
= link_to "New group", new_group_path, class: "btn btn-new #{("visible-xs" if show_new_nav?)}"
= link_to "New group", new_group_path, class: "btn btn-new visible-xs"
= content_for :flash_message do
= render 'shared/project_limit'
- if show_new_nav? && current_user.can_create_project?
- if current_user.can_create_project?
- content_for :breadcrumbs_extra do
= link_to "New project", new_project_path, class: 'btn btn-new'
......@@ -19,8 +19,8 @@
= link_to explore_root_path, title: 'Explore', data: {placement: 'right'} do
Explore projects
.nav-controls{ class: ("nav-controls-new-nav" if show_new_nav?) }
.nav-controls.nav-controls-new-nav
= render 'shared/projects/search_form'
= render 'shared/projects/dropdown'
- if current_user.can_create_project?
= link_to "New project", new_project_path, class: "btn btn-new #{("visible-xs" if show_new_nav?)}"
= link_to "New project", new_project_path, class: "btn btn-new visible-xs"
- if show_new_nav? && current_user
- if current_user
- content_for :breadcrumbs_extra do
= link_to "New snippet", new_snippet_path, class: "btn btn-new", title: "New snippet"
......@@ -10,7 +10,3 @@
= nav_link(page: explore_snippets_path) do
= link_to explore_snippets_path, title: 'Explore snippets', data: {placement: 'right'} do
Explore Snippets
- if current_user
.nav-controls.hidden-xs{ class: ("hidden-sm hidden-md hidden-lg" if show_new_nav?) }
= link_to "New snippet", new_snippet_path, class: "btn btn-new", title: "New snippet"
......@@ -4,15 +4,14 @@
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, params.merge(rss_url_options), title: "#{current_user.name} issues")
- if show_new_nav?
- content_for :breadcrumbs_extra do
= link_to params.merge(rss_url_options), class: 'btn has-tooltip append-right-10', title: 'Subscribe' do
= icon('rss')
= render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", with_feature_enabled: 'issues', type: :issues
- content_for :breadcrumbs_extra do
= link_to params.merge(rss_url_options), class: 'btn has-tooltip append-right-10', title: 'Subscribe' do
= icon('rss')
= render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", with_feature_enabled: 'issues', type: :issues
.top-area
= render 'shared/issuable/nav', type: :issues
.nav-controls{ class: ("visible-xs" if show_new_nav?) }
.nav-controls.visible-xs
= link_to params.merge(rss_url_options), class: 'btn has-tooltip', title: 'Subscribe' do
= icon('rss')
= render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", with_feature_enabled: 'issues', type: :issues
......
......@@ -2,13 +2,12 @@
- page_title "Merge Requests"
- header_title "Merge Requests", merge_requests_dashboard_path(assignee_id: current_user.id)
- if show_new_nav?
- content_for :breadcrumbs_extra do
= render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request", with_feature_enabled: 'merge_requests', type: :merge_requests
- content_for :breadcrumbs_extra do
= render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request", with_feature_enabled: 'merge_requests', type: :merge_requests
.top-area
= render 'shared/issuable/nav', type: :merge_requests
.nav-controls{ class: ("visible-xs" if show_new_nav?) }
.nav-controls.visible-xs
= render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request", with_feature_enabled: 'merge_requests', type: :merge_requests
= render 'shared/issuable/filter', type: :merge_requests
......
......@@ -2,14 +2,13 @@
- page_title 'Milestones'
- header_title 'Milestones', dashboard_milestones_path
- if show_new_nav?
- content_for :breadcrumbs_extra do
= render 'shared/new_project_item_select', path: 'milestones/new', label: 'New milestone', include_groups: true, type: :milestones
- content_for :breadcrumbs_extra do
= render 'shared/new_project_item_select', path: 'milestones/new', label: 'New milestone', include_groups: true, type: :milestones
.top-area
= render 'shared/milestones_filter', counts: @milestone_states
.nav-controls{ class: ("visible-xs" if show_new_nav?) }
.nav-controls.visible-xs
= render 'shared/new_project_item_select', path: 'milestones/new', label: 'New milestone', include_groups: true, type: :milestones
.milestones
......
- if discussion.resolved?
.discussion-headline-light.js-discussion-headline
Resolved
= discussion_resolved_intro(discussion)
- if discussion.resolved_by
by
= link_to_member(@project, discussion.resolved_by, avatar: false)
- if discussion.resolved_by_push?
with a push
= time_ago_with_tooltip(discussion.resolved_at, placement: "bottom")
- elsif discussion.updated?
.discussion-headline-light.js-discussion-headline
......
- breadcrumb_title "General Settings"
= render "groups/settings_head"
.panel.panel-default.prepend-top-default
.panel-heading
......
......@@ -8,7 +8,7 @@
= webpack_bundle_tag 'common_vue'
= webpack_bundle_tag 'filtered_search'
- if show_new_nav? && group_issues_exists
- if group_issues_exists
- content_for :breadcrumbs_extra do
= link_to params.merge(rss_url_options), class: 'btn btn-default append-right-10' do
= icon('rss')
......@@ -19,7 +19,7 @@
- if group_issues_exists
.top-area
= render 'shared/issuable/nav', type: :issues
.nav-controls{ class: ("visible-xs" if show_new_nav?) }
.nav-controls.visible-xs
= link_to params.merge(rss_url_options), class: 'btn' do
= icon('rss')
%span.icon-label
......
- page_title 'Labels'
- if show_new_nav? && can?(current_user, :admin_label, @group)
- if can?(current_user, :admin_label, @group)
- content_for :breadcrumbs_extra do
= link_to "New label", new_group_label_path(@group), class: "btn btn-new"
......@@ -10,7 +10,7 @@
.nav-text
Labels can be applied to issues and merge requests. Group labels are available for any project within the group.
.nav-controls{ class: ("visible-xs" if show_new_nav?) }
.nav-controls.visible-xs
- if can?(current_user, :admin_label, @group)
= link_to "New label", new_group_label_path(@group), class: "btn btn-new"
......
......@@ -4,7 +4,7 @@
= webpack_bundle_tag 'common_vue'
= webpack_bundle_tag 'filtered_search'
- if show_new_nav? && current_user
- if current_user
- content_for :breadcrumbs_extra do
= render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request", type: :merge_requests
......@@ -14,7 +14,7 @@
.top-area
= render 'shared/issuable/nav', type: :merge_requests
- if current_user
.nav-controls{ class: ("visible-xs" if show_new_nav?) }
.nav-controls.visible-xs
= render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request", type: :merge_requests
= render 'shared/issuable/search_bar', type: :merge_requests
......
- page_title "Milestones"
- if show_new_nav? && can?(current_user, :admin_milestones, @group)
- if can?(current_user, :admin_milestones, @group)
- content_for :breadcrumbs_extra do
= link_to "New milestone", new_group_milestone_path(@group), class: "btn btn-new"
......@@ -8,7 +8,7 @@
.top-area
= render 'shared/milestones_filter', counts: @milestone_states
.nav-controls{ class: ("visible-xs" if show_new_nav?) }
.nav-controls.visible-xs
- if can?(current_user, :admin_milestones, @group)
= link_to "New milestone", new_group_milestone_path(@group), class: "btn btn-new"
......
- breadcrumb_title "Projects"
= render "groups/settings_head"
.panel.panel-default.prepend-top-default
......
- page_title "Pipelines"
- breadcrumb_title "CI / CD Settings"
- page_title "CI / CD"
= render "groups/settings_head"
= render 'ci/variables/index'
- @no_container = true
- breadcrumb_title "Group"
- breadcrumb_title "Details"
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, group_url(@group, rss_url_options), title: "#{@group.name} activity")
......
- breadcrumb_title "Details"
- @no_container = true
= render 'head'
......
......@@ -32,9 +32,9 @@
= stylesheet_link_tag "test", media: "all" if Rails.env.test?
= stylesheet_link_tag 'performance_bar' if performance_bar_enabled?
- if show_new_nav?
= stylesheet_link_tag "new_nav", media: "all"
= stylesheet_link_tag "new_sidebar", media: "all"
// TODO: Combine these 2 stylesheets into application.scss
= stylesheet_link_tag "new_nav", media: "all"
= stylesheet_link_tag "new_sidebar", media: "all"
= Gon::Base.render_data
......
.page-with-sidebar{ class: page_with_sidebar_class }
- if show_new_nav?
- if defined?(nav) && nav
= render "layouts/nav/#{nav}"
- else
- if defined?(nav) && nav
.layout-nav
.container-fluid
= render "layouts/nav/#{nav}"
- if content_for?(:sub_nav)
= yield :sub_nav
.content-wrapper{ class: layout_nav_class }
- if show_new_nav?
.mobile-overlay
- if defined?(nav) && nav
= render "layouts/nav/sidebar/#{nav}"
.content-wrapper.page-with-new-nav
.mobile-overlay
.alert-wrapper
= render "layouts/broadcast"
- if show_new_nav?
- if content_for?(:new_global_flash)
= yield :new_global_flash
- unless @hide_breadcrumbs
= render "layouts/nav/breadcrumbs"
= render "layouts/flash"
= yield :flash_message
- unless @hide_breadcrumbs
= render "layouts/nav/breadcrumbs"
= render "layouts/flash"
%div{ class: "#{(container_class unless @no_container)} #{@content_class}" }
.content{ id: "content-body" }
= yield
- page_title "Admin Area"
- header_title "Admin Area", admin_root_path
- if show_new_nav?
- nav "new_admin_sidebar"
- @new_sidebar = true
- else
- nav "admin"
- nav "admin"
- @left_sidebar = true
= render template: "layouts/application"
......@@ -4,10 +4,7 @@
%body{ class: @body_class, data: { page: body_data_page, project: "#{@project.path if @project}", group: "#{@group.path if @group}", find_file: find_file_path } }
= render "layouts/init_auto_complete" if @gfm_form
= render 'peek/bar'
- if show_new_nav?
= render "layouts/header/new"
- else
= render "layouts/header/default", title: header_title
= render "layouts/header/default"
= render 'layouts/page', sidebar: sidebar, nav: nav
= yield :scripts_body
- page_title @group.name
- page_description @group.description unless page_description
- header_title group_title(@group) unless header_title
- if show_new_nav?
- nav "new_group_sidebar"
- @new_sidebar = true
- else
- nav "group"
- nav "group"
- @left_sidebar = true
= render template: "layouts/application"
%header.navbar.navbar-gitlab{ class: nav_header_class }
.navbar-border
%header.navbar.navbar-gitlab.navbar-gitlab-new
%a.sr-only.gl-accessibility{ href: "#content-body", tabindex: "1" } Skip to content
.container-fluid
.header-content
.dropdown.global-dropdown
%button.global-dropdown-toggle{ type: 'button', 'data-toggle' => 'dropdown' }
%span.sr-only Toggle navigation
= icon('bars')
.dropdown-menu-nav.global-dropdown-menu
- if current_user
= render 'layouts/nav/dashboard'
- else
= render 'layouts/nav/explore'
.header-logo
= link_to root_path, class: 'home', title: 'Dashboard', id: 'logo' do
= brand_header_logo
.title-container
%h1.title
= link_to root_path, title: 'Dashboard', id: 'logo' do
= brand_header_logo
%span.logo-text.hidden-xs
= render 'shared/logo_type.svg'
.title-container.js-title-container
%h1.title{ class: ('initializing' if @has_group_title) }= title
- if current_user
= render "layouts/nav/dashboard"
- else
= render "layouts/nav/explore"
.navbar-collapse.collapse
%ul.nav.navbar-nav
- if current_user
= render 'layouts/header/new_dropdown'
%li.hidden-sm.hidden-xs
= render 'layouts/search' unless current_controller?(:search)
%li.visible-sm-inline-block.visible-xs-inline-block
= link_to search_path, title: 'Search', aria: { label: "Search" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('search')
- if current_user
- if session[:impersonator_id]
%li.impersonation
= link_to admin_impersonation_path, method: :delete, title: "Stop impersonation", aria: { label: 'Stop impersonation' }, data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do
= icon('user-secret fw')
- if current_user.admin?
%li
= link_to admin_root_path, title: 'Admin area', aria: { label: "Admin area" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('wrench fw')
= render 'layouts/header/new_dropdown'
- if Gitlab::Sherlock.enabled?
%li
= link_to sherlock_transactions_path, title: 'Sherlock Transactions',
data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('tachometer fw')
%li.user-counter
= link_to assigned_issues_dashboard_path, title: 'Issues', aria: { label: "Issues" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= link_to assigned_issues_dashboard_path, title: 'Issues', class: 'dashboard-shortcuts-issues', aria: { label: "Issues" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= custom_icon('issues')
- issues_count = assigned_issuables_count(:issues)
%span.badge.issues-count{ class: ('hidden' if issues_count.zero?) }
= number_with_delimiter(issues_count)
%li.user-counter
= link_to assigned_mrs_dashboard_path, title: 'Merge requests', aria: { label: "Merge requests" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= link_to assigned_mrs_dashboard_path, title: 'Merge requests', class: 'dashboard-shortcuts-merge_requests', aria: { label: "Merge requests" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= custom_icon('mr_bold')
- merge_requests_count = assigned_issuables_count(:merge_requests)
%span.badge.merge-requests-count{ class: ('hidden' if merge_requests_count.zero?) }
......@@ -60,9 +42,9 @@
%span.badge.todos-count{ class: ('hidden' if todos_pending_count.zero?) }
= todos_count_format(todos_pending_count)
%li.header-user.dropdown
= link_to current_user, class: "header-user-dropdown-toggle", data: { toggle: "dropdown" } do
= image_tag avatar_icon(current_user, 26), width: 26, height: 26, class: "header-user-avatar"
= icon('caret-down')
= link_to current_user, class: user_dropdown_class, data: { toggle: "dropdown" } do
= image_tag avatar_icon(current_user, 23), width: 23, height: 23, class: "header-user-avatar"
= custom_icon('caret_down')
.dropdown-menu-nav.dropdown-menu-align-right
%ul
%li.current-user
......@@ -74,18 +56,24 @@
= link_to "Profile", current_user, class: 'profile-link', data: { user: current_user.username }
%li
= link_to "Settings", profile_path
- if current_user
%li
= link_to "Help", help_path
%li.divider
%li
= link_to "Sign out", destroy_user_session_path, method: :delete, class: "sign-out-link"
- if session[:impersonator_id]
%li.impersonation
= link_to admin_impersonation_path, class: 'impersonation-btn', method: :delete, title: "Stop impersonation", aria: { label: 'Stop impersonation' }, data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do
= icon('user-secret')
- else
%li
%div
= link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in btn-success'
= link_to "Sign in / Register", new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in'
%button.navbar-toggle{ type: 'button' }
%button.navbar-toggle.hidden-sm.hidden-md.hidden-lg{ type: 'button' }
%span.sr-only Toggle navigation
= icon('ellipsis-v')
= yield :header_content
= icon('ellipsis-v', class: 'js-navbar-toggle-right')
= icon('times', class: 'js-navbar-toggle-left')
= render 'shared/outdated_browser'
%header.navbar.navbar-gitlab.navbar-gitlab-new{ class: nav_header_class }
%a.sr-only.gl-accessibility{ href: "#content-body", tabindex: "1" } Skip to content
.container-fluid
.header-content
.title-container
%h1.title
= link_to root_path, title: 'Dashboard', id: 'logo' do
= brand_header_logo
%span.logo-text.hidden-xs
= render 'shared/logo_type.svg'
- if current_user
= render "layouts/nav/new_dashboard"
- else
= render "layouts/nav/new_explore"
.navbar-collapse.collapse
%ul.nav.navbar-nav
- if current_user
= render 'layouts/header/new_dropdown'
%li.hidden-sm.hidden-xs
= render 'layouts/search' unless current_controller?(:search)
%li.visible-sm-inline-block.visible-xs-inline-block
= link_to search_path, title: 'Search', aria: { label: "Search" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('search')
- if current_user
%li.user-counter
= link_to assigned_issues_dashboard_path, title: 'Issues', class: 'dashboard-shortcuts-issues', aria: { label: "Issues" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= custom_icon('issues')
- issues_count = assigned_issuables_count(:issues)
%span.badge.issues-count{ class: ('hidden' if issues_count.zero?) }
= number_with_delimiter(issues_count)
%li.user-counter
= link_to assigned_mrs_dashboard_path, title: 'Merge requests', class: 'dashboard-shortcuts-merge_requests', aria: { label: "Merge requests" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= custom_icon('mr_bold')
- merge_requests_count = assigned_issuables_count(:merge_requests)
%span.badge.merge-requests-count{ class: ('hidden' if merge_requests_count.zero?) }
= number_with_delimiter(merge_requests_count)
%li.user-counter
= link_to dashboard_todos_path, title: 'Todos', aria: { label: "Todos" }, class: 'shortcuts-todos', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= custom_icon('todo_done')
%span.badge.todos-count{ class: ('hidden' if todos_pending_count.zero?) }
= todos_count_format(todos_pending_count)
%li.header-user.dropdown
= link_to current_user, class: user_dropdown_class, data: { toggle: "dropdown" } do
= image_tag avatar_icon(current_user, 23), width: 23, height: 23, class: "header-user-avatar"
= custom_icon('caret_down')
.dropdown-menu-nav.dropdown-menu-align-right
%ul
%li.current-user
.user-name.bold
= current_user.name
@#{current_user.username}
%li.divider
%li
= link_to "Profile", current_user, class: 'profile-link', data: { user: current_user.username }
%li
= link_to "Settings", profile_path
- if current_user
%li
= link_to "Help", help_path
%li.divider
%li
= link_to "Sign out", destroy_user_session_path, method: :delete, class: "sign-out-link"
- if session[:impersonator_id]
%li.impersonation
= link_to admin_impersonation_path, class: 'impersonation-btn', method: :delete, title: "Stop impersonation", aria: { label: 'Stop impersonation' }, data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do
= icon('user-secret')
- else
%li
%div
= link_to "Sign in / Register", new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in'
%button.navbar-toggle.hidden-sm.hidden-md.hidden-lg{ type: 'button' }
%span.sr-only Toggle navigation
= icon('ellipsis-v', class: 'js-navbar-toggle-right')
= icon('times', class: 'js-navbar-toggle-left')
= render 'shared/outdated_browser'
%li.header-new.dropdown
= link_to new_project_path, class: "header-new-dropdown-toggle has-tooltip", title: "New...", ref: 'tooltip', aria: { label: "New..." }, data: { toggle: 'dropdown', placement: 'bottom', container: 'body' } do
- if show_new_nav?
= custom_icon('plus_square')
= custom_icon('caret_down')
- else
= icon('plus fw')
= custom_icon('caret_down')
= custom_icon('plus_square')
= custom_icon('caret_down')
.dropdown-menu-nav.dropdown-menu-align-right
%ul
- if @group&.persisted?
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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