Commit 4d2315a3 authored by Simon Knox's avatar Simon Knox

Merge branch '2518-persisted-issue-boards-filter-be' of...

Merge branch '2518-persisted-issue-boards-filter-be' of gitlab.com:gitlab-org/gitlab-ee into edit-board
parents 59d29e78 f753e99b
Please view this file on the master branch, on stable branches it's out of date.
## 10.0.2 (2017-09-27)
- [FIXED] Send valid project path as name for Jira dev panel.
- [FIXED] Fix delta size check to handle commit or nil objects.
## 10.0.1 (2017-09-23)
- No changes.
## 10.0.0 (2017-09-22)
- [SECURITY] Check if LDAP users are in external groups on login. !2720
......
......@@ -2,6 +2,17 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
## 10.0.2 (2017-09-27)
- [FIXED] Notes will not show an empty bubble when the author isn't a member. !14450
- [FIXED] Some checks in `rake gitlab:check` were failling with 'undefined method `run_command`'. !14469
- [FIXED] Make locked setting of Runner to not affect jobs scheduling. !14483
- [FIXED] Re-allow `name` attribute on user-provided anchor HTML.
## 10.0.1 (2017-09-23)
- [FIXED] Fix duplicate key errors in PostDeployMigrateUserExternalMailData migration.
## 10.0.0 (2017-09-22)
- [SECURITY] Upgrade brace-expansion NPM package due to security issue. !13665 (Markus Koller)
......
9.6.0-pre
10.1.0-pre
......@@ -24,6 +24,9 @@ const categoryLabelMap = {
flags: 'Flags',
};
const IS_VISIBLE = 'is-visible';
const IS_RENDERED = 'is-rendered';
class AwardsHandler {
constructor(emoji) {
this.emoji = emoji;
......@@ -51,7 +54,7 @@ class AwardsHandler {
if (!$target.closest('.emoji-menu').length) {
if ($('.emoji-menu').is(':visible')) {
$('.js-add-award.is-active').removeClass('is-active');
$('.emoji-menu').removeClass('is-visible');
this.hideMenuElement($('.emoji-menu'));
}
}
});
......@@ -88,12 +91,12 @@ class AwardsHandler {
if ($menu.length) {
if ($menu.is('.is-visible')) {
$addBtn.removeClass('is-active');
$menu.removeClass('is-visible');
this.hideMenuElement($menu);
$('.js-emoji-menu-search').blur();
} else {
$addBtn.addClass('is-active');
this.positionMenu($menu, $addBtn);
$menu.addClass('is-visible');
this.showMenuElement($menu);
$('.js-emoji-menu-search').focus();
}
} else {
......@@ -103,7 +106,7 @@ class AwardsHandler {
$addBtn.removeClass('is-loading');
this.positionMenu($createdMenu, $addBtn);
return setTimeout(() => {
$createdMenu.addClass('is-visible');
this.showMenuElement($createdMenu);
$('.js-emoji-menu-search').focus();
}, 200);
});
......@@ -241,7 +244,8 @@ class AwardsHandler {
if (isInIssuePage() && !isMainAwardsBlock) {
const id = votesBlock.attr('id').replace('note_', '');
$('.emoji-menu').removeClass('is-visible');
this.hideMenuElement($('.emoji-menu'));
$('.js-add-award.is-active').removeClass('is-active');
const toggleAwardEvent = new CustomEvent('toggleAward', {
detail: {
......@@ -261,7 +265,8 @@ class AwardsHandler {
return typeof callback === 'function' ? callback() : undefined;
});
$('.emoji-menu').removeClass('is-visible');
this.hideMenuElement($('.emoji-menu'));
return $('.js-add-award.is-active').removeClass('is-active');
}
......@@ -529,6 +534,33 @@ class AwardsHandler {
return $matchingElements.closest('li').clone();
}
/* showMenuElement and hideMenuElement are performance optimizations. We use
* opacity to show/hide the emoji menu, because we can animate it. But opacity
* leaves hidden elements in the render tree, which is unacceptable given the number
* of emoji elements in the emoji menu (5k+). To get the best of both worlds, we separately
* apply IS_RENDERED to add/remove the menu from the render tree and IS_VISIBLE to animate
* the menu being opened and closed. */
showMenuElement($emojiMenu) {
$emojiMenu.addClass(IS_RENDERED);
// enqueues animation as a microtask, so it begins ASAP once IS_RENDERED added
return Promise.resolve()
.then(() => $emojiMenu.addClass(IS_VISIBLE));
}
hideMenuElement($emojiMenu) {
$emojiMenu.on(transitionEndEventString, (e) => {
if (e.currentTarget === e.target) {
$emojiMenu
.removeClass(IS_RENDERED)
.off(transitionEndEventString);
}
});
$emojiMenu.removeClass(IS_VISIBLE);
}
destroy() {
this.eventListeners.forEach((entry) => {
entry.element.off.call(entry.element, ...entry.args);
......
......@@ -3,6 +3,7 @@
import '../lib/utils/url_utility';
import { HIDDEN_CLASS } from '../lib/utils/constants';
import csrf from '../lib/utils/csrf';
function toggleLoading($el, $icon, loading) {
if (loading) {
......@@ -36,9 +37,7 @@ export default class BlobFileDropzone {
maxFiles: 1,
addRemoveLinks: true,
previewsContainer: '.dropzone-previews',
headers: {
'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content'),
},
headers: csrf.headers,
init: function () {
this.on('addedfile', function () {
toggleLoading(submitButton, submitButtonLoadingIcon, false);
......
......@@ -11,14 +11,22 @@
function ImageFile(file) {
this.file = file;
this.requestImageInfo($('.two-up.view .frame.deleted img', this.file), (function(_this) {
// Determine if old and new file has same dimensions, if not show 'two-up' view
return function(deletedWidth, deletedHeight) {
return _this.requestImageInfo($('.two-up.view .frame.added img', _this.file), function(width, height) {
if (width === deletedWidth && height === deletedHeight) {
return _this.initViewModes();
} else {
return _this.initView('two-up');
}
_this.initViewModes();
// Load two-up view after images are loaded
// so that we can display the correct width and height information
const images = $('.two-up.view img', _this.file);
let loadedCount = 0;
images.on('load', () => {
loadedCount += 1;
if (loadedCount === images.length) {
_this.initView('two-up');
}
});
});
};
})(this));
......
......@@ -2,6 +2,7 @@
/* global Dropzone */
import _ from 'underscore';
import './preview_markdown';
import csrf from './lib/utils/csrf';
window.DropzoneInput = (function() {
function DropzoneInput(form) {
......@@ -50,9 +51,7 @@ window.DropzoneInput = (function() {
paramName: 'file',
maxFilesize: maxFileSize,
uploadMultiple: false,
headers: {
'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')
},
headers: csrf.headers,
previewContainer: false,
processing: function() {
return $('.div-dropzone-alert').alert('close');
......@@ -260,9 +259,7 @@ window.DropzoneInput = (function() {
dataType: 'json',
processData: false,
contentType: false,
headers: {
'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')
},
headers: csrf.headers,
beforeSend: function() {
showSpinner();
return closeAlertMessage();
......
/*
This module provides easy access to the CSRF token and caches
it for re-use. It also exposes some values commonly used in relation
to the CSRF token (header key and headers object).
If you need to refresh the csrfToken for some reason, just call `init` and
then use the accessors as you would normally.
If you need to compose a headers object, use the spread operator:
```
headers: {
...csrf.headers,
someOtherHeader: '12345',
}
```
*/
const csrf = {
init() {
const tokenEl = document.querySelector('meta[name=csrf-token]');
if (tokenEl !== null) {
this.csrfToken = tokenEl.getAttribute('content');
} else {
this.csrfToken = null;
}
},
get token() {
return this.csrfToken;
},
get headerKey() {
return 'X-CSRF-Token';
},
get headers() {
if (this.csrfToken !== null) {
return {
[this.headerKey]: this.token,
};
}
return {};
},
};
csrf.init();
// use our cached token for any $.rails-generated AJAX requests
if ($.rails) {
$.rails.csrfToken = () => csrf.token;
}
export default csrf;
......@@ -2,6 +2,7 @@
/* global Flash, Autosave */
import { mapActions, mapGetters } from 'vuex';
import _ from 'underscore';
import autosize from 'vendor/autosize';
import '../../autosave';
import TaskList from '../../task_list';
import * as constants from '../constants';
......@@ -96,6 +97,8 @@
methods: {
...mapActions([
'saveNote',
'stopPolling',
'restartPolling',
'removePlaceholderNotes',
]),
setIsSubmitButtonDisabled(note, isSubmitting) {
......@@ -124,10 +127,14 @@
}
this.isSubmitting = true;
this.note = ''; // Empty textarea while being requested. Repopulate in catch
this.resizeTextarea();
this.stopPolling();
this.saveNote(noteData)
.then((res) => {
this.isSubmitting = false;
this.restartPolling();
if (res.errors) {
if (res.errors.commands_only) {
this.discard();
......@@ -174,6 +181,8 @@
if (shouldClear) {
this.note = '';
this.resizeTextarea();
this.$refs.markdownField.previewMarkdown = false;
}
// reset autostave
......@@ -205,6 +214,11 @@
selector: '.notes',
});
},
resizeTextarea() {
this.$nextTick(() => {
autosize.update(this.$refs.textarea);
});
},
},
mounted() {
// jQuery is needed here because it is a custom event being dispatched with jQuery.
......@@ -247,7 +261,8 @@
:markdown-docs-path="markdownDocsPath"
:quick-actions-docs-path="quickActionsDocsPath"
:add-spacing-classes="false"
:is-confidential-issue="isConfidentialIssue">
:is-confidential-issue="isConfidentialIssue"
ref="markdownField">
<textarea
id="note-body"
name="note[note]"
......
......@@ -187,6 +187,14 @@ export const poll = ({ commit, state, getters }) => {
});
};
export const stopPolling = () => {
eTagPoll.stop();
};
export const restartPolling = () => {
eTagPoll.restart();
};
export const fetchData = ({ commit, state, getters }) => {
const requestData = { endpoint: state.notesData.notesPath, lastFetchedAt: state.lastFetchedAt };
......
......@@ -5,15 +5,19 @@ import * as constants from '../constants';
export default {
[types.ADD_NEW_NOTE](state, note) {
const { discussion_id, type } = note;
const noteData = {
expanded: true,
id: discussion_id,
individual_note: !(type === constants.DISCUSSION_NOTE),
notes: [note],
reply_id: discussion_id,
};
state.notes.push(noteData);
const [exists] = state.notes.filter(n => n.id === note.discussion_id);
if (!exists) {
const noteData = {
expanded: true,
id: discussion_id,
individual_note: !(type === constants.DISCUSSION_NOTE),
notes: [note],
reply_id: discussion_id,
};
state.notes.push(noteData);
}
},
[types.ADD_NEW_REPLY_TO_DISCUSSION](state, note) {
......
......@@ -69,8 +69,7 @@
@click="onClickAction(action.path)"
:class="{ disabled: isActionDisabled(action) }"
:disabled="isActionDisabled(action)">
<span v-html="playIconSvg"></span>
<span>{{action.name}}</span>
{{action.name}}
</button>
</li>
</ul>
......
......@@ -39,11 +39,7 @@
rel="nofollow"
download
:href="artifact.path">
<i
class="fa fa-download"
aria-hidden="true">
</i>
<span>Download {{artifact.name}} artifacts</span>
Download {{artifact.name}} artifacts
</a>
</li>
</ul>
......
<script>
import { s__ } from '../../locale';
const PAGINATION_UI_BUTTON_LIMIT = 4;
const UI_LIMIT = 6;
const SPREAD = '...';
const PREV = 'Prev';
const NEXT = 'Next';
const FIRST = '« First';
const LAST = 'Last »';
const PREV = s__('Pagination|Prev');
const NEXT = s__('Pagination|Next');
const FIRST = s__('Pagination|« First');
const LAST = s__('Pagination|Last »');
export default {
props: {
......
import Vue from 'vue';
import VueResource from 'vue-resource';
import csrf from '../lib/utils/csrf';
Vue.use(VueResource);
......@@ -18,9 +19,7 @@ Vue.http.interceptors.push((request, next) => {
// New Vue Resource version uses Headers, we are expecting a plain object to render pagination
// and polling.
Vue.http.interceptors.push((request, next) => {
if ($.rails) {
request.headers.set('X-CSRF-Token', $.rails.csrfToken());
}
request.headers.set(csrf.headerKey, csrf.token);
next((response) => {
// Headers object has a `forEach` property that iterates through all values.
......
......@@ -30,10 +30,12 @@
@import "framework/mobile";
@import "framework/modal";
@import "framework/nav";
@import "framework/new-nav";
@import "framework/pagination";
@import "framework/panels";
@import "framework/selects";
@import "framework/sidebar";
@import "framework/new-sidebar";
@import "framework/tables";
@import "framework/notes";
@import "framework/timeline";
......
......@@ -9,6 +9,7 @@
}
.emoji-menu {
display: none;
position: absolute;
top: 0;
margin-top: 3px;
......@@ -27,6 +28,10 @@
transition: .3s cubic-bezier(.67, .06, .19, 1.44);
transition-property: transform, opacity;
&.is-rendered {
display: block;
}
&.is-aligned-right {
transform-origin: 100% -45px;
}
......
......@@ -254,6 +254,10 @@ body {
.search-icon {
color: $theme-gray-200;
}
.search-input {
color: $gl-text-color;
}
}
.location-badge {
......
......@@ -144,3 +144,39 @@
@mixin green-status-color {
@include status-color($green-50, $green-500, $green-700);
}
@mixin fade($gradient-direction, $gradient-color) {
visibility: hidden;
opacity: 0;
z-index: 2;
position: absolute;
bottom: 12px;
width: 43px;
height: 30px;
transition-duration: .3s;
-webkit-transform: translateZ(0);
background: linear-gradient(to $gradient-direction, $gradient-color 45%, rgba($gradient-color, 0.4));
&.scrolling {
visibility: visible;
opacity: 1;
transition-duration: .3s;
}
.fa {
position: relative;
top: 5px;
font-size: 18px;
}
}
@mixin scrolling-links() {
overflow-x: auto;
overflow-y: hidden;
-webkit-overflow-scrolling: touch;
display: flex;
&::-webkit-scrollbar {
display: none;
}
}
@mixin fade($gradient-direction, $gradient-color) {
visibility: hidden;
opacity: 0;
z-index: 2;
position: absolute;
bottom: 12px;
width: 43px;
height: 30px;
transition-duration: .3s;
-webkit-transform: translateZ(0);
background: linear-gradient(to $gradient-direction, $gradient-color 45%, rgba($gradient-color, 0.4));
&.scrolling {
visibility: visible;
opacity: 1;
transition-duration: .3s;
}
.fa {
position: relative;
top: 5px;
font-size: 18px;
}
}
@mixin scrolling-links() {
overflow-x: auto;
overflow-y: hidden;
-webkit-overflow-scrolling: touch;
display: flex;
&::-webkit-scrollbar {
display: none;
}
}
.nav-links {
display: flex;
......
......@@ -295,75 +295,6 @@ header.navbar-gitlab-new {
margin-top: 4px;
}
.search {
margin: 4px 8px 0;
form {
height: 32px;
border: 0;
border-radius: $border-radius-default;
transition: border-color ease-in-out 0.15s, background-color ease-in-out 0.15s;
&:hover {
box-shadow: none;
}
}
&.search-active form {
box-shadow: none;
.search-input {
color: $gl-text-color;
transition: color ease-in-out 0.15s;
}
.search-input::placeholder {
color: $gl-text-color-tertiary;
}
.search-input-wrap {
.search-icon,
.clear-icon {
color: $gl-text-color-tertiary;
transition: color ease-in-out 0.15s;
}
}
}
.search-input {
color: $white-light;
background: none;
transition: color ease-in-out 0.15s;
}
.search-input::placeholder {
transition: color ease-in-out 0.15s;
}
.location-badge {
font-size: 12px;
margin: -4px 4px -4px -4px;
line-height: 25px;
padding: 4px 8px;
border-radius: 2px 0 0 2px;
height: 32px;
transition: border-color ease-in-out 0.15s;
}
&.search-active {
.location-badge {
background-color: $nav-badge-bg;
border-color: $border-color;
}
.search-input-wrap {
.clear-icon {
color: $white-light;
}
}
}
}
.breadcrumbs {
display: flex;
min-height: 48px;
......
......@@ -6,6 +6,7 @@ $gutter_width: 290px;
$gutter_inner_width: 250px;
$sidebar-transition-duration: .15s;
$sidebar-breakpoint: 1024px;
$default-transition-duration: .15s;
/*
* Color schema
......
......@@ -28,9 +28,7 @@ input[type="checkbox"]:hover {
}
.search {
margin-right: 10px;
margin-left: 10px;
margin-top: ($header-height - 35) / 2;
margin: 4px 8px 0;
form {
@extend .form-control;
......@@ -38,15 +36,23 @@ input[type="checkbox"]:hover {
padding: 4px;
width: $search-input-width;
line-height: 24px;
height: 32px;
border: 0;
border-radius: $border-radius-default;
transition: border-color ease-in-out $default-transition-duration, background-color ease-in-out $default-transition-duration;
&:hover {
border-color: lighten($dropdown-input-focus-border, 20%);
box-shadow: 0 0 4px lighten($search-input-focus-shadow-color, 20%);
box-shadow: none;
}
}
.location-text {
font-style: normal;
.location-badge {
font-size: 12px;
margin: -4px 4px -4px -4px;
line-height: 25px;
padding: 4px 8px;
border-radius: $border-radius-default 0 0 $border-radius-default;
transition: border-color ease-in-out $default-transition-duration;
}
.search-input {
......@@ -56,41 +62,26 @@ input[type="checkbox"]:hover {
margin-left: 5px;
line-height: 25px;
width: 98%;
color: $white-light;
background: none;
transition: color ease-in-out $default-transition-duration;
}
.location-badge {
line-height: 25px;
padding: 0 5px;
border-radius: $border-radius-default;
font-size: 14px;
font-style: normal;
color: $note-disabled-comment-color;
display: inline-block;
background-color: $gray-normal;
vertical-align: top;
cursor: default;
.search-input::placeholder {
transition: color ease-in-out $default-transition-duration;
}
.search-input-container {
display: -webkit-flex;
display: flex;
position: relative;
}
.search-input-wrap {
// Fallback if flexbox is not supported
display: inline-block;
}
.search-input-wrap {
width: 100%;
.search-icon,
.clear-icon {
position: absolute;
right: 5px;
top: 0;
color: $location-icon-color;
&::before {
font-family: FontAwesome;
......@@ -101,7 +92,7 @@ input[type="checkbox"]:hover {
.search-icon {
@extend .fa-search;
transition: color 0.15s;
transition: color $default-transition-duration;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
......@@ -148,21 +139,32 @@ input[type="checkbox"]:hover {
form {
@extend .form-control:focus;
border-color: $dropdown-input-focus-border;
box-shadow: 0 0 4px $search-input-focus-shadow-color;
}
box-shadow: none;
.search-input-wrap {
.search-icon,
.clear-icon {
color: $gl-text-color-tertiary;
transition: color ease-in-out $default-transition-duration;
}
}
.location-badge {
transition: all 0.15s;
background-color: $location-badge-active-bg;
color: $white-light;
}
.search-input {
color: $gl-text-color;
transition: color ease-in-out $default-transition-duration;
}
.search-input-wrap {
i {
color: $layout-link-gray;
.search-input::placeholder {
color: $gl-text-color-tertiary;
}
}
.location-badge {
transition: all $default-transition-duration;
background-color: $nav-badge-bg;
border-color: $border-color;
}
.dropdown-menu {
transition-duration: 100ms, 75ms;
transition-delay: 75ms, 100ms;
......
......@@ -12,6 +12,7 @@ class Admin::ApplicationController < ApplicationController
def display_geo_information
return unless Gitlab::Geo.secondary?
return unless Gitlab::Geo.primary_node_configured?
primary_node = view_context.link_to('primary node', Gitlab::Geo.primary_node.url)
flash.now[:notice] = "You are on a secondary (read-only) Geo node. If you want to make any changes, you must visit the #{primary_node}.".html_safe
......
......@@ -117,19 +117,34 @@ module IssuableCollections
key = 'issuable_sort'
cookies[key] = params[:sort] if params[:sort].present?
# id_desc and id_asc are old values for these two.
cookies[key] = sort_value_recently_created if cookies[key] == 'id_desc'
cookies[key] = sort_value_oldest_created if cookies[key] == 'id_asc'
cookies[key] = update_cookie_value(cookies[key])
params[:sort] = cookies[key]
end
def default_sort_order
case params[:state]
when 'opened', 'all' then sort_value_recently_created
when 'opened', 'all' then sort_value_created_date
when 'merged', 'closed' then sort_value_recently_updated
else sort_value_recently_created
else sort_value_created_date
end
end
# Update old values to the actual ones.
def update_cookie_value(value)
case value
when 'id_asc' then sort_value_oldest_created
when 'id_desc' then sort_value_recently_created
when 'created_asc' then sort_value_created_date
when 'created_desc' then sort_value_created_date
when 'due_date_asc' then sort_value_due_date
when 'due_date_desc' then sort_value_due_date
when 'milestone_due_asc' then sort_value_milestone
when 'milestone_due_desc' then sort_value_milestone
when 'downvotes_asc' then sort_value_popularity
when 'downvotes_desc' then sort_value_popularity
when 'weight_asc' then sort_value_weight
when 'weight_desc' then sort_value_weight
else value
end
end
end
......@@ -3,8 +3,13 @@ class HelpController < ApplicationController
layout 'help'
# Taken from Jekyll
# https://github.com/jekyll/jekyll/blob/3.5-stable/lib/jekyll/document.rb#L13
YAML_FRONT_MATTER_REGEXP = %r!\A(---\s*\n.*?\n?)^((---|\.\.\.)\s*$\n?)!m
def index
@help_index = File.read(Rails.root.join('doc', 'README.md'))
# Remove YAML frontmatter so that it doesn't look weird
@help_index = File.read(Rails.root.join('doc', 'README.md')).sub(YAML_FRONT_MATTER_REGEXP, '')
# Prefix Markdown links with `help/` unless they are external links
# See http://rubular.com/r/X3baHTbPO2
......@@ -22,7 +27,8 @@ class HelpController < ApplicationController
path = File.join(Rails.root, 'doc', "#{@path}.md")
if File.exist?(path)
@markdown = File.read(path)
# Remove YAML frontmatter so that it doesn't look weird
@markdown = File.read(path).gsub(YAML_FRONT_MATTER_REGEXP, '')
render 'show.html.haml'
else
......
......@@ -35,7 +35,10 @@ class Projects::TreeController < Projects::ApplicationController
end
format.json do
render json: TreeSerializer.new(project: @project, repository: @repository, ref: @ref).represent(@tree)
# n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/38261
Gitlab::GitalyClient.allow_n_plus_1_calls do
render json: TreeSerializer.new(project: @project, repository: @repository, ref: @ref).represent(@tree)
end
end
end
end
......
......@@ -25,9 +25,31 @@ class IssuableFinder
NONE = '0'.freeze
SCALAR_PARAMS = %i(scope state group_id project_id milestone_title assignee_id search label_name sort assignee_username author_id author_username authorized_only due_date iids non_archived weight my_reaction_emoji).freeze
SCALAR_PARAMS = %i[
assignee_id
assignee_username
author_id
author_username
authorized_only
due_date
group_id
iids
label_name
milestone_title
non_archived
project_id
scope
search
sort
state
].freeze
ARRAY_PARAMS = { label_name: [], iids: [], assignee_username: [] }.freeze
VALID_PARAMS = (SCALAR_PARAMS + [ARRAY_PARAMS]).freeze
EE_SCALAR_PARAMS = %i[
weight
].freeze
VALID_PARAMS = (SCALAR_PARAMS + [ARRAY_PARAMS] + EE_SCALAR_PARAMS).freeze
attr_accessor :current_user, :params
......
......@@ -176,13 +176,15 @@ module CommitsHelper
end
end
def view_file_button(commit_sha, diff_new_path, project)
def view_file_button(commit_sha, diff_new_path, project, replaced: false)
title = replaced ? _('View replaced file @ ') : _('View file @ ')
link_to(
project_blob_path(project,
tree_join(commit_sha, diff_new_path)),
class: 'btn view-file js-view-file'
) do
raw('View file @ ') + content_tag(:span, Commit.truncate_sha(commit_sha),
raw(title) + content_tag(:span, Commit.truncate_sha(commit_sha),
class: 'commit-sha')
end
end
......
......@@ -131,7 +131,7 @@ module GroupsHelper
end
def default_help
s_("GroupSettings|This setting will be applied to all subgroups unless overridden by a group owner.")
s_("GroupSettings|This setting will be applied to all subgroups unless overridden by a group owner. Groups that already have access to the project will continue to have access unless removed manually.")
end
def ancestor_locked_but_you_can_override(group)
......
module MirrorHelper
def branch_diverged_tooltip_message
message = "The branch could not be updated automatically because it has diverged from its upstream counterpart."
message << "<br>To discard the local changes and overwrite the branch with the upstream version, delete it here and choose 'Update Now' above." if can?(current_user, :push_code, @project)
message = s_('Branches|The branch could not be updated automatically because it has diverged from its upstream counterpart.')
if can?(current_user, :push_code, @project)
message << '<br>'
message << s_("Branches|To discard the local changes and overwrite the branch with the upstream version, delete it here and choose 'Update Now' above.")
end
message
end
......
This diff is collapsed.
......@@ -142,16 +142,18 @@ module Issuable
end
def sort(method, excluded_labels: [])
sorted = case method.to_s
when 'milestone_due_asc' then order_milestone_due_asc
when 'milestone_due_desc' then order_milestone_due_desc
when 'downvotes_desc' then order_downvotes_desc
when 'upvotes_desc' then order_upvotes_desc
when 'label_priority' then order_labels_priority(excluded_labels: excluded_labels)
when 'priority' then order_due_date_and_labels_priority(excluded_labels: excluded_labels)
else
order_by(method)
end
sorted =
case method.to_s
when 'downvotes_desc' then order_downvotes_desc
when 'label_priority' then order_labels_priority(excluded_labels: excluded_labels)
when 'milestone' then order_milestone_due_asc
when 'milestone_due_asc' then order_milestone_due_asc
when 'milestone_due_desc' then order_milestone_due_desc
when 'popularity' then order_upvotes_desc
when 'priority' then order_due_date_and_labels_priority(excluded_labels: excluded_labels)
when 'upvotes_desc' then order_upvotes_desc
else order_by(method)
end
# Break ties with the ID column for pagination
sorted.order(id: :desc)
......@@ -224,7 +226,7 @@ module Issuable
def grouping_columns(sort)
grouping_columns = [arel_table[:id]]
if %w(milestone_due_desc milestone_due_asc).include?(sort)
if %w(milestone_due_desc milestone_due_asc milestone).include?(sort)
milestone_table = Milestone.arel_table
grouping_columns << milestone_table[:id]
grouping_columns << milestone_table[:due_date]
......
......@@ -19,14 +19,15 @@ module Sortable
module ClassMethods
def order_by(method)
case method.to_s
when 'name_asc' then order_name_asc
when 'name_desc' then order_name_desc
when 'updated_asc' then order_updated_asc
when 'updated_desc' then order_updated_desc
when 'created_asc' then order_created_asc
when 'created_asc' then order_created_asc
when 'created_date' then order_created_desc
when 'created_desc' then order_created_desc
when 'id_desc' then order_id_desc
when 'id_asc' then order_id_asc
when 'id_asc' then order_id_asc
when 'id_desc' then order_id_desc
when 'name_asc' then order_name_asc
when 'name_desc' then order_name_desc
when 'updated_asc' then order_updated_asc
when 'updated_desc' then order_updated_desc
else
all
end
......
......@@ -128,10 +128,12 @@ class Issue < ActiveRecord::Base
def self.sort(method, excluded_labels: [])
case method.to_s
when 'due_date_asc' then order_due_date_asc
when 'due_date' then order_due_date_asc
when 'due_date_asc' then order_due_date_asc
when 'due_date_desc' then order_due_date_desc
when 'weight_desc' then order_weight_desc
when 'weight_asc' then order_weight_asc
when 'weight' then order_weight_asc
when 'weight_asc' then order_weight_asc
when 'weight_desc' then order_weight_desc
else
super
end
......
......@@ -82,30 +82,30 @@ class License < ActiveRecord::Base
ULTIMATE_PLAN = 'ultimate'.freeze
EARLY_ADOPTER_PLAN = 'early_adopter'.freeze
EES_FEATURES = [
{ AUDIT_EVENTS_FEATURE => 1 },
{ BURNDOWN_CHARTS_FEATURE => 1 },
{ CONTRIBUTION_ANALYTICS_FEATURE => 1 },
{ ELASTIC_SEARCH_FEATURE => 1 },
{ EXPORT_ISSUES_FEATURE => 1 },
{ FAST_FORWARD_MERGE_FEATURE => 1 },
{ GROUP_WEBHOOKS_FEATURE => 1 },
{ ISSUABLE_DEFAULT_TEMPLATES_FEATURE => 1 },
{ ISSUE_BOARD_FOCUS_MODE_FEATURE => 1 },
{ SCOPED_ISSUE_BOARD_FEATURE => 1 },
{ ISSUE_WEIGHTS_FEATURE => 1 },
{ JENKINS_INTEGRATION_FEATURE => 1 },
{ LDAP_EXTRAS_FEATURE => 1 },
{ MERGE_REQUEST_APPROVERS_FEATURE => 1 },
{ MERGE_REQUEST_REBASE_FEATURE => 1 },
{ MERGE_REQUEST_SQUASH_FEATURE => 1 },
{ MULTIPLE_ISSUE_ASSIGNEES_FEATURE => 1 },
{ MULTIPLE_ISSUE_BOARDS_FEATURE => 1 },
{ PUSH_RULES_FEATURE => 1 },
{ PROTECTED_REFS_FOR_USERS_FEATURE => 1 },
{ RELATED_ISSUES_FEATURE => 1 },
{ REPOSITORY_MIRRORS_FEATURE => 1 },
{ REPOSITORY_SIZE_LIMIT_FEATURE => 1 }
EES_FEATURES = %i[
audit_events
burndown_charts
contribution_analytics
elastic_search
export_issues
fast_forward_merge
group_webhooks
issuable_default_templates
issue_board_focus_mode
scoped_issue_board
issue_weights
jenkins_integration
ldap_extras
merge_request_approvers
merge_request_rebase
merge_request_squash
multiple_issue_assignees
multiple_issue_boards
push_rules
protected_refs_for_users
related_issues
repository_mirrors
repository_size_limit
].freeze
EEP_FEATURES = EES_FEATURES + %i[
......@@ -304,7 +304,7 @@ class License < ActiveRecord::Base
end
def features_from_add_ons
add_ons.map { |name, count| FEATURES_FOR_ADD_ONS[name] if count > 0 }.compact
add_ons.map { |name, count| FEATURES_FOR_ADD_ONS[name] if count.to_i > 0 }.compact
end
def features
......
......@@ -10,6 +10,7 @@ class Repository
RESERVED_REFS_NAMES = %W[
heads
tags
replace
#{REF_ENVIRONMENTS}
#{REF_KEEP_AROUND}
#{REF_ENVIRONMENTS}
......
......@@ -39,9 +39,11 @@ module Geo
Array([message, details].compact.join("\n"))
end
rescue Gitlab::Geo::GeoNodeNotFoundError
['This GitLab instance does not appear to be configured properly as a Geo node. Make sure the URLs are using the correct fully-qualified domain names.']
rescue OpenSSL::Cipher::CipherError
['Error decrypting the Geo secret from the database. Check that the primary uses the correct db_key_base.']
rescue HTTParty::Error, Timeout::Error, SocketError, Errno::ECONNRESET, Errno::ECONNREFUSED => e
rescue HTTParty::Error, Timeout::Error, SocketError, SystemCallError, OpenSSL::SSL::SSLError => e
[e.message]
end
......
- @no_container = true
- page_title 'Audit Log'
= render 'admin/monitoring/head'
%div{ class: container_class }
.todos-filters
......
- @no_container = true
- page_title "Background Jobs"
= render 'admin/monitoring/head'
%div{ class: container_class }
%h3.page-title Background Jobs
......
- breadcrumb_title "Cohorts"
- @no_container = true
= render "admin/dashboard/head"
%div{ class: container_class }
- if @cohorts
......
- @no_container = true
- page_title 'ConvDev Index'
= render 'admin/monitoring/head'
.container
- if show_callout?('convdev_intro_callout_dismissed')
= render 'callout'
......
= content_for :sub_nav do
.scrolling-tabs-container.sub-nav-scroll
= render 'shared/nav_scroll'
.nav-links.sub-nav.scrolling-tabs
%ul{ class: (container_class) }
= nav_link(controller: :dashboard, html_options: {class: 'home'}) do
= link_to admin_root_path, title: 'Overview' do
%span
Overview
= nav_link(controller: [:admin, :projects]) do
= link_to admin_projects_path, title: 'Projects' do
%span
Projects
= nav_link(controller: :users) do
= link_to admin_users_path, title: 'Users' do
%span
Users
= nav_link(controller: :groups) do
= link_to admin_groups_path, title: 'Groups' do
%span
Groups
= nav_link path: 'builds#index' do
= link_to admin_jobs_path, title: 'Jobs' do
%span
Jobs
= nav_link path: ['runners#index', 'runners#show'] do
= link_to admin_runners_path, title: 'Runners' do
%span
Runners
= nav_link path: 'cohorts#index' do
= link_to admin_cohorts_path, title: 'Cohorts' do
%span
Cohorts
= nav_link(controller: :conversational_development_index) do
= link_to admin_conversational_development_index_path, title: 'ConvDev Index' do
%span
ConvDev Index
- @no_container = true
- breadcrumb_title "Dashboard"
= render "admin/dashboard/head"
%div{ class: container_class }
- if @license
......
- @no_container = true
- page_title "Groups"
= render "admin/dashboard/head"
%div{ class: container_class }
.top-area
......
- @no_container = true
- page_title _('Health Check')
- no_errors = @errors.blank? && @failing_storage_statuses.blank?
= render 'admin/monitoring/head'
%div{ class: container_class }
%h3.page-title= page_title
......
- breadcrumb_title "Jobs"
- @no_container = true
= render "admin/dashboard/head"
%div{ class: container_class }
......
- @no_container = true
- page_title "Logs"
= render 'admin/monitoring/head'
%div{ class: container_class }
%ul.nav-links.log-tabs
......
= content_for :sub_nav do
.scrolling-tabs-container.sub-nav-scroll
= render 'shared/nav_scroll'
.nav-links.sub-nav.scrolling-tabs
%ul{ class: (container_class) }
= nav_link(controller: :system_info) do
= link_to admin_system_info_path, title: 'System Info' do
%span
System Info
= nav_link(controller: :background_jobs) do
= link_to admin_background_jobs_path, title: 'Background Jobs' do
%span
Background Jobs
= nav_link(controller: :logs) do
= link_to admin_logs_path, title: 'Logs' do
%span
Logs
= nav_link(controller: :health_check) do
= link_to admin_health_check_path, title: 'Health Check' do
%span
Health Check
= nav_link(controller: :requests_profiles) do
= link_to admin_requests_profiles_path, title: 'Requests Profiles' do
%span
Requests Profiles
= render 'admin/monitoring/ee/nav'
......@@ -2,7 +2,6 @@
- page_title "Projects"
- params[:visibility_level] ||= []
= render "admin/dashboard/head"
%div{ class: container_class }
.top-area
......
- @no_container = true
- page_title 'Requests Profiles'
= render 'admin/monitoring/head'
%div{ class: container_class }
%h3.page-title
......
- breadcrumb_title "Runners"
- @no_container = true
= render "admin/dashboard/head"
%div{ class: container_class }
.bs-callout
......
- @no_container = true
- page_title "System Info"
= render 'admin/monitoring/head'
%div{ class: container_class }
.prepend-top-default
......
- @no_container = true
- page_title "Users"
= render "admin/dashboard/head"
%div{ class: container_class }
.prepend-top-default
......
= content_for :sub_nav do
.scrolling-tabs-container.sub-nav-scroll
= render 'shared/nav_scroll'
.nav-links.sub-nav.scrolling-tabs
%ul{ class: container_class }
= nav_link(path: ['groups#show', 'groups#subgroups'], html_options: { class: 'home' }) do
= link_to group_path(@group), title: 'Group Home' do
%span
Home
= nav_link(path: 'groups#activity') do
= link_to activity_group_path(@group), title: 'Activity' do
%span
Activity
.hidden-xs
= render "projects/last_push"
= content_for :sub_nav do
.scrolling-tabs-container.sub-nav-scroll
= render 'shared/nav_scroll'
.nav-links.sub-nav.scrolling-tabs
%ul{ class: container_class }
= nav_link(path: 'groups#issues', html_options: { class: 'home' }) do
= link_to issues_group_path(@group), title: 'List' do
%span
List
- if @group.feature_available?(:group_issue_boards)
= nav_link(path: 'groups#boards') do
= link_to group_boards_path(@group), title: 'Boards' do
%span
Boards
= nav_link(path: 'labels#index') do
= link_to group_labels_path(@group), title: 'Labels' do
%span
Labels
= nav_link(path: 'milestones#index') do
= link_to group_milestones_path(@group), title: 'Milestones' do
%span
Milestones
= content_for :sub_nav do
.scrolling-tabs-container.sub-nav-scroll
= render 'shared/nav_scroll'
.nav-links.sub-nav.scrolling-tabs
%ul{ class: container_class }
= nav_link(path: 'groups#edit') do
= link_to edit_group_path(@group), title: 'General' do
%span
General
= nav_link(path: 'groups#projects') do
= link_to projects_group_path(@group), title: 'Projects' do
%span
Projects
= nav_link(controller: :ci_cd) do
= link_to group_settings_ci_cd_path(@group), title: 'Pipelines' do
%span
Pipelines
= render 'groups/ee/settings_nav'
......@@ -2,7 +2,6 @@
= auto_discovery_link_tag(:atom, group_url(@group, rss_url_options), title: "#{@group.name} activity")
- page_title "Activity"
= render 'groups/head'
%section.activities
= render 'activities'
- page_title "Audit Events"
= render "groups/settings_head"
%h3.page-title Group Audit Events
%p.light Events in #{@group.name}
......
- page_title "Billing"
= render "groups/settings_head"
- if @top_most_group
- top_most_group_plan = subscription_plan_info(@plans_data, @top_most_group.actual_plan)
......
- breadcrumb_title "General Settings"
= render "groups/settings_head"
.panel.panel-default.prepend-top-default
.panel-heading
Group settings
......
= render "groups/settings_head"
- if @group.feature_available?(:group_webhooks)
.row.prepend-top-default
.col-lg-3
......
- page_title "Issues"
- group_issues_exists = group_issues(@group).exists?
= render "head_issues"
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, params.merge(rss_url_options), title: "#{@group.name} issues")
......
- page_title 'Labels'
= render "groups/head_issues"
.top-area.adjust
.nav-text
Labels can be applied to issues and merge requests. Group labels are available for any project within the group.
......
- page_title "Milestones"
= render "groups/head_issues"
.top-area
= render 'shared/milestones_filter', counts: @milestone_states
......
- page_title "Pipelines quota"
= render "groups/settings_head"
%h3.page-title
Group pipelines quota
......
- breadcrumb_title "Projects"
= render "groups/settings_head"
.panel.panel-default.prepend-top-default
.panel-heading
......
- breadcrumb_title "CI / CD Settings"
- page_title "CI / CD"
= render "groups/settings_head"
= render 'ci/variables/index'
......@@ -4,7 +4,6 @@
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, group_url(@group, rss_url_options), title: "#{@group.name} activity")
= render 'groups/head'
= render 'groups/home_panel'
.groups-header{ class: container_class }
......
- breadcrumb_title "Details"
- @no_container = true
= render 'head'
= render 'groups/home_panel'
.groups-header{ class: container_class }
......
......@@ -32,10 +32,6 @@
= stylesheet_link_tag "test", media: "all" if Rails.env.test?
= stylesheet_link_tag 'performance_bar' if performance_bar_enabled?
// 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
- if content_for?(:library_javascripts)
......
......@@ -60,7 +60,7 @@
= link_to sherlock_transactions_path, class: 'admin-icon', title: 'Sherlock Transactions',
data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('tachometer fw')
- if Gitlab::Geo.secondary?
- if Gitlab::Geo.secondary? && Gitlab::Geo.primary_node_configured?
%li
= link_to Gitlab::Geo.primary_node.url, title: 'Go to primary node', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('globe fw')
= content_for :sub_nav do
.scrolling-tabs-container.sub-nav-scroll
= render 'shared/nav_scroll'
.nav-links.sub-nav.scrolling-tabs
%ul{ class: container_class }
= nav_link(path: 'projects#show') do
= link_to project_path(@project), title: _('Project home'), class: 'shortcuts-project' do
%span= _('Home')
= nav_link(path: 'projects#activity') do
= link_to activity_project_path(@project), title: _('Activity'), class: 'shortcuts-project-activity' do
%span= _('Activity')
- if can?(current_user, :read_cycle_analytics, @project)
= nav_link(path: 'cycle_analytics#show') do
= link_to project_cycle_analytics_path(@project), title: _('Cycle Analytics'), class: 'shortcuts-project-cycle-analytics' do
%span= _('Cycle Analytics')
......@@ -2,8 +2,6 @@
- page_title _("Activity")
= render "projects/head"
= render 'projects/last_push'
= render 'projects/activity'
- breadcrumb_title _('Artifacts')
- page_title @path.presence, 'Artifacts', "#{@build.name} (##{@build.id})", 'Jobs'
= render "projects/pipelines/head"
= render "projects/jobs/header", show_controls: false
......
- page_title @path, 'Artifacts', "#{@build.name} (##{@build.id})", 'Jobs'
= render "projects/pipelines/head"
= render "projects/jobs/header", show_controls: false
......
- page_title "Audit Events"
= render "projects/settings/head"
- if show_project_feature_promotion?(:audit_events)
= render 'shared/promotions/promote_audit_events'
......
- @no_container = true
- project_duration = age_map_duration(@blame_groups, @project)
- page_title "Blame", @blob.path, @ref
= render "projects/commits/head"
%div{ class: container_class }
#blob-content-holder.tree-holder
......
......@@ -4,7 +4,6 @@
- content_for :page_specific_javascripts do
= page_specific_javascript_tag('lib/ace.js')
= page_specific_javascript_bundle_tag('blob')
= render "projects/commits/head"
%div{ class: container_class }
- if @conflict
......
......@@ -2,7 +2,6 @@
- @no_container = true
- page_title @blob.path, @ref
= render "projects/commits/head"
- content_for :page_specific_javascripts do
= webpack_bundle_tag 'blob'
......
......@@ -13,26 +13,29 @@
- if branch.name == @repository.root_ref
%span.label.label-primary default
- elsif @repository.merged_to_root_ref? branch.name
%span.label.label-info.has-tooltip{ title: "Merged into #{@repository.root_ref}" }
merged
%span.label.label-info.has-tooltip{ title: s_('Branches|Merged into %{default_branch}') % { default_branch: @repository.root_ref } }
= s_('Branches|merged')
- if protected_branch?(@project, branch)
%span.label.label-success
protected
= s_('Branches|protected')
- if @project.mirror_ever_updated_successfully? && @repository.diverged_from_upstream?(branch.name)
%span.label.label-danger.has-tooltip{ data: { html: "true", title: branch_diverged_tooltip_message } }
= icon('exclamation-triangle')
diverged from upstream
= s_('Branches|diverged from upstream')
.controls.hidden-xs<
- if merge_project && create_mr_button?(@repository.root_ref, branch.name)
= link_to create_mr_path(@repository.root_ref, branch.name), class: 'btn btn-default' do
Merge request
= _('Merge request')
- if branch.name != @repository.root_ref
= link_to project_compare_index_path(@project, from: @repository.root_ref, to: branch.name), class: "btn btn-default #{'prepend-left-10' unless merge_project}", method: :post, title: "Compare" do
Compare
= link_to project_compare_index_path(@project, from: @repository.root_ref, to: branch.name),
class: "btn btn-default #{'prepend-left-10' unless merge_project}",
method: :post,
title: s_('Branches|Compare') do
= s_('Branches|Compare')
= render 'projects/buttons/download', project: @project, ref: branch.name, pipeline: @refs_pipelines[branch.name]
......@@ -40,12 +43,12 @@
- if branch.name == @project.repository.root_ref
%button{ class: "btn btn-remove remove-row js-ajax-loading-spinner has-tooltip disabled",
disabled: true,
title: "The default branch cannot be deleted" }
title: s_('Branches|The default branch cannot be deleted') }
= icon("trash-o")
- elsif protected_branch?(@project, branch)
- if can?(current_user, :delete_protected_branch, @project)
%button{ class: "btn btn-remove remove-row js-ajax-loading-spinner has-tooltip",
title: "Delete protected branch",
title: s_('Branches|Delete protected branch'),
data: { toggle: "modal",
target: "#modal-delete-branch",
delete_path: project_branch_path(@project, branch.name),
......@@ -55,20 +58,22 @@
- else
%button{ class: "btn btn-remove remove-row js-ajax-loading-spinner has-tooltip disabled",
disabled: true,
title: "Only a project master or owner can delete a protected branch" }
title: s_('Branches|Only a project master or owner can delete a protected branch') }
= icon("trash-o")
- else
= link_to project_branch_path(@project, branch.name),
class: "btn btn-remove remove-row js-ajax-loading-spinner has-tooltip",
title: "Delete branch",
title: s_('Branches|Delete branch'),
method: :delete,
data: { confirm: "Deleting the '#{branch.name}' branch cannot be undone. Are you sure?" },
data: { confirm: s_("Branches|Deleting the '%{branch_name}' branch cannot be undone. Are you sure?") % { branch_name: branch.name } },
remote: true,
"aria-label" => "Delete branch" do
'aria-label' => s_('Branches|Delete branch') do
= icon("trash-o")
- if branch.name != @repository.root_ref
.divergence-graph{ title: "#{number_commits_behind} commits behind #{@repository.root_ref}, #{number_commits_ahead} commits ahead" }
.divergence-graph{ title: s_('%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead') % { number_commits_behind: number_commits_behind,
default_branch: @repository.root_ref,
number_commits_ahead: number_commits_ahead } }
.graph-side
.bar.bar-behind{ style: "width: #{number_commits_behind * bar_graph_width_factor}%" }
%span.count.count-behind= number_commits_behind
......@@ -82,4 +87,4 @@
= render 'projects/branches/commit', commit: commit, project: @project
- else
%p
Cant find HEAD commit for this branch
= s_('Branches|Cant find HEAD commit for this branch')
......@@ -4,36 +4,38 @@
.modal-header
%button.close{ data: { dismiss: 'modal' } } ×
%h3.page-title
Delete protected branch
= surround "'", "'?" do
- title_branch_name = capture do
%span.js-branch-name.ref-name>[branch name]
= s_("Branches|Delete protected branch '%{branch_name}'?").html_safe % { branch_name: title_branch_name }
.modal-body
%p
You’re about to permanently delete the protected branch
= succeed '.' do
%strong.js-branch-name.ref-name [branch name]
- branch_name = capture do
%strong.js-branch-name.ref-name>[branch name]
= s_('Branches|You’re about to permanently delete the protected branch %{branch_name}.').html_safe % { branch_name: branch_name }
%p.js-not-merged
- default_branch = capture do
%span.ref-name= @repository.root_ref
= s_("Branches|This branch hasn’t been merged into %{default_branch}.").html_safe % { default_branch: default_branch }
= s_("Branches|To avoid data loss, consider merging this branch before deleting it.")
= s_('Branches|This branch hasn’t been merged into %{default_branch}.').html_safe % { default_branch: default_branch }
= s_('Branches|To avoid data loss, consider merging this branch before deleting it.')
%p
Once you confirm and press
= succeed ',' do
%strong Delete protected branch
it cannot be undone or recovered.
- delete_protected_branch = capture do
%strong
= s_('Branches|Delete protected branch')
= s_('Branches|Once you confirm and press %{delete_protected_branch}, it cannot be undone or recovered.').html_safe % { delete_protected_branch: delete_protected_branch }
%p
%strong To confirm, type
%kbd.js-branch-name [branch name]
- branch_name_confirmation = capture do
%kbd.js-branch-name [branch name]
%strong
= s_('Branches|To confirm, type %{branch_name_confirmation}:').html_safe % { branch_name_confirmation: branch_name_confirmation }
.form-group
= text_field_tag 'delete_branch_input', '', class: 'form-control js-delete-branch-input'
.modal-footer
%button.btn{ data: { dismiss: 'modal' } } Cancel
= link_to 'Delete protected branch', '',
= link_to s_('Branches|Delete protected branch'), '',
class: "btn btn-danger js-delete-branch",
title: 'Delete branch',
title: s_('Branches|Delete branch'),
method: :delete,
"aria-label" => "Delete"
'aria-label' => s_('Branches|Delete branch')
- @no_container = true
- page_title "Branches"
- add_to_breadcrumbs("Repository", project_tree_path(@project))
= render "projects/commits/head"
- page_title _('Branches')
- add_to_breadcrumbs(_('Repository'), project_tree_path(@project))
%div{ class: container_class }
.top-area.adjust
- if can?(current_user, :admin_project, @project)
.nav-text
Protected branches can be managed in
= link_to 'project settings', project_protected_branches_path(@project)
- project_settings_link = link_to s_('Branches|project settings'), project_protected_branches_path(@project)
= s_('Branches|Protected branches can be managed in %{project_settings_link}').html_safe % { project_settings_link: project_settings_link }
.nav-controls
= form_tag(filter_branches_path, method: :get) do
= search_field_tag :search, params[:search], { placeholder: 'Filter by branch name', id: 'branch-search', class: 'form-control search-text-input input-short', spellcheck: false }
= search_field_tag :search, params[:search], { placeholder: s_('Branches|Filter by branch name'), id: 'branch-search', class: 'form-control search-text-input input-short', spellcheck: false }
.dropdown.inline>
%button.dropdown-menu-toggle{ type: 'button', 'data-toggle' => 'dropdown' }
......@@ -21,23 +20,31 @@
= icon('chevron-down')
%ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-selectable
%li.dropdown-header
Sort by
= s_('Branches|Sort by')
- branches_sort_options_hash.each do |value, title|
%li
= link_to title, filter_branches_path(sort: value), class: ("is-active" if @sort == value)
- if can? current_user, :push_code, @project
= link_to project_merged_branches_path(@project), class: 'btn btn-inverted btn-remove has-tooltip', title: "Delete all branches that are merged into '#{@project.repository.root_ref}'", method: :delete, data: { confirm: "Deleting the merged branches cannot be undone. Are you sure?", container: 'body' } do
Delete merged branches
= link_to project_merged_branches_path(@project),
class: 'btn btn-inverted btn-remove has-tooltip',
title: s_("Branches|Delete all branches that are merged into '%{default_branch}'") % { default_branch: @project.repository.root_ref },
method: :delete,
data: { confirm: s_('Branches|Deleting the merged branches cannot be undone. Are you sure?'),
container: 'body' } do
= s_('Branches|Delete merged branches')
= link_to new_project_branch_path(@project), class: 'btn btn-create' do
New branch
= s_('Branches|New branch')
= render 'projects/commits/mirror_status'
- if @branches.any?
%ul.content-list.all-branches
- @branches.each do |branch|
= render "projects/branches/branch", branch: branch
= paginate @branches, theme: 'gitlab'
- else
.nothing-here-block No branches to show
.nothing-here-block
= s_('Branches|No branches to show')
= render 'projects/branches/delete_protected_modal'
......@@ -11,19 +11,15 @@
#{ _('Source code') }
%li
= link_to archive_project_repository_path(project, ref: ref, format: 'zip'), rel: 'nofollow', download: '' do
%i.fa.fa-download
%span= _('Download zip')
%li
= link_to archive_project_repository_path(project, ref: ref, format: 'tar.gz'), rel: 'nofollow', download: '' do
%i.fa.fa-download
%span= _('Download tar.gz')
%li
= link_to archive_project_repository_path(project, ref: ref, format: 'tar.bz2'), rel: 'nofollow', download: '' do
%i.fa.fa-download
%span= _('Download tar.bz2')
%li
= link_to archive_project_repository_path(project, ref: ref, format: 'tar'), rel: 'nofollow', download: '' do
%i.fa.fa-download
%span= _('Download tar')
- if pipeline && pipeline.latest_builds_with_artifacts.any?
......@@ -36,6 +32,5 @@
- pipeline.latest_builds_with_artifacts.each do |job|
%li
= link_to latest_succeeded_project_artifacts_path(project, "#{ref}/download", job: job.name), rel: 'nofollow', download: '' do
%i.fa.fa-download
%span
#{s_('DownloadArtifacts|Download')} '#{job.name}'
......@@ -11,19 +11,16 @@
- if can_create_issue
%li
= link_to new_project_issue_path(@project) do
= icon('exclamation-circle fw')
#{ _('New issue') }
- if merge_project
%li
= link_to project_new_merge_request_path(merge_project) do
= icon('tasks fw')
#{ _('New merge request') }
- if can_create_snippet
%li
= link_to new_project_snippet_path(@project) do
= icon('file-text-o fw')
#{ _('New snippet') }
- if can_create_issue || merge_project || can_create_snippet
......@@ -32,20 +29,16 @@
- if can?(current_user, :push_code, @project)
%li
= link_to project_new_blob_path(@project, @project.default_branch || 'master') do
= icon('file fw')
#{ _('New file') }
%li
= link_to new_project_branch_path(@project) do
= icon('code-fork fw')
#{ _('New branch') }
%li
= link_to new_project_tag_path(@project) do
= icon('tags fw')
#{ _('New tag') }
- elsif current_user && current_user.already_forked?(@project)
%li
= link_to project_new_blob_path(@project, @project.default_branch || 'master') do
= icon('file fw')
#{ _('New file') }
- elsif can?(current_user, :fork_project, @project)
%li
......@@ -55,5 +48,4 @@
- fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id,
continue: continue_params)
= link_to fork_path, method: :post do
= icon('file fw')
#{ _('New file') }
......@@ -6,7 +6,6 @@
- @content_class = limited_container_width
- page_title "#{@commit.title} (#{@commit.short_id})", "Commits"
- page_description @commit.description
= render "projects/commits/head"
.container-fluid{ class: [limited_container_width, container_class] }
= render "commit_box"
......
= content_for :sub_nav do
.scrolling-tabs-container.sub-nav-scroll
= render 'shared/nav_scroll'
.nav-links.sub-nav.scrolling-tabs
%ul{ class: (container_class) }
= nav_link(controller: %w(tree blob blame edit_tree new_tree find_file)) do
= link_to project_tree_path(@project) do
#{ _('Files') }
= nav_link(controller: [:commit, :commits]) do
= link_to project_commits_path(@project, current_ref) do
#{ _('Commits') }
= nav_link(html_options: {class: branches_tab_class}) do
= link_to project_branches_path(@project) do
#{ _('Branches') }
= nav_link(controller: [:tags, :releases]) do
= link_to project_tags_path(@project) do
#{ _('Tags') }
= nav_link(path: 'graphs#show') do
= link_to project_graph_path(@project, current_ref) do
#{ _('Contributors') }
= nav_link(controller: %w(network)) do
= link_to project_network_path(@project, current_ref) do
#{ s_('ProjectNetworkGraph|Graph') }
= nav_link(controller: :compare) do
= link_to project_compare_index_path(@project, from: @repository.root_ref, to: current_ref) do
#{ _('Compare') }
= nav_link(path: 'graphs#charts') do
= link_to charts_project_graph_path(@project, current_ref) do
#{ _('Charts') }
- if @project.feature_available?(:file_locks)
= nav_link(controller: [:path_locks]) do
= link_to project_path_locks_path(@project) do
Locked Files
......@@ -5,9 +5,6 @@
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, project_commits_url(@project, @ref, rss_url_options), title: "#{@project.name}:#{@ref} commits")
= content_for :sub_nav do
= render "head"
.js-project-commits-show{ 'data-commits-limit' => @limit }
%div{ class: container_class }
.tree-holder
......
- @no_container = true
- breadcrumb_title "Compare Revisions"
- page_title "Compare"
= render "projects/commits/head"
%div{ class: container_class }
.sub-header-block
......
- @no_container = true
- add_to_breadcrumbs "Compare Revisions", project_compare_index_path(@project)
- page_title "#{params[:from]}...#{params[:to]}"
= render "projects/commits/head"
%div{ class: container_class }
.sub-header-block.no-bottom-space
......
......@@ -4,8 +4,6 @@
= page_specific_javascript_bundle_tag('common_vue')
= page_specific_javascript_bundle_tag('cycle_analytics')
= render "projects/head"
#cycle-analytics{ class: container_class, "v-cloak" => "true", data: { request_path: project_cycle_analytics_path(@project) } }
- if @cycle_analytics_no_data
.landing.content-block{ "v-if" => "!isOverviewDialogDismissed" }
......
- environment = local_assigns.fetch(:environment, nil)
- file_hash = hexdigest(diff_file.file_path)
- image_diff = diff_file.rich_viewer && diff_file.rich_viewer.partial_name == 'image'
- image_replaced = diff_file.old_content_sha && diff_file.old_content_sha != diff_file.content_sha
.diff-file.file-holder{ id: file_hash, data: diff_file_html_data(project, diff_file.file_path, diff_file.content_sha) }
.js-file-title.file-title-flex-parent
.file-header-content
......@@ -17,6 +20,9 @@
= edit_blob_link(@merge_request.source_project, @merge_request.source_branch, diff_file.new_path,
blob: blob, link_opts: link_opts)
- if image_diff && image_replaced
= view_file_button(diff_file.old_content_sha, diff_file.old_path, project, replaced: true)
= view_file_button(diff_file.content_sha, diff_file.file_path, project)
= view_on_environment_button(diff_file.content_sha, diff_file.file_path, environment) if environment
......
......@@ -15,8 +15,7 @@
.two-up.view
%span.wrap
.frame.deleted
%a{ href: project_blob_path(@project, tree_join(diff_file.old_content_sha, diff_file.old_path)) }
= image_tag(old_blob_raw_path, alt: diff_file.old_path)
= image_tag(old_blob_raw_path, alt: diff_file.old_path)
%p.image-info.hide
%span.meta-filesize= number_to_human_size(old_blob.size)
|
......@@ -27,8 +26,7 @@
%span.meta-height
%span.wrap
.frame.added
%a{ href: project_blob_path(@project, tree_join(diff_file.content_sha, diff_file.new_path)) }
= image_tag(blob_raw_path, alt: diff_file.new_path)
= image_tag(blob_raw_path, alt: diff_file.new_path)
%p.image-info.hide
%span.meta-filesize= number_to_human_size(blob.size)
|
......
......@@ -7,8 +7,6 @@
= webpack_bundle_tag('common_vue')
= webpack_bundle_tag('service_desk')
= render "projects/settings/head"
.project-edit-container
%section.settings.general-settings
.settings-header
......
......@@ -3,7 +3,6 @@
= render partial: 'flash_messages', locals: { project: @project }
= render "projects/head"
= render "home_panel"
.row-content-block.second-block.center
......
- @no_container = true
- page_title "Edit", @environment.name, "Environments"
= render "projects/pipelines/head"
%div{ class: container_class }
%h3.page-title
......
- @no_container = true
- page_title "Environments"
= render "projects/pipelines/head"
- content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('common_vue')
......
- @no_container = true
- page_title "Environments"
- add_to_breadcrumbs("Pipelines", project_pipelines_path(@project))
= render "projects/pipelines/head"
- content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('common_vue')
......
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