Commit 57c97a10 authored by Mike Greiling's avatar Mike Greiling

Merge branch 'master' into 37220-es-modules

* master: (148 commits)
  Remove gaps under nav on build page
  Replace the 'project/snippets.feature' spinach test with an rspec analog
  Use correct group members path for members flyout link
  Replace the 'project/commits/revert.feature' spinach test with an rspec analog
  Merge branch 'rs-incoming-email-domain-docs' into 'security-10-0'
  Replace the 'project/archived.feature' spinach test with an rspec analog
  Fix broken link in docs/api/wiki.md
  Fixed the new sidebars width when browser has scrollbars
  Improve 'spec/features/profiles/*' specs
  Replace the 'search.feature' spinach test with an rspec analog
  dedupe yarn packages
  add dependency approvals (all MIT license)
  update build image to latest with node 8.x, yarn 1.0.2, and chrome 61
  Ensure we use `Entities::User` for non-admin `users/:id` API requests
  Minor update to address Sean McGivern's comment.
  Add data migration
  Fix setting share_with_group_lock
  created services for keys
  Prepare Repository#merge for migration to Gitaly
  Never connect to webpack-dev-server over SSL
  ...
parents 14a932f1 4cadf22e
image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.3-golang-1.8-git-2.13-phantomjs-2.1-node-7.1-postgresql-9.6" image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.3-golang-1.8-git-2.13-phantomjs-2.1-node-8.x-yarn-1.0-postgresql-9.6"
.default-cache: &default-cache .default-cache: &default-cache
key: "ruby-233-with-yarn" key: "ruby-233-with-yarn"
...@@ -191,6 +191,9 @@ review-docs-deploy: ...@@ -191,6 +191,9 @@ review-docs-deploy:
stage: build stage: build
environment: environment:
name: review-docs/$CI_COMMIT_REF_NAME name: review-docs/$CI_COMMIT_REF_NAME
# DOCS_REVIEW_APPS_DOMAIN and DOCS_GITLAB_REPO_SUFFIX are secret variables
# Discussion: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14236/diffs#note_40140693
url: http://$CI_COMMIT_REF_SLUG-built-from-ce-ee.$DOCS_REVIEW_APPS_DOMAIN/$DOCS_GITLAB_REPO_SUFFIX
on_stop: review-docs-cleanup on_stop: review-docs-cleanup
script: script:
- gem install gitlab --no-doc - gem install gitlab --no-doc
...@@ -543,7 +546,7 @@ karma: ...@@ -543,7 +546,7 @@ karma:
<<: *dedicated-runner <<: *dedicated-runner
<<: *except-docs <<: *except-docs
<<: *pull-cache <<: *pull-cache
image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.3-golang-1.8-git-2.13-chrome-60.0-node-7.1-postgresql-9.6" image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.3-golang-1.8-git-2.13-chrome-61.0-node-8.x-yarn-1.0-postgresql-9.6"
stage: test stage: test
variables: variables:
BABEL_ENV: "coverage" BABEL_ENV: "coverage"
......
...@@ -643,7 +643,7 @@ Metrics/ClassLength: ...@@ -643,7 +643,7 @@ Metrics/ClassLength:
# of test cases needed to validate a method. # of test cases needed to validate a method.
Metrics/CyclomaticComplexity: Metrics/CyclomaticComplexity:
Enabled: true Enabled: true
Max: 14 Max: 13
# Limit lines to 80 characters. # Limit lines to 80 characters.
Metrics/LineLength: Metrics/LineLength:
...@@ -665,7 +665,7 @@ Metrics/ParameterLists: ...@@ -665,7 +665,7 @@ Metrics/ParameterLists:
# A complexity metric geared towards measuring complexity for a human reader. # A complexity metric geared towards measuring complexity for a human reader.
Metrics/PerceivedComplexity: Metrics/PerceivedComplexity:
Enabled: true Enabled: true
Max: 17 Max: 15
# Lint ######################################################################## # Lint ########################################################################
......
...@@ -286,7 +286,10 @@ might be edited to make them small and simple. ...@@ -286,7 +286,10 @@ might be edited to make them small and simple.
Please submit Feature Proposals using the ['Feature Proposal' issue template](.gitlab/issue_templates/Feature Proposal.md) provided on the issue tracker. Please submit Feature Proposals using the ['Feature Proposal' issue template](.gitlab/issue_templates/Feature Proposal.md) provided on the issue tracker.
For changes in the interface, it can be helpful to create a mockup first. For changes in the interface, it is helpful to include a mockup. Issues that add to, or change, the interface should
be given the ~"UX" label. This will allow the UX team to provide input and guidance. You may
need to ask one of the [core team] members to add the label, if you do not have permissions to do it by yourself.
If you want to create something yourself, consider opening an issue first to If you want to create something yourself, consider opening an issue first to
discuss whether it is interesting to include this in GitLab. discuss whether it is interesting to include this in GitLab.
......
...@@ -357,7 +357,7 @@ GEM ...@@ -357,7 +357,7 @@ GEM
rake rake
grape_logging (1.7.0) grape_logging (1.7.0)
grape grape
grpc (1.4.5) grpc (1.6.0)
google-protobuf (~> 3.1) google-protobuf (~> 3.1)
googleauth (~> 0.5.1) googleauth (~> 0.5.1)
haml (4.0.7) haml (4.0.7)
......
...@@ -7,6 +7,7 @@ class DeleteModal { ...@@ -7,6 +7,7 @@ class DeleteModal {
this.$branchName = $('.js-branch-name', this.$modal); this.$branchName = $('.js-branch-name', this.$modal);
this.$confirmInput = $('.js-delete-branch-input', this.$modal); this.$confirmInput = $('.js-delete-branch-input', this.$modal);
this.$deleteBtn = $('.js-delete-branch', this.$modal); this.$deleteBtn = $('.js-delete-branch', this.$modal);
this.$notMerged = $('.js-not-merged', this.$modal);
this.bindEvents(); this.bindEvents();
} }
...@@ -16,8 +17,10 @@ class DeleteModal { ...@@ -16,8 +17,10 @@ class DeleteModal {
} }
setModalData(e) { setModalData(e) {
this.branchName = e.currentTarget.dataset.branchName || ''; const branchData = e.currentTarget.dataset;
this.deletePath = e.currentTarget.dataset.deletePath || ''; this.branchName = branchData.branchName || '';
this.deletePath = branchData.deletePath || '';
this.isMerged = !!branchData.isMerged;
this.updateModal(); this.updateModal();
} }
...@@ -30,6 +33,7 @@ class DeleteModal { ...@@ -30,6 +33,7 @@ class DeleteModal {
this.$confirmInput.val(''); this.$confirmInput.val('');
this.$deleteBtn.attr('href', this.deletePath); this.$deleteBtn.attr('href', this.deletePath);
this.$deleteBtn.attr('disabled', true); this.$deleteBtn.attr('disabled', true);
this.$notMerged.toggleClass('hidden', this.isMerged);
} }
} }
......
...@@ -15,6 +15,7 @@ class DropdownUser extends gl.FilteredSearchDropdown { ...@@ -15,6 +15,7 @@ class DropdownUser extends gl.FilteredSearchDropdown {
params: { params: {
per_page: 20, per_page: 20,
active: true, active: true,
group_id: this.getGroupId(),
project_id: this.getProjectId(), project_id: this.getProjectId(),
current_user: true, current_user: true,
}, },
...@@ -47,6 +48,10 @@ class DropdownUser extends gl.FilteredSearchDropdown { ...@@ -47,6 +48,10 @@ class DropdownUser extends gl.FilteredSearchDropdown {
super.renderContent(forceShowList); super.renderContent(forceShowList);
} }
getGroupId() {
return this.input.getAttribute('data-group-id');
}
getProjectId() { getProjectId() {
return this.input.getAttribute('data-project-id'); return this.input.getAttribute('data-project-id');
} }
......
...@@ -77,10 +77,11 @@ export const hideMenu = (el) => { ...@@ -77,10 +77,11 @@ export const hideMenu = (el) => {
export const moveSubItemsToPosition = (el, subItems) => { export const moveSubItemsToPosition = (el, subItems) => {
const boundingRect = el.getBoundingClientRect(); const boundingRect = el.getBoundingClientRect();
const top = calculateTop(boundingRect, subItems.offsetHeight); const top = calculateTop(boundingRect, subItems.offsetHeight);
const left = sidebar ? sidebar.offsetWidth : 50;
const isAbove = top < boundingRect.top; const isAbove = top < boundingRect.top;
subItems.classList.add('fly-out-list'); subItems.classList.add('fly-out-list');
subItems.style.transform = `translate3d(0, ${Math.floor(top) - headerHeight}px, 0)`; // eslint-disable-line no-param-reassign subItems.style.transform = `translate3d(${left}px, ${Math.floor(top) - headerHeight}px, 0)`; // eslint-disable-line no-param-reassign
const subItemsRect = subItems.getBoundingClientRect(); const subItemsRect = subItems.getBoundingClientRect();
...@@ -148,7 +149,7 @@ export const documentMouseMove = (e) => { ...@@ -148,7 +149,7 @@ export const documentMouseMove = (e) => {
export const subItemsMouseLeave = (relatedTarget) => { export const subItemsMouseLeave = (relatedTarget) => {
clearTimeout(timeoutId); clearTimeout(timeoutId);
if (!relatedTarget.closest(`.${IS_OVER_CLASS}`)) { if (relatedTarget && !relatedTarget.closest(`.${IS_OVER_CLASS}`)) {
hideMenu(currentOpenMenu); hideMenu(currentOpenMenu);
} }
}; };
......
...@@ -127,13 +127,6 @@ import DropdownUtils from './filtered_search/dropdown_utils'; ...@@ -127,13 +127,6 @@ import DropdownUtils from './filtered_search/dropdown_utils';
$('.has-tooltip', $value).tooltip({ $('.has-tooltip', $value).tooltip({
container: 'body' container: 'body'
}); });
return $value.find('a').each(function(i) {
return setTimeout((function(_this) {
return function() {
return gl.animate.animate($(_this), 'pulse');
};
})(this), 200 * i);
});
}); });
}; };
$dropdown.glDropdown({ $dropdown.glDropdown({
......
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-param-reassign, no-void, prefer-template, no-var, new-cap, prefer-arrow-callback, consistent-return, max-len */
(function() {
(function(w) {
if (w.gl == null) {
w.gl = {};
}
if (gl.animate == null) {
gl.animate = {};
}
gl.animate.animate = function($el, animation, options, done) {
if ((options != null ? options.cssStart : void 0) != null) {
$el.css(options.cssStart);
}
$el.removeClass(animation + ' animated').addClass(animation + ' animated').one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', function() {
$(this).removeClass(animation + ' animated');
if (done != null) {
done();
}
if ((options != null ? options.cssEnd : void 0) != null) {
$el.css(options.cssEnd);
}
});
};
gl.animate.animateEach = function($els, animation, time, options, done) {
var dfd;
dfd = $.Deferred();
if (!$els.length) {
dfd.resolve();
}
$els.each(function(i) {
setTimeout((function(_this) {
return function() {
var $this;
$this = $(_this);
return gl.animate.animate($this, animation, options, function() {
if (i === $els.length - 1) {
dfd.resolve();
if (done != null) {
return done();
}
}
});
};
})(this), time * i);
});
return dfd.promise();
};
})(window);
}).call(window);
...@@ -39,7 +39,6 @@ import './commit/file'; ...@@ -39,7 +39,6 @@ import './commit/file';
import './commit/image_file'; import './commit/image_file';
// lib/utils // lib/utils
import './lib/utils/animate';
import './lib/utils/bootstrap_linked_tabs'; import './lib/utils/bootstrap_linked_tabs';
import { handleLocationHash } from './lib/utils/common_utils'; import { handleLocationHash } from './lib/utils/common_utils';
import './lib/utils/datetime_utility'; import './lib/utils/datetime_utility';
......
...@@ -45,7 +45,7 @@ import _ from 'underscore'; ...@@ -45,7 +45,7 @@ import _ from 'underscore';
if (issueUpdateURL) { if (issueUpdateURL) {
milestoneLinkTemplate = _.template('<a href="/<%- full_path %>/milestones/<%- iid %>" class="bold has-tooltip" data-container="body" title="<%- remaining %>"><%- title %></a>'); milestoneLinkTemplate = _.template('<a href="/<%- full_path %>/milestones/<%- iid %>" class="bold has-tooltip" data-container="body" title="<%- remaining %>"><%- title %></a>');
milestoneLinkNoneTemplate = '<span class="no-value">None</span>'; milestoneLinkNoneTemplate = '<span class="no-value">None</span>';
collapsedSidebarLabelTemplate = _.template('<span class="has-tooltip" data-container="body" title="<%- remaining %>" data-placement="left"> <%- title %> </span>'); collapsedSidebarLabelTemplate = _.template('<span class="has-tooltip" data-container="body" title="<%- name %><br /><%- remaining %>" data-placement="left" data-html="true"> <%- title %> </span>');
} }
return $dropdown.glDropdown({ return $dropdown.glDropdown({
showMenuAbove: showMenuAbove, showMenuAbove: showMenuAbove,
...@@ -208,6 +208,7 @@ import _ from 'underscore'; ...@@ -208,6 +208,7 @@ import _ from 'underscore';
if (data.milestone != null) { if (data.milestone != null) {
data.milestone.full_path = _this.currentProject.full_path; data.milestone.full_path = _this.currentProject.full_path;
data.milestone.remaining = gl.utils.timeFor(data.milestone.due_date); data.milestone.remaining = gl.utils.timeFor(data.milestone.due_date);
data.milestone.name = data.milestone.title;
$value.html(milestoneLinkTemplate(data.milestone)); $value.html(milestoneLinkTemplate(data.milestone));
return $sidebarCollapsedValue.find('span').html(collapsedSidebarLabelTemplate(data.milestone)); return $sidebarCollapsedValue.find('span').html(collapsedSidebarLabelTemplate(data.milestone));
} else { } else {
......
<script> <script>
/* global Flash */ /* global Flash */
import _ from 'underscore'; import _ from 'underscore';
import statusCodes from '../../lib/utils/http_status';
import MonitoringService from '../services/monitoring_service'; import MonitoringService from '../services/monitoring_service';
import GraphGroup from './graph_group.vue'; import GraphGroup from './graph_group.vue';
import Graph from './graph.vue'; import Graph from './graph.vue';
import EmptyState from './empty_state.vue'; import EmptyState from './empty_state.vue';
import MonitoringStore from '../stores/monitoring_store'; import MonitoringStore from '../stores/monitoring_store';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
import { backOff, convertPermissionToBoolean } from '../../lib/utils/common_utils'; import { convertPermissionToBoolean } from '../../lib/utils/common_utils';
export default { export default {
...@@ -22,10 +21,9 @@ ...@@ -22,10 +21,9 @@
hasMetrics: convertPermissionToBoolean(metricsData.hasMetrics), hasMetrics: convertPermissionToBoolean(metricsData.hasMetrics),
documentationPath: metricsData.documentationPath, documentationPath: metricsData.documentationPath,
settingsPath: metricsData.settingsPath, settingsPath: metricsData.settingsPath,
endpoint: metricsData.additionalMetrics, metricsEndpoint: metricsData.additionalMetrics,
deploymentEndpoint: metricsData.deploymentEndpoint, deploymentEndpoint: metricsData.deploymentEndpoint,
showEmptyState: true, showEmptyState: true,
backOffRequestCounter: 0,
updateAspectRatio: false, updateAspectRatio: false,
updatedAspectRatios: 0, updatedAspectRatios: 0,
resizeThrottled: {}, resizeThrottled: {},
...@@ -40,50 +38,16 @@ ...@@ -40,50 +38,16 @@
methods: { methods: {
getGraphsData() { getGraphsData() {
const maxNumberOfRequests = 3;
this.state = 'loading'; this.state = 'loading';
backOff((next, stop) => { Promise.all([
this.service.get().then((resp) => { this.service.getGraphsData()
if (resp.status === statusCodes.NO_CONTENT) { .then(data => this.store.storeMetrics(data)),
this.backOffRequestCounter = this.backOffRequestCounter += 1; this.service.getDeploymentData()
if (this.backOffRequestCounter < maxNumberOfRequests) { .then(data => this.store.storeDeploymentData(data))
next(); .catch(() => new Flash('Error getting deployment information.')),
} else { ])
stop(new Error('Failed to connect to the prometheus server')); .then(() => { this.showEmptyState = false; })
} .catch(() => { this.state = 'unableToConnect'; });
} else {
stop(resp);
}
}).catch(stop);
})
.then((resp) => {
if (resp.status === statusCodes.NO_CONTENT) {
this.state = 'unableToConnect';
return false;
}
return resp.json();
})
.then((metricGroupsData) => {
if (!metricGroupsData) return false;
this.store.storeMetrics(metricGroupsData.data);
return this.getDeploymentData();
})
.then((deploymentData) => {
if (deploymentData !== false) {
this.store.storeDeploymentData(deploymentData.deployments);
this.showEmptyState = false;
}
return {};
})
.catch(() => {
this.state = 'unableToConnect';
});
},
getDeploymentData() {
return this.service.getDeploymentData(this.deploymentEndpoint)
.then(resp => resp.json())
.catch(() => new Flash('Error getting deployment information.'));
}, },
resize() { resize() {
...@@ -100,7 +64,10 @@ ...@@ -100,7 +64,10 @@
}, },
created() { created() {
this.service = new MonitoringService(this.endpoint); this.service = new MonitoringService({
metricsEndpoint: this.metricsEndpoint,
deploymentEndpoint: this.deploymentEndpoint,
});
eventHub.$on('toggleAspectRatio', this.toggleAspectRatio); eventHub.$on('toggleAspectRatio', this.toggleAspectRatio);
}, },
......
import Vue from 'vue'; import Vue from 'vue';
import VueResource from 'vue-resource'; import VueResource from 'vue-resource';
import statusCodes from '../../lib/utils/http_status';
import { backOff } from '../../lib/utils/common_utils';
Vue.use(VueResource); Vue.use(VueResource);
const MAX_REQUESTS = 3;
function backOffRequest(makeRequestCallback) {
let requestCounter = 0;
return backOff((next, stop) => {
makeRequestCallback().then((resp) => {
if (resp.status === statusCodes.NO_CONTENT) {
requestCounter += 1;
if (requestCounter < MAX_REQUESTS) {
next();
} else {
stop(new Error('Failed to connect to the prometheus server'));
}
} else {
stop(resp);
}
}).catch(stop);
});
}
export default class MonitoringService { export default class MonitoringService {
constructor(endpoint) { constructor({ metricsEndpoint, deploymentEndpoint }) {
this.graphs = Vue.resource(endpoint); this.metricsEndpoint = metricsEndpoint;
this.deploymentEndpoint = deploymentEndpoint;
} }
get() { getGraphsData() {
return this.graphs.get(); return backOffRequest(() => Vue.http.get(this.metricsEndpoint))
.then(resp => resp.json())
.then((response) => {
if (!response || !response.data) {
throw new Error('Unexpected metrics data response from prometheus endpoint');
}
return response.data;
});
} }
// eslint-disable-next-line class-methods-use-this getDeploymentData() {
getDeploymentData(endpoint) { return backOffRequest(() => Vue.http.get(this.deploymentEndpoint))
return Vue.http.get(endpoint); .then(resp => resp.json())
.then((response) => {
if (!response || !response.deployments) {
throw new Error('Unexpected deployment data response from prometheus endpoint');
}
return response.deployments;
});
} }
} }
...@@ -15,7 +15,6 @@ export default class NewNavSidebar { ...@@ -15,7 +15,6 @@ export default class NewNavSidebar {
this.$openSidebar = $('.toggle-mobile-nav'); this.$openSidebar = $('.toggle-mobile-nav');
this.$closeSidebar = $('.close-nav-button'); this.$closeSidebar = $('.close-nav-button');
this.$sidebarToggle = $('.js-toggle-sidebar'); this.$sidebarToggle = $('.js-toggle-sidebar');
this.$topLevelLinks = $('.sidebar-top-level-items > li > a');
} }
bindEvents() { bindEvents() {
...@@ -56,10 +55,6 @@ export default class NewNavSidebar { ...@@ -56,10 +55,6 @@ export default class NewNavSidebar {
this.$page.toggleClass('page-with-icon-sidebar', breakpoint === 'sm' ? true : collapsed); this.$page.toggleClass('page-with-icon-sidebar', breakpoint === 'sm' ? true : collapsed);
} }
NewNavSidebar.setCollapsedCookie(collapsed); NewNavSidebar.setCollapsedCookie(collapsed);
this.$topLevelLinks.attr('title', function updateTopLevelTitle() {
return collapsed ? this.getAttribute('aria-label') : '';
});
} }
render() { render() {
......
...@@ -86,7 +86,7 @@ ...@@ -86,7 +86,7 @@
<div class="note-actions"> <div class="note-actions">
<span <span
v-if="accessLevel" v-if="accessLevel"
class="note-role">{{accessLevel}}</span> class="note-role note-role-access">{{accessLevel}}</span>
<div <div
v-if="canAddAwardEmoji" v-if="canAddAwardEmoji"
class="note-actions-item"> class="note-actions-item">
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
@import "framework/flash"; @import "framework/flash";
@import "framework/forms"; @import "framework/forms";
@import "framework/gfm"; @import "framework/gfm";
@import "framework/gitlab-theme";
@import "framework/header"; @import "framework/header";
@import "framework/highlight"; @import "framework/highlight";
@import "framework/issue_box"; @import "framework/issue_box";
......
/**
* Styles the GitLab application with a specific color theme
*/
@mixin gitlab-theme($color-100, $color-200, $color-500, $color-700, $color-800, $color-900, $color-alternate) {
// Header
header.navbar-gitlab-new {
background: linear-gradient(to right, $color-900, $color-800);
.navbar-collapse {
color: $color-200;
}
.container-fluid {
.navbar-toggle {
border-left: 1px solid lighten($color-700, 10%);
}
}
.navbar-sub-nav,
.navbar-nav {
> li {
> a:hover,
> a:focus {
background-color: rgba($color-200, .2);
}
&.active > a,
&.dropdown.open > a {
color: $color-900;
background-color: $color-alternate;
svg {
fill: currentColor;
}
}
&.line-separator {
border-left: 1px solid rgba($color-200, .2);
}
}
}
.navbar-sub-nav {
color: $color-200;
}
.nav {
> li {
color: $color-200;
> a {
svg {
fill: $color-200;
}
&.header-user-dropdown-toggle {
.header-user-avatar {
border-color: $color-200;
}
}
&:hover,
&:focus {
@media (min-width: $screen-sm-min) {
background-color: rgba($color-200, .2);
}
svg {
fill: currentColor;
}
}
}
&.active > a,
&.dropdown.open > a {
color: $color-900;
background-color: $color-alternate;
&:hover {
svg {
fill: $color-900;
}
}
}
.impersonated-user,
.impersonated-user:hover {
svg {
fill: $color-900;
}
}
}
}
}
.title {
> a {
&:hover,
&:focus {
background-color: rgba($color-200, .2);
}
}
}
.search {
form {
background-color: rgba($color-200, .2);
&:hover {
background-color: rgba($color-200, .3);
}
}
.location-badge {
color: $color-100;
background-color: rgba($color-200, .1);
border-right: 1px solid $color-800;
}
.search-input::placeholder {
color: rgba($color-200, .8);
}
.search-input-wrap {
.search-icon,
.clear-icon {
color: rgba($color-200, .8);
}
}
&.search-active {
form {
background-color: $white-light;
}
.location-badge {
color: $gl-text-color;
}
.search-input-wrap {
.search-icon {
color: rgba($color-200, .8);
}
}
}
}
.btn-sign-in {
background-color: $color-100;
color: $color-900;
}
// Sidebar
.nav-sidebar li.active {
box-shadow: inset 4px 0 0 $color-700;
> a {
color: $color-800;
}
svg {
fill: $color-800;
}
}
.sidebar-top-level-items > li.active .badge {
color: $color-800;
}
.nav-links li.active a {
border-bottom-color: $color-500;
.badge {
font-weight: $gl-font-weight-bold;
}
}
}
body {
&.ui_indigo {
@include gitlab-theme($indigo-100, $indigo-200, $indigo-500, $indigo-700, $indigo-800, $indigo-900, $white-light);
}
&.ui_dark {
@include gitlab-theme($theme-gray-100, $theme-gray-200, $theme-gray-500, $theme-gray-700, $theme-gray-800, $theme-gray-900, $white-light);
}
&.ui_blue {
@include gitlab-theme($theme-blue-100, $theme-blue-200, $theme-blue-500, $theme-blue-700, $theme-blue-800, $theme-blue-900, $white-light);
}
&.ui_green {
@include gitlab-theme($theme-green-100, $theme-green-200, $theme-green-500, $theme-green-700, $theme-green-800, $theme-green-900, $white-light);
}
&.ui_light {
@include gitlab-theme($theme-gray-900, $theme-gray-700, $theme-gray-800, $theme-gray-700, $theme-gray-700, $theme-gray-100, $theme-gray-700);
header.navbar-gitlab-new {
background: $theme-gray-100;
box-shadow: 0 2px 0 0 $border-color;
.logo-text svg {
fill: $theme-gray-900;
}
.navbar-sub-nav,
.navbar-nav {
> li {
> a:hover,
> a:focus {
color: $theme-gray-900;
}
&.active > a {
color: $white-light;
&:hover {
color: $white-light;
}
}
}
}
.container-fluid {
.navbar-toggle,
.navbar-toggle:hover {
color: $theme-gray-700;
border-left: 1px solid $theme-gray-200;
}
}
}
.search {
form {
background-color: $white-light;
box-shadow: inset 0 0 0 1px $border-color;
&:hover {
background-color: $white-light;
box-shadow: inset 0 0 0 1px $blue-100;
.location-badge {
box-shadow: inset 0 0 0 1px $blue-100;
}
}
}
.search-input-wrap {
.search-icon {
color: $theme-gray-200;
}
}
.location-badge {
color: $theme-gray-700;
box-shadow: inset 0 0 0 1px $border-color;
background-color: $nav-badge-bg;
border-right: 0;
}
}
.nav-sidebar li.active {
> a {
color: $theme-gray-900;
}
svg {
fill: $theme-gray-900;
}
}
.sidebar-top-level-items > li.active .badge {
color: $theme-gray-900;
}
}
}
...@@ -111,7 +111,6 @@ header { ...@@ -111,7 +111,6 @@ header {
svg { svg {
height: 16px; height: 16px;
width: 23px; width: 23px;
fill: currentColor;
} }
} }
......
...@@ -328,7 +328,7 @@ ...@@ -328,7 +328,7 @@
border-bottom: 1px solid $border-color; border-bottom: 1px solid $border-color;
transition: padding $sidebar-transition-duration; transition: padding $sidebar-transition-duration;
text-align: center; text-align: center;
margin-top: $header-height; margin-top: $new-navbar-height;
.container-fluid { .container-fluid {
position: relative; position: relative;
......
...@@ -78,16 +78,16 @@ ...@@ -78,16 +78,16 @@
.right-sidebar { .right-sidebar {
border-left: 1px solid $border-color; border-left: 1px solid $border-color;
height: calc(100% - #{$header-height}); height: calc(100% - #{$new-navbar-height});
&.affix { &.affix {
position: fixed; position: fixed;
top: $header-height; top: $new-navbar-height;
} }
} }
.with-performance-bar .right-sidebar.affix { .with-performance-bar .right-sidebar.affix {
top: $header-height + $performance-bar-height; top: $new-navbar-height + $performance-bar-height;
} }
@mixin maintain-sidebar-dimensions { @mixin maintain-sidebar-dimensions {
......
...@@ -13,6 +13,7 @@ $sidebar-breakpoint: 1024px; ...@@ -13,6 +13,7 @@ $sidebar-breakpoint: 1024px;
$darken-normal-factor: 7%; $darken-normal-factor: 7%;
$darken-dark-factor: 10%; $darken-dark-factor: 10%;
$darken-border-factor: 5%; $darken-border-factor: 5%;
$darken-border-dashed-factor: 25%;
$white-light: #fff; $white-light: #fff;
$white-normal: #f0f0f0; $white-normal: #f0f0f0;
...@@ -74,6 +75,8 @@ $red-700: #a62d19; ...@@ -74,6 +75,8 @@ $red-700: #a62d19;
$red-800: #8b2615; $red-800: #8b2615;
$red-900: #711e11; $red-900: #711e11;
// GitLab themes
$indigo-50: #f7f7ff; $indigo-50: #f7f7ff;
$indigo-100: #ebebfa; $indigo-100: #ebebfa;
$indigo-200: #d1d1f0; $indigo-200: #d1d1f0;
...@@ -86,6 +89,43 @@ $indigo-800: #393982; ...@@ -86,6 +89,43 @@ $indigo-800: #393982;
$indigo-900: #292961; $indigo-900: #292961;
$indigo-950: #1a1a40; $indigo-950: #1a1a40;
$theme-gray-50: #fafafa;
$theme-gray-100: #f2f2f2;
$theme-gray-200: #dfdfdf;
$theme-gray-300: #cccccc;
$theme-gray-400: #bababa;
$theme-gray-500: #a7a7a7;
$theme-gray-600: #949494;
$theme-gray-700: #707070;
$theme-gray-800: #4f4f4f;
$theme-gray-900: #2e2e2e;
$theme-gray-950: #1f1f1f;
$theme-blue-50: #f4f8fc;
$theme-blue-100: #e6edf5;
$theme-blue-200: #c8d7e6;
$theme-blue-300: #97b3cf;
$theme-blue-400: #648cb4;
$theme-blue-500: #4a79a8;
$theme-blue-600: #3e6fa0;
$theme-blue-700: #305c88;
$theme-blue-800: #25496e;
$theme-blue-900: #1a3652;
$theme-blue-950: #0f2235;
$theme-green-50: #f2faf6;
$theme-green-100: #e4f3ea;
$theme-green-200: #c0dfcd;
$theme-green-300: #8ac2a1;
$theme-green-400: #52a274;
$theme-green-500: #35935c;
$theme-green-600: #288a50;
$theme-green-700: #1c7441;
$theme-green-800: #145d33;
$theme-green-900: #0d4524;
$theme-green-950: #072d16;
$black: #000; $black: #000;
$black-transparent: rgba(0, 0, 0, 0.3); $black-transparent: rgba(0, 0, 0, 0.3);
$almost-black: #242424; $almost-black: #242424;
...@@ -95,6 +135,7 @@ $border-white-normal: darken($white-normal, $darken-border-factor); ...@@ -95,6 +135,7 @@ $border-white-normal: darken($white-normal, $darken-border-factor);
$border-gray-light: darken($gray-light, $darken-border-factor); $border-gray-light: darken($gray-light, $darken-border-factor);
$border-gray-normal: darken($gray-normal, $darken-border-factor); $border-gray-normal: darken($gray-normal, $darken-border-factor);
$border-gray-normal-dashed: darken($gray-normal, $darken-border-dashed-factor);
$border-gray-dark: darken($white-normal, $darken-border-factor); $border-gray-dark: darken($white-normal, $darken-border-factor);
/* /*
......
...@@ -9,10 +9,20 @@ ...@@ -9,10 +9,20 @@
header.navbar-gitlab-new { header.navbar-gitlab-new {
color: $white-light; color: $white-light;
background: linear-gradient(to right, $indigo-900, $indigo-800);
border-bottom: 0; border-bottom: 0;
min-height: $new-navbar-height; min-height: $new-navbar-height;
.logo-text {
line-height: initial;
svg {
width: 55px;
height: 14px;
margin: 0;
fill: $white-light;
}
}
.header-content { .header-content {
display: -webkit-flex; display: -webkit-flex;
display: flex; display: flex;
...@@ -38,10 +48,10 @@ header.navbar-gitlab-new { ...@@ -38,10 +48,10 @@ header.navbar-gitlab-new {
img { img {
height: 28px; height: 28px;
margin-right: 10px; margin-right: 8px;
} }
> a { a {
display: -webkit-flex; display: -webkit-flex;
display: flex; display: flex;
align-items: center; align-items: center;
...@@ -54,22 +64,6 @@ header.navbar-gitlab-new { ...@@ -54,22 +64,6 @@ header.navbar-gitlab-new {
margin-right: 8px; margin-right: 8px;
} }
} }
.logo-text {
line-height: initial;
svg {
width: 55px;
height: 14px;
margin: 0;
fill: $white-light;
}
}
&:hover,
&:focus {
background-color: rgba($indigo-200, .2);
}
} }
} }
...@@ -106,7 +100,6 @@ header.navbar-gitlab-new { ...@@ -106,7 +100,6 @@ header.navbar-gitlab-new {
.navbar-collapse { .navbar-collapse {
padding-left: 0; padding-left: 0;
color: $indigo-200;
box-shadow: 0; box-shadow: 0;
@media (max-width: $screen-xs-max) { @media (max-width: $screen-xs-max) {
...@@ -132,7 +125,6 @@ header.navbar-gitlab-new { ...@@ -132,7 +125,6 @@ header.navbar-gitlab-new {
font-size: 14px; font-size: 14px;
text-align: center; text-align: center;
color: currentColor; color: currentColor;
border-left: 1px solid lighten($indigo-700, 10%);
&:hover, &:hover,
&:focus, &:focus,
...@@ -167,63 +159,49 @@ header.navbar-gitlab-new { ...@@ -167,63 +159,49 @@ header.navbar-gitlab-new {
will-change: color; will-change: color;
margin: 4px 2px; margin: 4px 2px;
padding: 6px 8px; padding: 6px 8px;
color: $indigo-200;
height: 32px; height: 32px;
@media (max-width: $screen-xs-max) { @media (max-width: $screen-xs-max) {
padding: 0; padding: 0;
} }
svg {
fill: $indigo-200;
}
&.header-user-dropdown-toggle { &.header-user-dropdown-toggle {
margin-left: 2px; margin-left: 2px;
.header-user-avatar { .header-user-avatar {
border-color: $indigo-200;
margin-right: 0; margin-right: 0;
} }
} }
}
.header-new-dropdown-toggle {
margin-right: 0;
}
> a:hover, &:hover,
> a:focus { &:focus {
text-decoration: none; text-decoration: none;
outline: 0; outline: 0;
opacity: 1; opacity: 1;
color: $white-light; color: $white-light;
@media (min-width: $screen-sm-min) {
background-color: rgba($indigo-200, .2);
}
svg { svg {
fill: currentColor; fill: currentColor;
} }
&.header-user-dropdown-toggle { &.header-user-dropdown-toggle {
.header-user-avatar { .header-user-avatar {
border-color: $white-light; border-color: $white-light;
}
} }
} }
} }
.header-new-dropdown-toggle {
margin-right: 0;
}
.impersonated-user, .impersonated-user,
.impersonated-user:hover { .impersonated-user:hover {
margin-right: 1px; margin-right: 1px;
background-color: $white-light; background-color: $white-light;
border-top-right-radius: 0; border-top-right-radius: 0;
border-bottom-right-radius: 0; border-bottom-right-radius: 0;
svg {
fill: $indigo-900;
}
} }
.impersonation-btn, .impersonation-btn,
...@@ -241,8 +219,6 @@ header.navbar-gitlab-new { ...@@ -241,8 +219,6 @@ header.navbar-gitlab-new {
&.active > a, &.active > a,
&.dropdown.open > a { &.dropdown.open > a {
color: $indigo-900;
background-color: $white-light;
svg { svg {
fill: currentColor; fill: currentColor;
...@@ -256,7 +232,6 @@ header.navbar-gitlab-new { ...@@ -256,7 +232,6 @@ header.navbar-gitlab-new {
display: -webkit-flex; display: -webkit-flex;
display: flex; display: flex;
margin: 0 0 0 6px; margin: 0 0 0 6px;
color: $indigo-200;
.dropdown-chevron { .dropdown-chevron {
position: relative; position: relative;
...@@ -274,17 +249,6 @@ header.navbar-gitlab-new { ...@@ -274,17 +249,6 @@ header.navbar-gitlab-new {
text-decoration: none; text-decoration: none;
outline: 0; outline: 0;
color: $white-light; color: $white-light;
background-color: rgba($indigo-200, .2);
svg {
fill: currentColor;
}
}
&.active > a,
&.dropdown.open > a {
color: $indigo-900;
background-color: $white-light;
svg { svg {
fill: currentColor; fill: currentColor;
...@@ -309,7 +273,6 @@ header.navbar-gitlab-new { ...@@ -309,7 +273,6 @@ header.navbar-gitlab-new {
} }
&.line-separator { &.line-separator {
border-left: 1px solid rgba($indigo-200, .2);
margin: 8px; margin: 8px;
} }
} }
...@@ -339,17 +302,14 @@ header.navbar-gitlab-new { ...@@ -339,17 +302,14 @@ header.navbar-gitlab-new {
height: 32px; height: 32px;
border: 0; border: 0;
border-radius: $border-radius-default; border-radius: $border-radius-default;
background-color: rgba($indigo-200, .2);
transition: border-color ease-in-out 0.15s, background-color ease-in-out 0.15s; transition: border-color ease-in-out 0.15s, background-color ease-in-out 0.15s;
&:hover { &:hover {
background-color: rgba($indigo-200, .3);
box-shadow: none; box-shadow: none;
} }
} }
&.search-active form { &.search-active form {
background-color: $white-light;
box-shadow: none; box-shadow: none;
.search-input { .search-input {
...@@ -377,43 +337,26 @@ header.navbar-gitlab-new { ...@@ -377,43 +337,26 @@ header.navbar-gitlab-new {
} }
.search-input::placeholder { .search-input::placeholder {
color: rgba($indigo-200, .8);
transition: color ease-in-out 0.15s; transition: color ease-in-out 0.15s;
} }
.location-badge { .location-badge {
font-size: 12px; font-size: 12px;
color: $indigo-100;
background-color: rgba($indigo-200, .1);
will-change: color;
margin: -4px 4px -4px -4px; margin: -4px 4px -4px -4px;
line-height: 25px; line-height: 25px;
padding: 4px 8px; padding: 4px 8px;
border-radius: 2px 0 0 2px; border-radius: 2px 0 0 2px;
border-right: 1px solid $indigo-800;
height: 32px; height: 32px;
transition: border-color ease-in-out 0.15s; transition: border-color ease-in-out 0.15s;
} }
.search-input-wrap {
.search-icon,
.clear-icon {
color: rgba($indigo-200, .8);
}
}
&.search-active { &.search-active {
.location-badge { .location-badge {
color: $gl-text-color;
background-color: $nav-badge-bg; background-color: $nav-badge-bg;
border-color: $border-color; border-color: $border-color;
} }
.search-input-wrap { .search-input-wrap {
.search-icon {
color: rgba($indigo-200, .8);
}
.clear-icon { .clear-icon {
color: $white-light; color: $white-light;
} }
...@@ -488,6 +431,7 @@ header.navbar-gitlab-new { ...@@ -488,6 +431,7 @@ header.navbar-gitlab-new {
.breadcrumb-item-text { .breadcrumb-item-text {
@include str-truncated(128px); @include str-truncated(128px);
text-decoration: inherit;
} }
.breadcrumbs-list-angle { .breadcrumbs-list-angle {
...@@ -517,8 +461,6 @@ header.navbar-gitlab-new { ...@@ -517,8 +461,6 @@ header.navbar-gitlab-new {
.btn-sign-in { .btn-sign-in {
margin-top: 3px; margin-top: 3px;
background-color: $indigo-100;
color: $indigo-900;
font-weight: $gl-font-weight-bold; font-weight: $gl-font-weight-bold;
&:hover { &:hover {
......
...@@ -3,8 +3,6 @@ ...@@ -3,8 +3,6 @@
@import "bootstrap/variables"; @import "bootstrap/variables";
$active-background: rgba(0, 0, 0, .04); $active-background: rgba(0, 0, 0, .04);
$active-border: $indigo-500;
$active-color: $indigo-700;
$active-hover-background: $active-background; $active-hover-background: $active-background;
$active-hover-color: $gl-text-color; $active-hover-color: $gl-text-color;
$inactive-badge-background: rgba(0, 0, 0, .08); $inactive-badge-background: rgba(0, 0, 0, .08);
...@@ -107,7 +105,8 @@ $new-sidebar-collapsed-width: 50px; ...@@ -107,7 +105,8 @@ $new-sidebar-collapsed-width: 50px;
} }
&.sidebar-icons-only { &.sidebar-icons-only {
width: $new-sidebar-collapsed-width; width: auto;
min-width: $new-sidebar-collapsed-width;
.nav-sidebar-inner-scroll { .nav-sidebar-inner-scroll {
overflow-x: hidden; overflow-x: hidden;
...@@ -126,6 +125,10 @@ $new-sidebar-collapsed-width: 50px; ...@@ -126,6 +125,10 @@ $new-sidebar-collapsed-width: 50px;
.fly-out-top-item { .fly-out-top-item {
display: block; display: block;
} }
.avatar-container {
margin-right: 0;
}
} }
&.nav-sidebar-expanded { &.nav-sidebar-expanded {
...@@ -162,16 +165,9 @@ $new-sidebar-collapsed-width: 50px; ...@@ -162,16 +165,9 @@ $new-sidebar-collapsed-width: 50px;
} }
li.active { li.active {
box-shadow: inset 4px 0 0 $active-border;
> a { > a {
color: $active-color;
font-weight: $gl-font-weight-bold; font-weight: $gl-font-weight-bold;
} }
svg {
fill: $active-color;
}
} }
@media (max-width: $screen-xs-max) { @media (max-width: $screen-xs-max) {
...@@ -196,7 +192,7 @@ $new-sidebar-collapsed-width: 50px; ...@@ -196,7 +192,7 @@ $new-sidebar-collapsed-width: 50px;
.nav-sidebar-inner-scroll { .nav-sidebar-inner-scroll {
height: 100%; height: 100%;
width: 100%; width: 100%;
overflow: auto; overflow: scroll;
} }
.with-performance-bar .nav-sidebar { .with-performance-bar .nav-sidebar {
...@@ -224,7 +220,6 @@ $new-sidebar-collapsed-width: 50px; ...@@ -224,7 +220,6 @@ $new-sidebar-collapsed-width: 50px;
&:hover, &:hover,
&:focus { &:focus {
background: $active-background; background: $active-background;
color: $active-color;
} }
} }
} }
...@@ -258,7 +253,7 @@ $new-sidebar-collapsed-width: 50px; ...@@ -258,7 +253,7 @@ $new-sidebar-collapsed-width: 50px;
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
position: fixed; position: fixed;
top: 0; top: 0;
left: $new-sidebar-width; left: 0;
min-width: 150px; min-width: 150px;
margin-top: -1px; margin-top: -1px;
padding: 4px 1px; padding: 4px 1px;
...@@ -324,7 +319,6 @@ $new-sidebar-collapsed-width: 50px; ...@@ -324,7 +319,6 @@ $new-sidebar-collapsed-width: 50px;
} }
.badge { .badge {
color: $active-color;
font-weight: $gl-font-weight-bold; font-weight: $gl-font-weight-bold;
} }
...@@ -397,10 +391,6 @@ $new-sidebar-collapsed-width: 50px; ...@@ -397,10 +391,6 @@ $new-sidebar-collapsed-width: 50px;
} }
.sidebar-sub-level-items { .sidebar-sub-level-items {
@media (min-width: $screen-sm-min) {
left: $new-sidebar-collapsed-width;
}
&:not(.flyout-list) { &:not(.flyout-list) {
display: none; display: none;
} }
...@@ -501,13 +491,3 @@ $new-sidebar-collapsed-width: 50px; ...@@ -501,13 +491,3 @@ $new-sidebar-collapsed-width: 50px;
.with-performance-bar .boards-list { .with-performance-bar .boards-list {
height: calc(100vh - #{$new-navbar-height} - #{$performance-bar-height}); height: calc(100vh - #{$new-navbar-height} - #{$performance-bar-height});
} }
// Change color of all horizontal tabs to match the new indigo color
.nav-links li.active a {
border-bottom-color: $active-border;
.badge {
font-weight: $gl-font-weight-bold;
}
}
...@@ -64,10 +64,10 @@ ...@@ -64,10 +64,10 @@
color: $gl-text-color; color: $gl-text-color;
position: sticky; position: sticky;
position: -webkit-sticky; position: -webkit-sticky;
top: $header-height; top: $new-navbar-height;
&.affix { &.affix {
top: $header-height; top: $new-navbar-height;
} }
// with sidebar // with sidebar
...@@ -174,10 +174,10 @@ ...@@ -174,10 +174,10 @@
.with-performance-bar .build-page { .with-performance-bar .build-page {
.top-bar { .top-bar {
top: $header-height + $performance-bar-height; top: $new-navbar-height + $performance-bar-height;
&.affix { &.affix {
top: $header-height + $performance-bar-height; top: $new-navbar-height + $performance-bar-height;
} }
} }
} }
......
...@@ -634,8 +634,16 @@ ...@@ -634,8 +634,16 @@
padding-top: 8px; padding-top: 8px;
padding-bottom: 8px; padding-bottom: 8px;
} }
.diff-changed-file {
display: flex;
align-items: center;
}
} }
.diff-file-changes-path { .diff-file-changes-path {
@include str-truncated(78%); flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
} }
...@@ -449,6 +449,12 @@ ...@@ -449,6 +449,12 @@
} }
} }
} }
.milestone-title span {
@include str-truncated(100%);
display: block;
margin: 0 4px;
}
} }
a { a {
......
...@@ -95,6 +95,8 @@ ...@@ -95,6 +95,8 @@
} }
.omniauth-container { .omniauth-container {
font-size: 13px;
p { p {
margin: 0; margin: 0;
} }
......
@mixin application-theme-preview($color-1, $color-2, $color-3, $color-4) {
.one {
background-color: $color-1;
border-top-left-radius: $border-radius-default;
}
.two {
background-color: $color-2;
border-top-right-radius: $border-radius-default;
}
.three {
background-color: $color-3;
border-bottom-left-radius: $border-radius-default;
}
.four {
background-color: $color-4;
border-bottom-right-radius: $border-radius-default;
}
}
.application-theme {
label {
margin-right: 20px;
text-align: center;
}
.preview {
font-size: 0;
margin-bottom: 10px;
&.indigo {
@include application-theme-preview($indigo-900, $indigo-700, $indigo-800, $indigo-500);
}
&.dark {
@include application-theme-preview($theme-gray-900, $theme-gray-700, $theme-gray-800, $theme-gray-600);
}
&.light {
@include application-theme-preview($theme-gray-600, $theme-gray-200, $theme-gray-400, $theme-gray-100);
}
&.blue {
@include application-theme-preview($theme-blue-900, $theme-blue-700, $theme-blue-800, $theme-blue-500);
}
&.green {
@include application-theme-preview($theme-green-900, $theme-green-700, $theme-green-800, $theme-green-500);
}
}
.preview-row {
display: block;
}
.quadrant {
display: inline-block;
height: 50px;
width: 80px;
}
}
.syntax-theme { .syntax-theme {
label { label {
margin-right: 20px; margin-right: 20px;
......
...@@ -752,7 +752,7 @@ a.deploy-project-label { ...@@ -752,7 +752,7 @@ a.deploy-project-label {
} }
li.missing { li.missing {
border: 1px dashed $border-gray-normal; border: 1px dashed $border-gray-normal-dashed;
border-radius: $border-radius-default; border-radius: $border-radius-default;
a { a {
......
...@@ -71,6 +71,11 @@ ...@@ -71,6 +71,11 @@
height: 100%; height: 100%;
.monaco-editor.vs { .monaco-editor.vs {
.current-line {
border: none;
background: $well-light-border;
}
.line-numbers { .line-numbers {
cursor: pointer; cursor: pointer;
...@@ -84,6 +89,13 @@ ...@@ -84,6 +89,13 @@
} }
} }
.blob-no-preview {
.vertical-center {
justify-content: center;
width: 100%;
}
}
&.edit-mode { &.edit-mode {
.blob-viewer-container { .blob-viewer-container {
overflow: hidden; overflow: hidden;
...@@ -103,7 +115,7 @@ ...@@ -103,7 +115,7 @@
overflow: auto; overflow: auto;
> div, > div,
.file-content { .file-content:not(.wiki) {
display: flex; display: flex;
} }
......
...@@ -10,9 +10,8 @@ class Admin::DeployKeysController < Admin::ApplicationController ...@@ -10,9 +10,8 @@ class Admin::DeployKeysController < Admin::ApplicationController
end end
def create def create
@deploy_key = deploy_keys.new(create_params.merge(user: current_user)) @deploy_key = DeployKeys::CreateService.new(current_user, create_params.merge(public: true)).execute
if @deploy_key.persisted?
if @deploy_key.save
redirect_to admin_deploy_keys_path redirect_to admin_deploy_keys_path
else else
render 'new' render 'new'
......
...@@ -211,6 +211,7 @@ class Admin::UsersController < Admin::ApplicationController ...@@ -211,6 +211,7 @@ class Admin::UsersController < Admin::ApplicationController
:provider, :provider,
:remember_me, :remember_me,
:skype, :skype,
:theme_id,
:twitter, :twitter,
:username, :username,
:website_url :website_url
......
...@@ -3,31 +3,10 @@ class AutocompleteController < ApplicationController ...@@ -3,31 +3,10 @@ class AutocompleteController < ApplicationController
skip_before_action :authenticate_user!, only: [:users, :award_emojis] skip_before_action :authenticate_user!, only: [:users, :award_emojis]
before_action :load_project, only: [:users] before_action :load_project, only: [:users]
before_action :find_users, only: [:users] before_action :load_group, only: [:users]
def users def users
@users ||= User.none @users = AutocompleteUsersFinder.new(params: params, current_user: current_user, project: @project, group: @group).execute
@users = @users.active
@users = @users.reorder(:name)
@users = @users.search(params[:search]) if params[:search].present?
@users = @users.where.not(id: params[:skip_users]) if params[:skip_users].present?
@users = @users.page(params[:page]).per(params[:per_page])
if params[:todo_filter].present? && current_user
@users = @users.todo_authors(current_user.id, params[:todo_state_filter])
end
if params[:search].blank?
# Include current user if available to filter by "Me"
if params[:current_user].present? && current_user
@users = [current_user, *@users].uniq
end
if params[:author_id].present? && current_user
author = User.find_by_id(params[:author_id])
@users = [author, *@users].uniq if author
end
end
render json: @users, only: [:name, :username, :id], methods: [:avatar_url] render json: @users, only: [:name, :username, :id], methods: [:avatar_url]
end end
...@@ -60,26 +39,14 @@ class AutocompleteController < ApplicationController ...@@ -60,26 +39,14 @@ class AutocompleteController < ApplicationController
private private
def find_users def load_group
@users = @group ||= begin
if @project if @project.blank? && params[:group_id].present?
user_ids = @project.team.users.pluck(:id)
if params[:author_id].present?
user_ids << params[:author_id]
end
User.where(id: user_ids)
elsif params[:group_id].present?
group = Group.find(params[:group_id]) group = Group.find(params[:group_id])
return render_404 unless can?(current_user, :read_group, group) return render_404 unless can?(current_user, :read_group, group)
group
group.users
elsif current_user
User.all
else
User.none
end end
end
end end
def load_project def load_project
......
...@@ -11,9 +11,15 @@ module Boards ...@@ -11,9 +11,15 @@ module Boards
issues = Boards::Issues::ListService.new(board_parent, current_user, filter_params).execute issues = Boards::Issues::ListService.new(board_parent, current_user, filter_params).execute
issues = issues.page(params[:page]).per(params[:per] || 20) issues = issues.page(params[:page]).per(params[:per] || 20)
make_sure_position_is_set(issues) make_sure_position_is_set(issues)
issues = issues.preload(:project,
:milestone,
:assignees,
labels: [:priorities],
notes: [:award_emoji, :author]
)
render json: { render json: {
issues: serialize_as_json(issues.preload(:project)), issues: serialize_as_json(issues),
size: issues.total_count size: issues.total_count
} }
end end
...@@ -76,14 +82,13 @@ module Boards ...@@ -76,14 +82,13 @@ module Boards
def serialize_as_json(resource) def serialize_as_json(resource)
resource.as_json( resource.as_json(
labels: true,
only: [:id, :iid, :project_id, :title, :confidential, :due_date, :relative_position], only: [:id, :iid, :project_id, :title, :confidential, :due_date, :relative_position],
labels: true,
include: { include: {
project: { only: [:id, :path] }, project: { only: [:id, :path] },
assignees: { only: [:id, :name, :username], methods: [:avatar_url] }, assignees: { only: [:id, :name, :username], methods: [:avatar_url] },
milestone: { only: [:id, :title] } milestone: { only: [:id, :title] }
}, }
user: current_user
) )
end end
end end
......
...@@ -7,11 +7,11 @@ module Ci ...@@ -7,11 +7,11 @@ module Ci
def create def create
@content = params[:content] @content = params[:content]
@error = Ci::GitlabCiYamlProcessor.validation_message(@content) @error = Gitlab::Ci::YamlProcessor.validation_message(@content)
@status = @error.blank? @status = @error.blank?
if @error.blank? if @error.blank?
@config_processor = Ci::GitlabCiYamlProcessor.new(@content) @config_processor = Gitlab::Ci::YamlProcessor.new(@content)
@stages = @config_processor.stages @stages = @config_processor.stages
@builds = @config_processor.builds @builds = @config_processor.builds
@jobs = @config_processor.jobs @jobs = @config_processor.jobs
......
...@@ -48,7 +48,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController ...@@ -48,7 +48,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
ProjectsFinder ProjectsFinder
.new(params: finder_params, current_user: current_user) .new(params: finder_params, current_user: current_user)
.execute .execute
.includes(:route, :creator, namespace: :route) .includes(:route, :creator, namespace: [:route, :owner])
end end
def load_events def load_events
......
...@@ -7,9 +7,9 @@ class Profiles::GpgKeysController < Profiles::ApplicationController ...@@ -7,9 +7,9 @@ class Profiles::GpgKeysController < Profiles::ApplicationController
end end
def create def create
@gpg_key = current_user.gpg_keys.new(gpg_key_params) @gpg_key = GpgKeys::CreateService.new(current_user, gpg_key_params).execute
if @gpg_key.save if @gpg_key.persisted?
redirect_to profile_gpg_keys_path redirect_to profile_gpg_keys_path
else else
@gpg_keys = current_user.gpg_keys.select(&:persisted?) @gpg_keys = current_user.gpg_keys.select(&:persisted?)
......
...@@ -11,9 +11,9 @@ class Profiles::KeysController < Profiles::ApplicationController ...@@ -11,9 +11,9 @@ class Profiles::KeysController < Profiles::ApplicationController
end end
def create def create
@key = current_user.keys.new(key_params) @key = Keys::CreateService.new(current_user, key_params).execute
if @key.save if @key.persisted?
redirect_to profile_key_path(@key) redirect_to profile_key_path(@key)
else else
@keys = current_user.keys.select(&:persisted?) @keys = current_user.keys.select(&:persisted?)
......
...@@ -35,7 +35,8 @@ class Profiles::PreferencesController < Profiles::ApplicationController ...@@ -35,7 +35,8 @@ class Profiles::PreferencesController < Profiles::ApplicationController
:color_scheme_id, :color_scheme_id,
:layout, :layout,
:dashboard, :dashboard,
:project_view :project_view,
:theme_id
) )
end end
end end
...@@ -27,7 +27,7 @@ class Projects::CompareController < Projects::ApplicationController ...@@ -27,7 +27,7 @@ class Projects::CompareController < Projects::ApplicationController
def create def create
if params[:from].blank? || params[:to].blank? if params[:from].blank? || params[:to].blank?
flash[:alert] = "You must select from and to branches" flash[:alert] = "You must select a Source and a Target revision"
from_to_vars = { from_to_vars = {
from: params[:from].presence, from: params[:from].presence,
to: params[:to].presence to: params[:to].presence
......
...@@ -22,7 +22,7 @@ class Projects::DeployKeysController < Projects::ApplicationController ...@@ -22,7 +22,7 @@ class Projects::DeployKeysController < Projects::ApplicationController
end end
def create def create
@key = DeployKey.new(create_params.merge(user: current_user)) @key = DeployKeys::CreateService.new(current_user, create_params).execute
unless @key.valid? && @project.deploy_keys << @key unless @key.valid? && @project.deploy_keys << @key
flash[:alert] = @key.errors.full_messages.join(', ').html_safe flash[:alert] = @key.errors.full_messages.join(', ').html_safe
......
...@@ -132,10 +132,10 @@ class Projects::PipelinesController < Projects::ApplicationController ...@@ -132,10 +132,10 @@ class Projects::PipelinesController < Projects::ApplicationController
def charts def charts
@charts = {} @charts = {}
@charts[:week] = Ci::Charts::WeekChart.new(project) @charts[:week] = Gitlab::Ci::Charts::WeekChart.new(project)
@charts[:month] = Ci::Charts::MonthChart.new(project) @charts[:month] = Gitlab::Ci::Charts::MonthChart.new(project)
@charts[:year] = Ci::Charts::YearChart.new(project) @charts[:year] = Gitlab::Ci::Charts::YearChart.new(project)
@charts[:pipeline_times] = Ci::Charts::PipelineTime.new(project) @charts[:pipeline_times] = Gitlab::Ci::Charts::PipelineTime.new(project)
@counts = {} @counts = {}
@counts[:total] = @project.pipelines.count(:all) @counts[:total] = @project.pipelines.count(:all)
......
class AutocompleteUsersFinder
attr_reader :current_user, :project, :group, :search, :skip_users,
:page, :per_page, :author_id, :params
def initialize(params:, current_user:, project:, group:)
@current_user = current_user
@project = project
@group = group
@search = params[:search]
@skip_users = params[:skip_users]
@page = params[:page]
@per_page = params[:per_page]
@author_id = params[:author_id]
@params = params
end
def execute
items = find_users
items = items.active
items = items.reorder(:name)
items = items.search(search) if search.present?
items = items.where.not(id: skip_users) if skip_users.present?
items = items.page(page).per(per_page)
if params[:todo_filter].present? && current_user
items = items.todo_authors(current_user.id, params[:todo_state_filter])
end
if search.blank?
# Include current user if available to filter by "Me"
if params[:current_user].present? && current_user
items = [current_user, *items].uniq
end
if author_id.present? && current_user
author = User.find_by_id(author_id)
items = [author, *items].uniq if author
end
end
items
end
private
def find_users
return users_from_project if project
return group.users if group
return User.all if current_user
User.none
end
def users_from_project
user_ids = project.team.users.pluck(:id)
user_ids << author_id if author_id.present?
User.where(id: user_ids)
end
end
module AutoDevopsHelper module AutoDevopsHelper
def show_auto_devops_callout?(project) def show_auto_devops_callout?(project)
show_callout?('auto_devops_settings_dismissed') && Feature.get(:auto_devops_banner_disabled).off? &&
show_callout?('auto_devops_settings_dismissed') &&
can?(current_user, :admin_pipeline, project) && can?(current_user, :admin_pipeline, project) &&
project.has_auto_devops_implicitly_disabled? project.has_auto_devops_implicitly_disabled? &&
!project.repository.gitlab_ci_yml &&
project.ci_services.active.none?
end end
end end
...@@ -77,4 +77,8 @@ module BoardsHelper ...@@ -77,4 +77,8 @@ module BoardsHelper
'max-select': dropdown_options[:data][:'max-select'] 'max-select': dropdown_options[:data][:'max-select']
} }
end end
def boards_link_text
_("Board")
end
end end
...@@ -30,7 +30,7 @@ module BuildsHelper ...@@ -30,7 +30,7 @@ module BuildsHelper
def build_failed_issue_options def build_failed_issue_options
{ {
title: "Build Failed ##{@build.id}", title: "Job Failed ##{@build.id}",
description: project_job_url(@project, @build) description: project_job_url(@project, @build)
} }
end end
......
...@@ -21,7 +21,7 @@ module GroupsHelper ...@@ -21,7 +21,7 @@ module GroupsHelper
group.ancestors.reverse.each_with_index do |parent, index| group.ancestors.reverse.each_with_index do |parent, index|
if index > 0 if index > 0
add_to_breadcrumb_dropdown(group_title_link(parent, hidable: false, show_avatar: true), location: :before) add_to_breadcrumb_dropdown(group_title_link(parent, hidable: false, show_avatar: true, for_dropdown: true), location: :before)
else else
full_title += breadcrumb_list_item group_title_link(parent, hidable: false) full_title += breadcrumb_list_item group_title_link(parent, hidable: false)
end end
...@@ -85,8 +85,8 @@ module GroupsHelper ...@@ -85,8 +85,8 @@ module GroupsHelper
private private
def group_title_link(group, hidable: false, show_avatar: false) def group_title_link(group, hidable: false, show_avatar: false, for_dropdown: false)
link_to(group_path(group), class: "group-path breadcrumb-item-text js-breadcrumb-item-text #{'hidable' if hidable}") do link_to(group_path(group), class: "group-path #{'breadcrumb-item-text' unless for_dropdown} js-breadcrumb-item-text #{'hidable' if hidable}") do
output = output =
if (group.try(:avatar_url) || show_avatar) && !Rails.env.test? if (group.try(:avatar_url) || show_avatar) && !Rails.env.test?
image_tag(group_icon(group), class: "avatar-tile", width: 15, height: 15) image_tag(group_icon(group), class: "avatar-tile", width: 15, height: 15)
......
...@@ -40,6 +40,10 @@ module PreferencesHelper ...@@ -40,6 +40,10 @@ module PreferencesHelper
] ]
end end
def user_application_theme
@user_application_theme ||= Gitlab::Themes.for_user(current_user).css_class
end
def user_color_scheme def user_color_scheme
Gitlab::ColorSchemes.for_user(current_user).css_class Gitlab::ColorSchemes.for_user(current_user).css_class
end end
......
...@@ -320,7 +320,7 @@ module ProjectsHelper ...@@ -320,7 +320,7 @@ module ProjectsHelper
def git_user_name def git_user_name
if current_user if current_user
current_user.name current_user.name.gsub('"', '\"')
else else
_("Your name") _("Your name")
end end
......
...@@ -119,8 +119,4 @@ module TabHelper ...@@ -119,8 +119,4 @@ module TabHelper
'active' if current_controller?('oauth/applications') 'active' if current_controller?('oauth/applications')
end end
def sidebar_link(href, title: nil, css: nil, &block)
link_to capture(&block), href, title: (title if collapsed_sidebar?), class: css, aria: { label: title }
end
end end
...@@ -137,11 +137,11 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -137,11 +137,11 @@ class ApplicationSetting < ActiveRecord::Base
validates :housekeeping_full_repack_period, validates :housekeeping_full_repack_period,
presence: true, presence: true,
numericality: { only_integer: true, greater_than: :housekeeping_incremental_repack_period } numericality: { only_integer: true, greater_than_or_equal_to: :housekeeping_incremental_repack_period }
validates :housekeeping_gc_period, validates :housekeeping_gc_period,
presence: true, presence: true,
numericality: { only_integer: true, greater_than: :housekeeping_full_repack_period } numericality: { only_integer: true, greater_than_or_equal_to: :housekeeping_full_repack_period }
validates :terminal_max_session_time, validates :terminal_max_session_time,
presence: true, presence: true,
......
...@@ -13,7 +13,7 @@ module BlobViewer ...@@ -13,7 +13,7 @@ module BlobViewer
prepare! prepare!
@validation_message = Ci::GitlabCiYamlProcessor.validation_message(blob.data) @validation_message = Gitlab::Ci::YamlProcessor.validation_message(blob.data)
end end
def valid? def valid?
......
...@@ -446,8 +446,8 @@ module Ci ...@@ -446,8 +446,8 @@ module Ci
return unless trace return unless trace
trace = trace.dup trace = trace.dup
Ci::MaskSecret.mask!(trace, project.runners_token) if project Gitlab::Ci::MaskSecret.mask!(trace, project.runners_token) if project
Ci::MaskSecret.mask!(trace, token) Gitlab::Ci::MaskSecret.mask!(trace, token)
trace trace
end end
......
module Ci module Ci
class GroupVariable < ActiveRecord::Base class GroupVariable < ActiveRecord::Base
extend Ci::Model extend Gitlab::Ci::Model
include HasVariable include HasVariable
include Presentable include Presentable
......
module Ci module Ci
class Pipeline < ActiveRecord::Base class Pipeline < ActiveRecord::Base
extend Ci::Model extend Gitlab::Ci::Model
include HasStatus include HasStatus
include Importable include Importable
include AfterCommitQueue include AfterCommitQueue
...@@ -336,8 +336,8 @@ module Ci ...@@ -336,8 +336,8 @@ module Ci
return @config_processor if defined?(@config_processor) return @config_processor if defined?(@config_processor)
@config_processor ||= begin @config_processor ||= begin
Ci::GitlabCiYamlProcessor.new(ci_yaml_file, project.full_path) Gitlab::Ci::YamlProcessor.new(ci_yaml_file, project.full_path)
rescue Ci::GitlabCiYamlProcessor::ValidationError, Psych::SyntaxError => e rescue Gitlab::Ci::YamlProcessor::ValidationError, Psych::SyntaxError => e
self.yaml_errors = e.message self.yaml_errors = e.message
nil nil
rescue rescue
...@@ -453,6 +453,10 @@ module Ci ...@@ -453,6 +453,10 @@ module Ci
.fabricate! .fabricate!
end end
def latest_builds_with_artifacts
@latest_builds_with_artifacts ||= builds.latest.with_artifacts
end
private private
def ci_yaml_from_repo def ci_yaml_from_repo
......
module Ci module Ci
class PipelineSchedule < ActiveRecord::Base class PipelineSchedule < ActiveRecord::Base
extend Ci::Model extend Gitlab::Ci::Model
include Importable include Importable
acts_as_paranoid acts_as_paranoid
......
module Ci module Ci
class PipelineScheduleVariable < ActiveRecord::Base class PipelineScheduleVariable < ActiveRecord::Base
extend Ci::Model extend Gitlab::Ci::Model
include HasVariable include HasVariable
belongs_to :pipeline_schedule belongs_to :pipeline_schedule
......
module Ci module Ci
class PipelineVariable < ActiveRecord::Base class PipelineVariable < ActiveRecord::Base
extend Ci::Model extend Gitlab::Ci::Model
include HasVariable include HasVariable
belongs_to :pipeline belongs_to :pipeline
......
module Ci module Ci
class Runner < ActiveRecord::Base class Runner < ActiveRecord::Base
extend Ci::Model extend Gitlab::Ci::Model
RUNNER_QUEUE_EXPIRY_TIME = 60.minutes RUNNER_QUEUE_EXPIRY_TIME = 60.minutes
ONLINE_CONTACT_TIMEOUT = 1.hour ONLINE_CONTACT_TIMEOUT = 1.hour
......
module Ci module Ci
class RunnerProject < ActiveRecord::Base class RunnerProject < ActiveRecord::Base
extend Ci::Model extend Gitlab::Ci::Model
belongs_to :runner belongs_to :runner
belongs_to :project belongs_to :project
......
module Ci module Ci
class Stage < ActiveRecord::Base class Stage < ActiveRecord::Base
extend Ci::Model extend Gitlab::Ci::Model
include Importable include Importable
include HasStatus include HasStatus
include Gitlab::OptimisticLocking include Gitlab::OptimisticLocking
......
module Ci module Ci
class Trigger < ActiveRecord::Base class Trigger < ActiveRecord::Base
extend Ci::Model extend Gitlab::Ci::Model
acts_as_paranoid acts_as_paranoid
......
module Ci module Ci
class TriggerRequest < ActiveRecord::Base class TriggerRequest < ActiveRecord::Base
extend Ci::Model extend Gitlab::Ci::Model
belongs_to :trigger belongs_to :trigger
belongs_to :pipeline, foreign_key: :commit_id belongs_to :pipeline, foreign_key: :commit_id
......
module Ci module Ci
class Variable < ActiveRecord::Base class Variable < ActiveRecord::Base
extend Ci::Model extend Gitlab::Ci::Model
include HasVariable include HasVariable
include Presentable include Presentable
......
...@@ -28,10 +28,4 @@ class DeployKey < Key ...@@ -28,10 +28,4 @@ class DeployKey < Key
def can_push_to?(project) def can_push_to?(project)
can_push? && has_access_to?(project) can_push? && has_access_to?(project)
end end
private
# we don't want to notify the user for deploy keys
def notify_user
end
end end
...@@ -6,7 +6,10 @@ class Environment < ActiveRecord::Base ...@@ -6,7 +6,10 @@ class Environment < ActiveRecord::Base
belongs_to :project, required: true, validate: true belongs_to :project, required: true, validate: true
has_many :deployments, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :deployments,
-> (env) { where(project_id: env.project_id) },
dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_one :last_deployment, -> { order('deployments.id DESC') }, class_name: 'Deployment' has_one :last_deployment, -> { order('deployments.id DESC') }, class_name: 'Deployment'
before_validation :nullify_external_url before_validation :nullify_external_url
......
...@@ -241,13 +241,7 @@ class Event < ActiveRecord::Base ...@@ -241,13 +241,7 @@ class Event < ActiveRecord::Base
def action_name def action_name
if push? if push?
if new_ref? push_action_name
"pushed new"
elsif rm_ref?
"deleted"
else
"pushed to"
end
elsif closed? elsif closed?
"closed" "closed"
elsif merged? elsif merged?
...@@ -263,11 +257,7 @@ class Event < ActiveRecord::Base ...@@ -263,11 +257,7 @@ class Event < ActiveRecord::Base
elsif commented? elsif commented?
"commented on" "commented on"
elsif created_project? elsif created_project?
if project.external_import? created_project_action_name
"imported"
else
"created"
end
else else
"opened" "opened"
end end
...@@ -360,6 +350,24 @@ class Event < ActiveRecord::Base ...@@ -360,6 +350,24 @@ class Event < ActiveRecord::Base
private private
def push_action_name
if new_ref?
"pushed new"
elsif rm_ref?
"deleted"
else
"pushed to"
end
end
def created_project_action_name
if project.external_import?
"imported"
else
"created"
end
end
def recent_update? def recent_update?
project.last_activity_at > RESET_PROJECT_ACTIVITY_INTERVAL.ago project.last_activity_at > RESET_PROJECT_ACTIVITY_INTERVAL.ago
end end
......
...@@ -36,7 +36,6 @@ class GpgKey < ActiveRecord::Base ...@@ -36,7 +36,6 @@ class GpgKey < ActiveRecord::Base
before_validation :extract_fingerprint, :extract_primary_keyid before_validation :extract_fingerprint, :extract_primary_keyid
after_commit :update_invalid_gpg_signatures, on: :create after_commit :update_invalid_gpg_signatures, on: :create
after_commit :notify_user, on: :create
def primary_keyid def primary_keyid
super&.upcase super&.upcase
...@@ -107,8 +106,4 @@ class GpgKey < ActiveRecord::Base ...@@ -107,8 +106,4 @@ class GpgKey < ActiveRecord::Base
# only allows one key # only allows one key
self.primary_keyid = Gitlab::Gpg.primary_keyids_from_key(key).first self.primary_keyid = Gitlab::Gpg.primary_keyids_from_key(key).first
end end
def notify_user
NotificationService.new.new_gpg_key(self)
end
end end
...@@ -30,9 +30,6 @@ class Issue < ActiveRecord::Base ...@@ -30,9 +30,6 @@ class Issue < ActiveRecord::Base
has_many :issue_assignees has_many :issue_assignees
has_many :assignees, class_name: "User", through: :issue_assignees has_many :assignees, class_name: "User", through: :issue_assignees
has_many :issue_assignees
has_many :assignees, class_name: "User", through: :issue_assignees
validates :project, presence: true validates :project, presence: true
scope :in_projects, ->(project_ids) { where(project_id: project_ids) } scope :in_projects, ->(project_ids) { where(project_id: project_ids) }
......
...@@ -28,7 +28,6 @@ class Key < ActiveRecord::Base ...@@ -28,7 +28,6 @@ class Key < ActiveRecord::Base
delegate :name, :email, to: :user, prefix: true delegate :name, :email, to: :user, prefix: true
after_commit :add_to_shell, on: :create after_commit :add_to_shell, on: :create
after_commit :notify_user, on: :create
after_create :post_create_hook after_create :post_create_hook
after_commit :remove_from_shell, on: :destroy after_commit :remove_from_shell, on: :destroy
after_destroy :post_destroy_hook after_destroy :post_destroy_hook
...@@ -118,8 +117,4 @@ class Key < ActiveRecord::Base ...@@ -118,8 +117,4 @@ class Key < ActiveRecord::Base
"type is forbidden. Must be #{allowed_types}" "type is forbidden. Must be #{allowed_types}"
end end
def notify_user
NotificationService.new.new_key(self)
end
end end
...@@ -127,7 +127,12 @@ class Label < ActiveRecord::Base ...@@ -127,7 +127,12 @@ class Label < ActiveRecord::Base
end end
def priority(project) def priority(project)
priorities.find_by(project: project).try(:priority) priority = if priorities.loaded?
priorities.first { |p| p.project == project }
else
priorities.find_by(project: project)
end
priority.try(:priority)
end end
def template? def template?
......
...@@ -231,6 +231,13 @@ class Namespace < ActiveRecord::Base ...@@ -231,6 +231,13 @@ class Namespace < ActiveRecord::Base
end end
def force_share_with_group_lock_on_descendants def force_share_with_group_lock_on_descendants
descendants.update_all(share_with_group_lock: true) return unless Group.supports_nested_groups?
# We can't use `descendants.update_all` since Rails will throw away the WITH
# RECURSIVE statement. We also can't use WHERE EXISTS since we can't use
# different table aliases, hence we're just using WHERE IN. Since we have a
# maximum of 20 nested groups this should be fine.
Namespace.where(id: descendants.select(:id))
.update_all(share_with_group_lock: true)
end end
end end
...@@ -28,7 +28,7 @@ class PersonalAccessToken < ActiveRecord::Base ...@@ -28,7 +28,7 @@ class PersonalAccessToken < ActiveRecord::Base
protected protected
def validate_scopes def validate_scopes
unless scopes.all? { |scope| Gitlab::Auth::AVAILABLE_SCOPES.include?(scope.to_sym) } unless revoked || scopes.all? { |scope| Gitlab::Auth::AVAILABLE_SCOPES.include?(scope.to_sym) }
errors.add :scopes, "can only contain available scopes" errors.add :scopes, "can only contain available scopes"
end end
end end
......
...@@ -161,7 +161,7 @@ class Project < ActiveRecord::Base ...@@ -161,7 +161,7 @@ class Project < ActiveRecord::Base
has_many :notification_settings, as: :source, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent has_many :notification_settings, as: :source, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
has_one :import_data, class_name: 'ProjectImportData', inverse_of: :project, autosave: true has_one :import_data, class_name: 'ProjectImportData', inverse_of: :project, autosave: true
has_one :project_feature has_one :project_feature, inverse_of: :project
has_one :statistics, class_name: 'ProjectStatistics' has_one :statistics, class_name: 'ProjectStatistics'
# Container repositories need to remove data from the container registry, # Container repositories need to remove data from the container registry,
...@@ -190,7 +190,7 @@ class Project < ActiveRecord::Base ...@@ -190,7 +190,7 @@ class Project < ActiveRecord::Base
has_one :auto_devops, class_name: 'ProjectAutoDevops' has_one :auto_devops, class_name: 'ProjectAutoDevops'
accepts_nested_attributes_for :variables, allow_destroy: true accepts_nested_attributes_for :variables, allow_destroy: true
accepts_nested_attributes_for :project_feature accepts_nested_attributes_for :project_feature, update_only: true
accepts_nested_attributes_for :import_data accepts_nested_attributes_for :import_data
accepts_nested_attributes_for :auto_devops accepts_nested_attributes_for :auto_devops
...@@ -1163,6 +1163,23 @@ class Project < ActiveRecord::Base ...@@ -1163,6 +1163,23 @@ class Project < ActiveRecord::Base
pipelines.order(id: :desc).find_by(sha: sha, ref: ref) pipelines.order(id: :desc).find_by(sha: sha, ref: ref)
end end
def latest_successful_pipeline_for_default_branch
if defined?(@latest_successful_pipeline_for_default_branch)
return @latest_successful_pipeline_for_default_branch
end
@latest_successful_pipeline_for_default_branch =
pipelines.latest_successful_for(default_branch)
end
def latest_successful_pipeline_for(ref = nil)
if ref && ref != default_branch
pipelines.latest_successful_for(ref)
else
latest_successful_pipeline_for_default_branch
end
end
def enable_ci def enable_ci
project_feature.update_attribute(:builds_access_level, ProjectFeature::ENABLED) project_feature.update_attribute(:builds_access_level, ProjectFeature::ENABLED)
end end
......
class ProjectAutoDevops < ActiveRecord::Base class ProjectAutoDevops < ActiveRecord::Base
belongs_to :project belongs_to :project
scope :enabled, -> { where(enabled: true) }
scope :disabled, -> { where(enabled: false) }
validates :domain, allow_blank: true, hostname: { allow_numeric_hostname: true } validates :domain, allow_blank: true, hostname: { allow_numeric_hostname: true }
def variables def variables
......
...@@ -41,6 +41,8 @@ class ProjectFeature < ActiveRecord::Base ...@@ -41,6 +41,8 @@ class ProjectFeature < ActiveRecord::Base
# http://stackoverflow.com/questions/1540645/how-to-disable-default-scope-for-a-belongs-to # http://stackoverflow.com/questions/1540645/how-to-disable-default-scope-for-a-belongs-to
belongs_to :project, -> { unscope(where: :pending_delete) } belongs_to :project, -> { unscope(where: :pending_delete) }
validates :project, presence: true
validate :repository_children_level validate :repository_children_level
default_value_for :builds_access_level, value: ENABLED, allows_nil: false default_value_for :builds_access_level, value: ENABLED, allows_nil: false
......
...@@ -80,6 +80,6 @@ class PipelinesEmailService < Service ...@@ -80,6 +80,6 @@ class PipelinesEmailService < Service
end end
def retrieve_recipients(data) def retrieve_recipients(data)
recipients.to_s.split(',').reject(&:blank?) recipients.to_s.split(/[,(?:\r?\n) ]+/).reject(&:empty?)
end end
end end
...@@ -90,6 +90,12 @@ class Repository ...@@ -90,6 +90,12 @@ class Repository
) )
end end
# we need to have this method here because it is not cached in ::Git and
# the method is called multiple times for every request
def has_visible_content?
branch_count > 0
end
def inspect def inspect
"#<#{self.class.name}:#{@disk_path}>" "#<#{self.class.name}:#{@disk_path}>"
end end
...@@ -166,7 +172,7 @@ class Repository ...@@ -166,7 +172,7 @@ class Repository
end end
def add_branch(user, branch_name, ref) def add_branch(user, branch_name, ref)
branch = raw_repository.add_branch(branch_name, committer: user, target: ref) branch = raw_repository.add_branch(branch_name, user: user, target: ref)
after_create_branch after_create_branch
...@@ -176,7 +182,7 @@ class Repository ...@@ -176,7 +182,7 @@ class Repository
end end
def add_tag(user, tag_name, target, message = nil) def add_tag(user, tag_name, target, message = nil)
raw_repository.add_tag(tag_name, committer: user, target: target, message: message) raw_repository.add_tag(tag_name, user: user, target: target, message: message)
rescue Gitlab::Git::Repository::InvalidRef rescue Gitlab::Git::Repository::InvalidRef
false false
end end
...@@ -184,7 +190,7 @@ class Repository ...@@ -184,7 +190,7 @@ class Repository
def rm_branch(user, branch_name) def rm_branch(user, branch_name)
before_remove_branch before_remove_branch
raw_repository.rm_branch(branch_name, committer: user) raw_repository.rm_branch(branch_name, user: user)
after_remove_branch after_remove_branch
true true
...@@ -193,7 +199,7 @@ class Repository ...@@ -193,7 +199,7 @@ class Repository
def rm_tag(user, tag_name) def rm_tag(user, tag_name)
before_remove_tag before_remove_tag
raw_repository.rm_tag(tag_name, committer: user) raw_repository.rm_tag(tag_name, user: user)
after_remove_tag after_remove_tag
true true
...@@ -762,17 +768,23 @@ class Repository ...@@ -762,17 +768,23 @@ class Repository
multi_action(**options) multi_action(**options)
end end
def with_branch(user, *args) def with_cache_hooks
result = Gitlab::Git::OperationService.new(user, raw_repository).with_branch(*args) do |start_commit| result = yield
yield start_commit
end return unless result
newrev, should_run_after_create, should_run_after_create_branch = result after_create if result.repo_created?
after_create_branch if result.branch_created?
after_create if should_run_after_create result.newrev
after_create_branch if should_run_after_create_branch end
newrev def with_branch(user, *args)
with_cache_hooks do
Gitlab::Git::OperationService.new(user, raw_repository).with_branch(*args) do |start_commit|
yield start_commit
end
end
end end
# rubocop:disable Metrics/ParameterLists # rubocop:disable Metrics/ParameterLists
...@@ -837,30 +849,13 @@ class Repository ...@@ -837,30 +849,13 @@ class Repository
end end
end end
def merge(user, source, merge_request, options = {}) def merge(user, source_sha, merge_request, message)
with_branch( with_cache_hooks do
user, raw_repository.merge(user, source_sha, merge_request.target_branch, message) do |commit_id|
merge_request.target_branch) do |start_commit| merge_request.update(in_progress_merge_commit_sha: commit_id)
our_commit = start_commit.sha nil # Return value does not matter.
their_commit = source end
raise 'Invalid merge target' unless our_commit
raise 'Invalid merge source' unless their_commit
merge_index = rugged.merge_commits(our_commit, their_commit)
break if merge_index.conflicts?
actual_options = options.merge(
parents: [our_commit, their_commit],
tree: merge_index.write_tree(rugged)
)
commit_id = create_commit(actual_options)
merge_request.update(in_progress_merge_commit_sha: commit_id)
commit_id
end end
rescue Gitlab::Git::CommitError # when merge_index.conflicts?
false
end end
def revert( def revert(
...@@ -1151,12 +1146,6 @@ class Repository ...@@ -1151,12 +1146,6 @@ class Repository
Gitlab::Metrics.add_event(event, { path: full_path }.merge(tags)) Gitlab::Metrics.add_event(event, { path: full_path }.merge(tags))
end end
def create_commit(params = {})
params[:message].delete!("\r")
Rugged::Commit.create(rugged, params)
end
def last_commit_for_path_by_gitaly(sha, path) def last_commit_for_path_by_gitaly(sha, path)
c = raw_repository.gitaly_commit_client.last_commit_for_path(sha, path) c = raw_repository.gitaly_commit_client.last_commit_for_path(sha, path)
commit(c) commit(c)
......
...@@ -35,6 +35,7 @@ class User < ActiveRecord::Base ...@@ -35,6 +35,7 @@ class User < ActiveRecord::Base
default_value_for :project_view, :files default_value_for :project_view, :files
default_value_for :notified_of_own_activity, false default_value_for :notified_of_own_activity, false
default_value_for :preferred_language, I18n.default_locale default_value_for :preferred_language, I18n.default_locale
default_value_for :theme_id, gitlab_config.default_theme
attr_encrypted :otp_secret, attr_encrypted :otp_secret,
key: Gitlab::Application.secrets.otp_key_base, key: Gitlab::Application.secrets.otp_key_base,
......
...@@ -32,8 +32,8 @@ class BuildDetailsEntity < JobEntity ...@@ -32,8 +32,8 @@ class BuildDetailsEntity < JobEntity
private private
def build_failed_issue_options def build_failed_issue_options
{ title: "Build Failed ##{build.id}", { title: "Job Failed ##{build.id}",
description: project_job_path(project, build) } description: "Job [##{build.id}](#{project_job_path(project, build)}) failed for #{build.sha}:\n" }
end end
def current_user def current_user
......
...@@ -14,7 +14,7 @@ module Ci ...@@ -14,7 +14,7 @@ module Ci
pipeline = Ci::CreatePipelineService.new(project, trigger.owner, ref: params[:ref]) pipeline = Ci::CreatePipelineService.new(project, trigger.owner, ref: params[:ref])
.execute(:trigger, ignore_skip_ci: true) do |pipeline| .execute(:trigger, ignore_skip_ci: true) do |pipeline|
trigger.trigger_requests.create!(pipeline: pipeline) pipeline.trigger_requests.create!(trigger: trigger)
create_pipeline_variables!(pipeline) create_pipeline_variables!(pipeline)
end end
......
module DeployKeys
class CreateService < Keys::BaseService
def execute
DeployKey.create(params.merge(user: user))
end
end
end
module GpgKeys
class CreateService < Keys::BaseService
def execute
key = user.gpg_keys.create(params)
notification_service.new_gpg_key(key) if key.persisted?
key
end
end
end
module Keys
class BaseService
attr_accessor :user, :params
def initialize(user, params)
@user, @params = user, params
end
def notification_service
NotificationService.new
end
end
end
module Keys
class CreateService < ::Keys::BaseService
def execute
key = user.keys.create(params)
notification_service.new_key(key) if key.persisted?
key
end
end
end
...@@ -38,15 +38,9 @@ module MergeRequests ...@@ -38,15 +38,9 @@ module MergeRequests
private private
def commit def commit
committer = repository.user_to_committer(current_user) message = params[:commit_message] || merge_request.merge_commit_message
options = { commit_id = repository.merge(current_user, source, merge_request, message)
message: params[:commit_message] || merge_request.merge_commit_message,
author: committer,
committer: committer
}
commit_id = repository.merge(current_user, source, merge_request, options)
raise MergeError, 'Conflicts detected during merge' unless commit_id raise MergeError, 'Conflicts detected during merge' unless commit_id
......
...@@ -24,7 +24,10 @@ module Projects ...@@ -24,7 +24,10 @@ module Projects
success success
else else
error('Project could not be updated!') model_errors = project.errors.full_messages.to_sentence
error_message = model_errors.presence || 'Project could not be updated!'
error(error_message)
end end
end end
......
...@@ -111,6 +111,11 @@ ...@@ -111,6 +111,11 @@
GitLab API GitLab API
%span.pull-right %span.pull-right
= API::API::version = API::API::version
- if Gitlab.config.pages.enabled
%p
GitLab Pages
%span.pull-right
= Gitlab::Pages::VERSION
%p %p
Git Git
%span.pull-right %span.pull-right
......
...@@ -7,6 +7,8 @@ ...@@ -7,6 +7,8 @@
%span.light %span.light
- has_icon = provider_has_icon?(provider) - has_icon = provider_has_icon?(provider)
= link_to provider_image_tag(provider), omniauth_authorize_path(:user, provider), method: :post, class: 'oauth-login' + (has_icon ? ' oauth-image-link' : ' btn'), id: "oauth-login-#{provider}" = link_to provider_image_tag(provider), omniauth_authorize_path(:user, provider), method: :post, class: 'oauth-login' + (has_icon ? ' oauth-image-link' : ' btn'), id: "oauth-login-#{provider}"
%fieldset.prepend-top-10 %fieldset.prepend-top-10.checkbox.remember-me
= check_box_tag :remember_me %label
= label_tag :remember_me, 'Remember me' = check_box_tag :remember_me, nil, false, class: 'remember-me-checkbox'
%span
Remember me
!!! 5 !!! 5
%html{ lang: I18n.locale, class: page_class } %html{ lang: I18n.locale, class: page_class }
= render "layouts/head" = render "layouts/head"
%body{ class: @body_class, data: { page: body_data_page, project: "#{@project.path if @project}", group: "#{@group.path if @group}", find_file: find_file_path } } %body{ class: "#{user_application_theme} #{@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 "layouts/init_auto_complete" if @gfm_form
= render 'peek/bar' = render 'peek/bar'
= render "layouts/header/default" = render "layouts/header/default"
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
.sidebar-context-title Admin Area .sidebar-context-title Admin Area
%ul.sidebar-top-level-items %ul.sidebar-top-level-items
= nav_link(controller: %w(dashboard admin projects users groups jobs runners cohorts conversational_development_index), html_options: {class: 'home'}) do = nav_link(controller: %w(dashboard admin projects users groups jobs runners cohorts conversational_development_index), html_options: {class: 'home'}) do
= sidebar_link admin_root_path, title: _('Overview'), css: 'shortcuts-tree' do = link_to admin_root_path, class: 'shortcuts-tree' do
.nav-icon-container .nav-icon-container
= custom_icon('overview') = custom_icon('overview')
%span.nav-item-name %span.nav-item-name
...@@ -53,7 +53,7 @@ ...@@ -53,7 +53,7 @@
ConvDev Index ConvDev Index
= nav_link(controller: %w(system_info background_jobs logs health_check requests_profiles)) do = nav_link(controller: %w(system_info background_jobs logs health_check requests_profiles)) do
= sidebar_link admin_system_info_path, title: _('Monitoring') do = link_to admin_system_info_path do
.nav-icon-container .nav-icon-container
= custom_icon('monitoring') = custom_icon('monitoring')
%span.nav-item-name %span.nav-item-name
...@@ -87,7 +87,7 @@ ...@@ -87,7 +87,7 @@
Requests Profiles Requests Profiles
= nav_link(controller: :broadcast_messages) do = nav_link(controller: :broadcast_messages) do
= sidebar_link admin_broadcast_messages_path, title: _('Messages') do = link_to admin_broadcast_messages_path do
.nav-icon-container .nav-icon-container
= custom_icon('messages') = custom_icon('messages')
%span.nav-item-name %span.nav-item-name
...@@ -99,7 +99,7 @@ ...@@ -99,7 +99,7 @@
#{ _('Messages') } #{ _('Messages') }
= nav_link(controller: [:hooks, :hook_logs]) do = nav_link(controller: [:hooks, :hook_logs]) do
= sidebar_link admin_hooks_path, title: _('Hooks') do = link_to admin_hooks_path do
.nav-icon-container .nav-icon-container
= custom_icon('system_hooks') = custom_icon('system_hooks')
%span.nav-item-name %span.nav-item-name
...@@ -111,7 +111,7 @@ ...@@ -111,7 +111,7 @@
#{ _('System Hooks') } #{ _('System Hooks') }
= nav_link(controller: :applications) do = nav_link(controller: :applications) do
= sidebar_link admin_applications_path, title: _('Applications') do = link_to admin_applications_path do
.nav-icon-container .nav-icon-container
= custom_icon('applications') = custom_icon('applications')
%span.nav-item-name %span.nav-item-name
...@@ -123,7 +123,7 @@ ...@@ -123,7 +123,7 @@
#{ _('Applications') } #{ _('Applications') }
= nav_link(controller: :abuse_reports) do = nav_link(controller: :abuse_reports) do
= sidebar_link admin_abuse_reports_path, title: _("Abuse Reports") do = link_to admin_abuse_reports_path do
.nav-icon-container .nav-icon-container
= custom_icon('abuse_reports') = custom_icon('abuse_reports')
%span.nav-item-name %span.nav-item-name
...@@ -138,7 +138,7 @@ ...@@ -138,7 +138,7 @@
- if akismet_enabled? - if akismet_enabled?
= nav_link(controller: :spam_logs) do = nav_link(controller: :spam_logs) do
= sidebar_link admin_spam_logs_path, title: _("Spam Logs") do = link_to admin_spam_logs_path do
.nav-icon-container .nav-icon-container
= custom_icon('spam_logs') = custom_icon('spam_logs')
%span.nav-item-name %span.nav-item-name
...@@ -150,7 +150,7 @@ ...@@ -150,7 +150,7 @@
#{ _('Spam Logs') } #{ _('Spam Logs') }
= nav_link(controller: :deploy_keys) do = nav_link(controller: :deploy_keys) do
= sidebar_link admin_deploy_keys_path, title: _('Deploy Keys') do = link_to admin_deploy_keys_path do
.nav-icon-container .nav-icon-container
= custom_icon('key') = custom_icon('key')
%span.nav-item-name %span.nav-item-name
...@@ -162,7 +162,7 @@ ...@@ -162,7 +162,7 @@
#{ _('Deploy Keys') } #{ _('Deploy Keys') }
= nav_link(controller: :services) do = nav_link(controller: :services) do
= sidebar_link admin_application_settings_services_path, title: _('Service Templates') do = link_to admin_application_settings_services_path do
.nav-icon-container .nav-icon-container
= custom_icon('service_templates') = custom_icon('service_templates')
%span.nav-item-name %span.nav-item-name
...@@ -174,7 +174,7 @@ ...@@ -174,7 +174,7 @@
#{ _('Service Templates') } #{ _('Service Templates') }
= nav_link(controller: :labels) do = nav_link(controller: :labels) do
= sidebar_link admin_labels_path, title: _('Labels') do = link_to admin_labels_path do
.nav-icon-container .nav-icon-container
= custom_icon('labels') = custom_icon('labels')
%span.nav-item-name %span.nav-item-name
...@@ -186,7 +186,7 @@ ...@@ -186,7 +186,7 @@
#{ _('Labels') } #{ _('Labels') }
= nav_link(controller: :appearances) do = nav_link(controller: :appearances) do
= sidebar_link admin_appearances_path, title: _('Appearances') do = link_to admin_appearances_path do
.nav-icon-container .nav-icon-container
= custom_icon('appearance') = custom_icon('appearance')
%span.nav-item-name %span.nav-item-name
...@@ -198,7 +198,7 @@ ...@@ -198,7 +198,7 @@
#{ _('Appearance') } #{ _('Appearance') }
= nav_link(controller: :application_settings) do = nav_link(controller: :application_settings) do
= sidebar_link admin_application_settings_path, title: _('Settings') do = link_to admin_application_settings_path do
.nav-icon-container .nav-icon-container
= custom_icon('settings') = custom_icon('settings')
%span.nav-item-name %span.nav-item-name
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
= @group.name = @group.name
%ul.sidebar-top-level-items %ul.sidebar-top-level-items
= nav_link(path: ['groups#show', 'groups#activity', 'groups#subgroups'], html_options: { class: 'home' }) do = nav_link(path: ['groups#show', 'groups#activity', 'groups#subgroups'], html_options: { class: 'home' }) do
= sidebar_link group_path(@group), title: _('Group overview') do = link_to group_path(@group) do
.nav-icon-container .nav-icon-container
= custom_icon('project') = custom_icon('project')
%span.nav-item-name %span.nav-item-name
...@@ -34,7 +34,7 @@ ...@@ -34,7 +34,7 @@
Activity Activity
= nav_link(path: ['groups#issues', 'labels#index', 'milestones#index']) do = nav_link(path: ['groups#issues', 'labels#index', 'milestones#index']) do
= sidebar_link issues_group_path(@group), title: _('Issues') do = link_to issues_group_path(@group) do
.nav-icon-container .nav-icon-container
= custom_icon('issues') = custom_icon('issues')
%span.nav-item-name %span.nav-item-name
...@@ -64,7 +64,7 @@ ...@@ -64,7 +64,7 @@
Milestones Milestones
= nav_link(path: 'groups#merge_requests') do = nav_link(path: 'groups#merge_requests') do
= sidebar_link merge_requests_group_path(@group), title: _('Merge Requests') do = link_to merge_requests_group_path(@group) do
.nav-icon-container .nav-icon-container
= custom_icon('mr_bold') = custom_icon('mr_bold')
%span.nav-item-name %span.nav-item-name
...@@ -77,19 +77,19 @@ ...@@ -77,19 +77,19 @@
#{ _('Merge Requests') } #{ _('Merge Requests') }
%span.badge.count.merge_counter.js-merge-counter.fly-out-badge= number_with_delimiter(merge_requests.count) %span.badge.count.merge_counter.js-merge-counter.fly-out-badge= number_with_delimiter(merge_requests.count)
= nav_link(path: 'group_members#index') do = nav_link(path: 'group_members#index') do
= sidebar_link group_group_members_path(@group), title: _('Members') do = link_to group_group_members_path(@group) do
.nav-icon-container .nav-icon-container
= custom_icon('members') = custom_icon('members')
%span.nav-item-name %span.nav-item-name
Members Members
%ul.sidebar-sub-level-items.is-fly-out-only %ul.sidebar-sub-level-items.is-fly-out-only
= nav_link(path: 'group_members#index', html_options: { class: "fly-out-top-item" } ) do = nav_link(path: 'group_members#index', html_options: { class: "fly-out-top-item" } ) do
= link_to merge_requests_group_path(@group) do = link_to group_group_members_path(@group) do
%strong.fly-out-top-item-name %strong.fly-out-top-item-name
#{ _('Members') } #{ _('Members') }
- if current_user && can?(current_user, :admin_group, @group) - if current_user && can?(current_user, :admin_group, @group)
= nav_link(path: %w[groups#projects groups#edit ci_cd#show]) do = nav_link(path: %w[groups#projects groups#edit ci_cd#show]) do
= sidebar_link edit_group_path(@group), title: _('Settings') do = link_to edit_group_path(@group) do
.nav-icon-container .nav-icon-container
= custom_icon('settings') = custom_icon('settings')
%span.nav-item-name %span.nav-item-name
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
.sidebar-context-title User Settings .sidebar-context-title User Settings
%ul.sidebar-top-level-items %ul.sidebar-top-level-items
= nav_link(path: 'profiles#show', html_options: {class: 'home'}) do = nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
= sidebar_link profile_path, title: _('Profile Settings') do = link_to profile_path do
.nav-icon-container .nav-icon-container
= custom_icon('profile') = custom_icon('profile')
%span.nav-item-name %span.nav-item-name
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
%strong.fly-out-top-item-name %strong.fly-out-top-item-name
#{ _('Profile') } #{ _('Profile') }
= nav_link(controller: [:accounts, :two_factor_auths]) do = nav_link(controller: [:accounts, :two_factor_auths]) do
= sidebar_link profile_account_path, title: _('Account') do = link_to profile_account_path do
.nav-icon-container .nav-icon-container
= custom_icon('account') = custom_icon('account')
%span.nav-item-name %span.nav-item-name
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
#{ _('Account') } #{ _('Account') }
- if current_application_settings.user_oauth_applications? - if current_application_settings.user_oauth_applications?
= nav_link(controller: 'oauth/applications') do = nav_link(controller: 'oauth/applications') do
= sidebar_link applications_profile_path, title: _('Applications') do = link_to applications_profile_path do
.nav-icon-container .nav-icon-container
= custom_icon('applications') = custom_icon('applications')
%span.nav-item-name %span.nav-item-name
...@@ -41,7 +41,7 @@ ...@@ -41,7 +41,7 @@
%strong.fly-out-top-item-name %strong.fly-out-top-item-name
#{ _('Applications') } #{ _('Applications') }
= nav_link(controller: :chat_names) do = nav_link(controller: :chat_names) do
= sidebar_link profile_chat_names_path, title: _('Chat') do = link_to profile_chat_names_path do
.nav-icon-container .nav-icon-container
= custom_icon('chat') = custom_icon('chat')
%span.nav-item-name %span.nav-item-name
...@@ -52,7 +52,7 @@ ...@@ -52,7 +52,7 @@
%strong.fly-out-top-item-name %strong.fly-out-top-item-name
#{ _('Chat') } #{ _('Chat') }
= nav_link(controller: :personal_access_tokens) do = nav_link(controller: :personal_access_tokens) do
= sidebar_link profile_personal_access_tokens_path, title: _('Access Tokens') do = link_to profile_personal_access_tokens_path do
.nav-icon-container .nav-icon-container
= custom_icon('access_tokens') = custom_icon('access_tokens')
%span.nav-item-name %span.nav-item-name
...@@ -63,7 +63,7 @@ ...@@ -63,7 +63,7 @@
%strong.fly-out-top-item-name %strong.fly-out-top-item-name
#{ _('Access Tokens') } #{ _('Access Tokens') }
= nav_link(controller: :emails) do = nav_link(controller: :emails) do
= sidebar_link profile_emails_path, title: _('Emails') do = link_to profile_emails_path do
.nav-icon-container .nav-icon-container
= custom_icon('emails') = custom_icon('emails')
%span.nav-item-name %span.nav-item-name
...@@ -75,7 +75,7 @@ ...@@ -75,7 +75,7 @@
#{ _('Emails') } #{ _('Emails') }
- unless current_user.ldap_user? - unless current_user.ldap_user?
= nav_link(controller: :passwords) do = nav_link(controller: :passwords) do
= sidebar_link edit_profile_password_path, title: _('Password') do = link_to edit_profile_password_path do
.nav-icon-container .nav-icon-container
= custom_icon('lock') = custom_icon('lock')
%span.nav-item-name %span.nav-item-name
...@@ -86,7 +86,7 @@ ...@@ -86,7 +86,7 @@
%strong.fly-out-top-item-name %strong.fly-out-top-item-name
#{ _('Password') } #{ _('Password') }
= nav_link(controller: :notifications) do = nav_link(controller: :notifications) do
= sidebar_link profile_notifications_path, title: _('Notifications') do = link_to profile_notifications_path do
.nav-icon-container .nav-icon-container
= custom_icon('notifications') = custom_icon('notifications')
%span.nav-item-name %span.nav-item-name
...@@ -97,7 +97,7 @@ ...@@ -97,7 +97,7 @@
%strong.fly-out-top-item-name %strong.fly-out-top-item-name
#{ _('Notifications') } #{ _('Notifications') }
= nav_link(controller: :keys) do = nav_link(controller: :keys) do
= sidebar_link profile_keys_path, title: _('SSH Keys') do = link_to profile_keys_path do
.nav-icon-container .nav-icon-container
= custom_icon('key') = custom_icon('key')
%span.nav-item-name %span.nav-item-name
...@@ -108,7 +108,7 @@ ...@@ -108,7 +108,7 @@
%strong.fly-out-top-item-name %strong.fly-out-top-item-name
#{ _('SSH Keys') } #{ _('SSH Keys') }
= nav_link(controller: :gpg_keys) do = nav_link(controller: :gpg_keys) do
= sidebar_link profile_gpg_keys_path, title: _('GPG Keys') do = link_to profile_gpg_keys_path do
.nav-icon-container .nav-icon-container
= custom_icon('key_2') = custom_icon('key_2')
%span.nav-item-name %span.nav-item-name
...@@ -119,7 +119,7 @@ ...@@ -119,7 +119,7 @@
%strong.fly-out-top-item-name %strong.fly-out-top-item-name
#{ _('GPG Keys') } #{ _('GPG Keys') }
= nav_link(controller: :preferences) do = nav_link(controller: :preferences) do
= sidebar_link profile_preferences_path, title: _('Preferences') do = link_to profile_preferences_path do
.nav-icon-container .nav-icon-container
= custom_icon('preferences') = custom_icon('preferences')
%span.nav-item-name %span.nav-item-name
...@@ -130,7 +130,7 @@ ...@@ -130,7 +130,7 @@
%strong.fly-out-top-item-name %strong.fly-out-top-item-name
#{ _('Preferences') } #{ _('Preferences') }
= nav_link(path: 'profiles#audit_log') do = nav_link(path: 'profiles#audit_log') do
= sidebar_link audit_log_profile_path, title: _('Authentication log') do = link_to audit_log_profile_path do
.nav-icon-container .nav-icon-container
= custom_icon('authentication_log') = custom_icon('authentication_log')
%span.nav-item-name %span.nav-item-name
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
= @project.name = @project.name
%ul.sidebar-top-level-items %ul.sidebar-top-level-items
= nav_link(path: ['projects#show', 'projects#activity', 'cycle_analytics#show'], html_options: { class: 'home' }) do = nav_link(path: ['projects#show', 'projects#activity', 'cycle_analytics#show'], html_options: { class: 'home' }) do
= sidebar_link project_path(@project), title: _('Project overview'), css: 'shortcuts-project' do = link_to project_path(@project), class: 'shortcuts-project' do
.nav-icon-container .nav-icon-container
= custom_icon('project') = custom_icon('project')
%span.nav-item-name %span.nav-item-name
...@@ -36,7 +36,7 @@ ...@@ -36,7 +36,7 @@
- if project_nav_tab? :files - if project_nav_tab? :files
= nav_link(controller: %w(tree blob blame edit_tree new_tree find_file commit commits compare projects/repositories tags branches releases graphs network)) do = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file commit commits compare projects/repositories tags branches releases graphs network)) do
= sidebar_link project_tree_path(@project), title: _('Repository'), css: 'shortcuts-tree' do = link_to project_tree_path(@project), class: 'shortcuts-tree' do
.nav-icon-container .nav-icon-container
= custom_icon('doc_text') = custom_icon('doc_text')
%span.nav-item-name %span.nav-item-name
...@@ -82,7 +82,7 @@ ...@@ -82,7 +82,7 @@
- if project_nav_tab? :container_registry - if project_nav_tab? :container_registry
= nav_link(controller: %w[projects/registry/repositories]) do = nav_link(controller: %w[projects/registry/repositories]) do
= sidebar_link project_container_registry_index_path(@project), title: _('Container Registry'), css: 'shortcuts-container-registry' do = link_to project_container_registry_index_path(@project), class: 'shortcuts-container-registry' do
.nav-icon-container .nav-icon-container
= custom_icon('container_registry') = custom_icon('container_registry')
%span.nav-item-name %span.nav-item-name
...@@ -90,7 +90,7 @@ ...@@ -90,7 +90,7 @@
- if project_nav_tab? :issues - if project_nav_tab? :issues
= nav_link(controller: @project.issues_enabled? ? [:issues, :labels, :milestones, :boards] : :issues) do = nav_link(controller: @project.issues_enabled? ? [:issues, :labels, :milestones, :boards] : :issues) do
= sidebar_link project_issues_path(@project), title: _('Issues'), css: 'shortcuts-issues' do = link_to project_issues_path(@project), class: 'shortcuts-issues' do
.nav-icon-container .nav-icon-container
= custom_icon('issues') = custom_icon('issues')
%span.nav-item-name %span.nav-item-name
...@@ -114,9 +114,9 @@ ...@@ -114,9 +114,9 @@
List List
= nav_link(controller: :boards) do = nav_link(controller: :boards) do
= link_to project_boards_path(@project), title: 'Board' do = link_to project_boards_path(@project), title: boards_link_text do
%span %span
Board = boards_link_text
.feature-highlight.js-feature-highlight{ disabled: true, data: { trigger: 'manual', container: 'body', toggle: 'popover', placement: 'right', highlight: 'issue-boards' } } .feature-highlight.js-feature-highlight{ disabled: true, data: { trigger: 'manual', container: 'body', toggle: 'popover', placement: 'right', highlight: 'issue-boards' } }
.feature-highlight-popover-content .feature-highlight-popover-content
= render 'feature_highlight/issue_boards.svg' = render 'feature_highlight/issue_boards.svg'
...@@ -144,7 +144,7 @@ ...@@ -144,7 +144,7 @@
- if project_nav_tab? :merge_requests - if project_nav_tab? :merge_requests
= nav_link(controller: @project.issues_enabled? ? :merge_requests : [:merge_requests, :labels, :milestones]) do = nav_link(controller: @project.issues_enabled? ? :merge_requests : [:merge_requests, :labels, :milestones]) do
= sidebar_link project_merge_requests_path(@project), title: _('Merge Requests'), css: 'shortcuts-merge_requests' do = link_to project_merge_requests_path(@project), class: 'shortcuts-merge_requests' do
.nav-icon-container .nav-icon-container
= custom_icon('mr_bold') = custom_icon('mr_bold')
%span.nav-item-name %span.nav-item-name
...@@ -161,7 +161,7 @@ ...@@ -161,7 +161,7 @@
- if project_nav_tab? :pipelines - if project_nav_tab? :pipelines
= nav_link(controller: [:pipelines, :builds, :jobs, :pipeline_schedules, :environments, :artifacts]) do = nav_link(controller: [:pipelines, :builds, :jobs, :pipeline_schedules, :environments, :artifacts]) do
= sidebar_link project_pipelines_path(@project), title: _('CI / CD'), css: 'shortcuts-pipelines' do = link_to project_pipelines_path(@project), class: 'shortcuts-pipelines' do
.nav-icon-container .nav-icon-container
= custom_icon('pipeline') = custom_icon('pipeline')
%span.nav-item-name %span.nav-item-name
...@@ -205,7 +205,7 @@ ...@@ -205,7 +205,7 @@
- if project_nav_tab? :wiki - if project_nav_tab? :wiki
= nav_link(controller: :wikis) do = nav_link(controller: :wikis) do
= sidebar_link get_project_wiki_path(@project), title: _('Wiki'), css: 'shortcuts-wiki' do = link_to get_project_wiki_path(@project), class: 'shortcuts-wiki' do
.nav-icon-container .nav-icon-container
= custom_icon('wiki') = custom_icon('wiki')
%span.nav-item-name %span.nav-item-name
...@@ -218,7 +218,7 @@ ...@@ -218,7 +218,7 @@
- if project_nav_tab? :snippets - if project_nav_tab? :snippets
= nav_link(controller: :snippets) do = nav_link(controller: :snippets) do
= sidebar_link project_snippets_path(@project), title: _('Snippets'), css: 'shortcuts-snippets' do = link_to project_snippets_path(@project), class: 'shortcuts-snippets' do
.nav-icon-container .nav-icon-container
= custom_icon('snippets') = custom_icon('snippets')
%span.nav-item-name %span.nav-item-name
...@@ -231,7 +231,7 @@ ...@@ -231,7 +231,7 @@
- if project_nav_tab? :settings - if project_nav_tab? :settings
= nav_link(path: %w[projects#edit project_members#index integrations#show services#edit repository#show ci_cd#show pages#show]) do = nav_link(path: %w[projects#edit project_members#index integrations#show services#edit repository#show ci_cd#show pages#show]) do
= sidebar_link edit_project_path(@project), title: _('Settings'), css: 'shortcuts-tree' do = link_to edit_project_path(@project), class: 'shortcuts-tree' do
.nav-icon-container .nav-icon-container
= custom_icon('settings') = custom_icon('settings')
%span.nav-item-name %span.nav-item-name
......
...@@ -3,6 +3,26 @@ ...@@ -3,6 +3,26 @@
= render 'profiles/head' = render 'profiles/head'
= form_for @user, url: profile_preferences_path, remote: true, method: :put, html: { class: 'row prepend-top-default js-preferences-form' } do |f| = form_for @user, url: profile_preferences_path, remote: true, method: :put, html: { class: 'row prepend-top-default js-preferences-form' } do |f|
.col-lg-4.application-theme
%h4.prepend-top-0
GitLab navigation theme
%p Customize the appearance of the application header and navigation sidebar.
.col-lg-8.application-theme
- Gitlab::Themes.each do |theme|
= label_tag do
.preview{ class: theme.name.downcase }
.preview-row
.quadrant.one
.quadrant.two
.preview-row
.quadrant.three
.quadrant.four
= f.radio_button :theme_id, theme.id, checked: Gitlab::Themes.for_user(@user).id == theme.id
= theme.name
.col-sm-12
%hr
.col-lg-4.profile-settings-sidebar .col-lg-4.profile-settings-sidebar
%h4.prepend-top-0 %h4.prepend-top-0
Syntax highlighting theme Syntax highlighting theme
......
// Remove body class for any previous theme, re-add current one
$('body').removeClass('<%= Gitlab::Themes.body_classes %>')
$('body').addClass('<%= user_application_theme %>')
// Toggle container-fluid class // Toggle container-fluid class
if ('<%= current_user.layout %>' === 'fluid') { if ('<%= current_user.layout %>' === 'fluid') {
$('.content-wrapper .container-fluid').removeClass('container-limited') $('.content-wrapper .container-fluid').removeClass('container-limited')
......
.file-content.blob_file.blob-no-preview .file-content.blob_file.blob-no-preview
.center .center.render-error.vertical-center
= link_to blob_raw_path do = link_to blob_raw_path do
%h1.light %h1.light
= icon('download') = icon('download')
......
...@@ -43,7 +43,8 @@ ...@@ -43,7 +43,8 @@
data: { toggle: "modal", data: { toggle: "modal",
target: "#modal-delete-branch", target: "#modal-delete-branch",
delete_path: project_branch_path(@project, branch.name), delete_path: project_branch_path(@project, branch.name),
branch_name: branch.name } } branch_name: branch.name,
is_merged: ("true" if @repository.merged_to_root_ref?(branch.name)) } }
= icon("trash-o") = icon("trash-o")
- else - else
%button{ class: "btn btn-remove remove-row js-ajax-loading-spinner has-tooltip disabled", %button{ class: "btn btn-remove remove-row js-ajax-loading-spinner has-tooltip disabled",
......
...@@ -6,13 +6,18 @@ ...@@ -6,13 +6,18 @@
%h3.page-title %h3.page-title
Delete protected branch Delete protected branch
= surround "'", "'?" do = surround "'", "'?" do
%span.js-branch-name>[branch name] %span.js-branch-name.ref-name>[branch name]
.modal-body .modal-body
%p %p
You’re about to permanently delete the protected branch You’re about to permanently delete the protected branch
= succeed '.' do = succeed '.' do
%strong.js-branch-name [branch name] %strong.js-branch-name.ref-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.")
%p %p
Once you confirm and press Once you confirm and press
= succeed ',' do = succeed ',' do
......
- pipeline = local_assigns.fetch(:pipeline) { project.pipelines.latest_successful_for(ref) } - pipeline = local_assigns.fetch(:pipeline) { project.latest_successful_pipeline_for(ref) }
- if !project.empty_repo? && can?(current_user, :download_code, project) - if !project.empty_repo? && can?(current_user, :download_code, project)
.project-action-button.dropdown.inline> .project-action-button.dropdown.inline>
...@@ -26,18 +26,16 @@ ...@@ -26,18 +26,16 @@
%i.fa.fa-download %i.fa.fa-download
%span= _('Download tar') %span= _('Download tar')
- if pipeline - if pipeline && pipeline.latest_builds_with_artifacts.any?
- artifacts = pipeline.builds.latest.with_artifacts %li.dropdown-header Artifacts
- if artifacts.any? - unless pipeline.latest?
%li.dropdown-header Artifacts - latest_pipeline = project.pipeline_for(ref)
- unless pipeline.latest? %li
- latest_pipeline = project.pipeline_for(ref) .unclickable= ci_status_for_statuseable(latest_pipeline)
%li %li.dropdown-header Previous Artifacts
.unclickable= ci_status_for_statuseable(latest_pipeline) - pipeline.latest_builds_with_artifacts.each do |job|
%li.dropdown-header Previous Artifacts %li
- artifacts.each do |job| = link_to latest_succeeded_project_artifacts_path(project, "#{ref}/download", job: job.name), rel: 'nofollow', download: '' do
%li %i.fa.fa-download
= link_to latest_succeeded_project_artifacts_path(project, "#{ref}/download", job: job.name), rel: 'nofollow', download: '' do %span
%i.fa.fa-download #{s_('DownloadArtifacts|Download')} '#{job.name}'
%span
#{ s_('DownloadArtifacts|Download') } '#{job.name}'
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.
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