Commit 0994bbf9 authored by Mike Greiling's avatar Mike Greiling

Merge branch 'master' into ide

* master: (86 commits)
  Show all labels
  33874 confidential issue redesign
  Exclude merge_jid on Import/Export attribute configuration
  Resolve "User dropdown in filtered search does not load avatar on `master`"
  Re-add column locked_at on migration rollback
  Group-level new issue & MR using previously selected project
  [EE Backport] Update log audit event in omniauth_callbacks_controller.rb
  more eagerly bail when the state is prevented
  Move locked_at removal to post-deployment migration
  Add class to other sidebars
  Improve mobile sidebar
  reduce iterations by keeping a count of remaining enablers
  Store & use ConvDev percentages returned by Version app
  Store MergeWorker JID on merge request, and clean up stuck merges
  Backport changes in https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2551 to CE
  DRY up caching in AbstractReferenceFilter
  Update CHANGELOG
  Add CHANGELOG entry
  Fix html structure Removes test for removed behavior
  Port form back to use form_tag
  ...
parents 1375db50 9e7ac48b
......@@ -3,4 +3,5 @@ lib/gitlab/sanitizers/svg/whitelist.rb
lib/gitlab/diff/position_tracer.rb
app/policies/project_policy.rb
app/models/concerns/relative_positioning.rb
app/workers/stuck_merge_jobs_worker.rb
lib/gitlab/redis/*.rb
......@@ -390,8 +390,18 @@ gem 'health_check', '~> 2.6.0'
gem 'vmstat', '~> 2.3.0'
gem 'sys-filesystem', '~> 1.1.6'
# SSH host key support
gem 'net-ssh', '~> 4.1.0'
# Required for ED25519 SSH host key support
group :ed25519 do
gem 'rbnacl-libsodium'
gem 'rbnacl', '~> 3.2'
gem 'bcrypt_pbkdf', '~> 1.0'
end
# Gitaly GRPC client
gem 'gitaly', '~> 0.24.0'
gem 'gitaly', '~> 0.26.0'
gem 'toml-rb', '~> 0.3.15', require: false
......
......@@ -75,6 +75,7 @@ GEM
babosa (1.0.2)
base32 (0.3.2)
bcrypt (3.1.11)
bcrypt_pbkdf (1.0.0)
benchmark-ips (2.3.0)
better_errors (2.1.1)
coderay (>= 1.0.0)
......@@ -269,7 +270,7 @@ GEM
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
gherkin-ruby (0.3.2)
gitaly (0.24.0)
gitaly (0.26.0)
google-protobuf (~> 3.1)
grpc (~> 1.0)
github-linguist (4.7.6)
......@@ -475,6 +476,7 @@ GEM
mustermann (~> 1.0.0)
mysql2 (0.4.5)
net-ldap (0.16.0)
net-ssh (4.1.0)
netrc (0.11.0)
nokogiri (1.6.8.1)
mini_portile2 (~> 2.1.0)
......@@ -662,6 +664,10 @@ GEM
rake (12.0.0)
rblineprof (0.3.6)
debugger-ruby_core_source (~> 1.3)
rbnacl (3.4.0)
ffi
rbnacl-libsodium (1.0.11)
rbnacl (>= 3.0.1)
rdoc (4.2.2)
json (~> 1.4)
re2 (1.1.1)
......@@ -924,6 +930,7 @@ DEPENDENCIES
awesome_print (~> 1.2.0)
babosa (~> 1.0.2)
base32 (~> 0.3.0)
bcrypt_pbkdf (~> 1.0)
benchmark-ips (~> 2.3.0)
better_errors (~> 2.1.0)
binding_of_caller (~> 0.7.2)
......@@ -975,7 +982,7 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.2.0)
gitaly (~> 0.24.0)
gitaly (~> 0.26.0)
github-linguist (~> 4.7.0)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-markup (~> 1.5.1)
......@@ -1016,6 +1023,7 @@ DEPENDENCIES
mousetrap-rails (~> 1.4.6)
mysql2 (~> 0.4.5)
net-ldap
net-ssh (~> 4.1.0)
nokogiri (~> 1.6.7, >= 1.6.7.2)
oauth2 (~> 1.4)
octokit (~> 4.6.2)
......@@ -1062,6 +1070,8 @@ DEPENDENCIES
rainbow (~> 2.2)
raindrops (~> 0.18)
rblineprof (~> 0.3.6)
rbnacl (~> 3.2)
rbnacl-libsodium
rdoc (~> 4.2)
re2 (~> 1.1.1)
recaptcha (~> 3.0)
......
......@@ -97,9 +97,8 @@ gl.issueBoards.IssueCardInner = Vue.extend({
return `Avatar for ${assignee.name}`;
},
showLabel(label) {
if (!this.list) return true;
return !this.list.label || label.id !== this.list.label.id;
if (!this.list || !label) return true;
return true;
},
filterByLabel(label, e) {
if (!this.updateFilters) return;
......
......@@ -164,7 +164,6 @@ window.Build = (function () {
Build.prototype.initSidebar = function () {
this.$sidebar = $('.js-build-sidebar');
this.$sidebar.niceScroll();
};
Build.prototype.getBuildTrace = function () {
......
......@@ -2,7 +2,7 @@
$(function() {
$('.reveal-variables').off('click').on('click', function() {
$('.js-build').toggle().niceScroll();
$('.js-build-variables').toggle();
$(this).hide();
});
});
......@@ -3,13 +3,13 @@ import $ from 'jquery';
// bootstrap jQuery plugins
import 'bootstrap-sass/assets/javascripts/bootstrap/affix';
import 'bootstrap-sass/assets/javascripts/bootstrap/alert';
import 'bootstrap-sass/assets/javascripts/bootstrap/button';
import 'bootstrap-sass/assets/javascripts/bootstrap/dropdown';
import 'bootstrap-sass/assets/javascripts/bootstrap/modal';
import 'bootstrap-sass/assets/javascripts/bootstrap/tab';
import 'bootstrap-sass/assets/javascripts/bootstrap/transition';
import 'bootstrap-sass/assets/javascripts/bootstrap/tooltip';
import 'bootstrap-sass/assets/javascripts/bootstrap/popover';
import 'bootstrap-sass/assets/javascripts/bootstrap/button';
// custom jQuery functions
$.fn.extend({
......
......@@ -6,6 +6,5 @@ import 'vendor/jquery.endless-scroll';
import 'vendor/jquery.caret';
import 'vendor/jquery.atwho';
import 'vendor/jquery.scrollTo';
import 'vendor/jquery.nicescroll';
import 'vendor/jquery.waitforimages';
import 'select2/select2';
......@@ -580,7 +580,6 @@ import UserFeatureHelper from './helpers/user_feature_helper';
shortcut_handler = new ShortcutsWiki();
new ZenMode();
new gl.GLForm($('.wiki-form'), true);
new Sidebar();
break;
case 'snippets':
shortcut_handler = new ShortcutsNavigation();
......
......@@ -45,8 +45,10 @@ export default class NewNavSidebar {
toggleCollapsedSidebar(collapsed) {
this.$sidebar.toggleClass('sidebar-icons-only', collapsed);
this.$page.toggleClass('page-with-new-sidebar', !collapsed);
this.$page.toggleClass('page-with-icon-sidebar', collapsed);
if (this.$sidebar.length) {
this.$page.toggleClass('page-with-new-sidebar', !collapsed);
this.$page.toggleClass('page-with-icon-sidebar', collapsed);
}
NewNavSidebar.setCollapsedCookie(collapsed);
}
......
/* eslint-disable func-names, space-before-function-paren, wrap-iife, prefer-arrow-callback, no-var, comma-dangle, object-shorthand, one-var, one-var-declaration-per-line, no-else-return, quotes, max-len */
import Api from './api';
import ProjectSelectComboButton from './project_select_combo_button';
(function() {
this.ProjectSelect = (function() {
......@@ -58,7 +59,8 @@ import Api from './api';
if (this.includeGroups) {
placeholder += " or group";
}
return $(select).select2({
$(select).select2({
placeholder: placeholder,
minimumInputLength: 0,
query: (function(_this) {
......@@ -96,21 +98,18 @@ import Api from './api';
};
})(this),
id: function(project) {
return project.web_url;
return JSON.stringify({
name: project.name,
url: project.web_url,
});
},
text: function(project) {
return project.name_with_namespace || project.name;
},
dropdownCssClass: "ajax-project-dropdown"
});
});
$('.new-project-item-select-button').on('click', function() {
$('.project-item-select', this.parentNode).select2('open');
});
$('.project-item-select').on('click', function() {
window.location = `${$(this).val()}/${this.dataset.relativePath}`;
return new ProjectSelectComboButton(select);
});
}
......
import AccessorUtilities from './lib/utils/accessor';
export default class ProjectSelectComboButton {
constructor(select) {
this.projectSelectInput = $(select);
this.newItemBtn = $('.new-project-item-link');
this.newItemBtnBaseText = this.newItemBtn.data('label');
this.itemType = this.deriveItemTypeFromLabel();
this.groupId = this.projectSelectInput.data('groupId');
this.bindEvents();
this.initLocalStorage();
}
bindEvents() {
this.projectSelectInput.siblings('.new-project-item-select-button')
.on('click', this.openDropdown);
this.projectSelectInput.on('change', () => this.selectProject());
}
initLocalStorage() {
const localStorageIsSafe = AccessorUtilities.isLocalStorageAccessSafe();
if (localStorageIsSafe) {
const itemTypeKebabed = this.newItemBtnBaseText.toLowerCase().split(' ').join('-');
this.localStorageKey = ['group', this.groupId, itemTypeKebabed, 'recent-project'].join('-');
this.setBtnTextFromLocalStorage();
}
}
openDropdown() {
$(this).siblings('.project-item-select').select2('open');
}
selectProject() {
const selectedProjectData = JSON.parse(this.projectSelectInput.val());
const projectUrl = `${selectedProjectData.url}/${this.projectSelectInput.data('relativePath')}`;
const projectName = selectedProjectData.name;
const projectMeta = {
url: projectUrl,
name: projectName,
};
this.setNewItemBtnAttributes(projectMeta);
this.setProjectInLocalStorage(projectMeta);
}
setBtnTextFromLocalStorage() {
const cachedProjectData = this.getProjectFromLocalStorage();
this.setNewItemBtnAttributes(cachedProjectData);
}
setNewItemBtnAttributes(project) {
if (project) {
this.newItemBtn.attr('href', project.url);
this.newItemBtn.text(`${this.newItemBtnBaseText} in ${project.name}`);
this.newItemBtn.enable();
} else {
this.newItemBtn.text(`Select project to create ${this.itemType}`);
this.newItemBtn.disable();
}
}
deriveItemTypeFromLabel() {
// label is either 'New issue' or 'New merge request'
return this.newItemBtnBaseText.split(' ').slice(1).join(' ');
}
getProjectFromLocalStorage() {
const projectString = localStorage.getItem(this.localStorageKey);
return JSON.parse(projectString);
}
setProjectInLocalStorage(projectMeta) {
const projectString = JSON.stringify(projectMeta);
localStorage.setItem(this.localStorageKey, projectString);
}
}
import '../lib/utils/url_utility';
const bindEvents = () => {
const path = gl.utils.getParameterValues('path')[0];
// get the path url and append it in the inputS
$('.js-path-name').val(path);
};
document.addEventListener('DOMContentLoaded', bindEvents);
export default {
bindEvents,
};
let hasUserDefinedProjectPath = false;
const deriveProjectPathFromUrl = ($projectImportUrl, $projectPath) => {
if ($projectImportUrl.attr('disabled') || hasUserDefinedProjectPath) {
if (hasUserDefinedProjectPath) {
return;
}
......@@ -27,8 +27,6 @@ const deriveProjectPathFromUrl = ($projectImportUrl, $projectPath) => {
const bindEvents = () => {
const $newProjectForm = $('#new_project');
const importBtnTooltip = 'Please enter a valid project name.';
const $importBtnWrapper = $('.import_gitlab_project');
const $projectImportUrl = $('#project_import_url');
const $projectPath = $('#project_path');
......@@ -50,31 +48,15 @@ const bindEvents = () => {
$('.btn_import_gitlab_project').attr('href', `${importHref}?namespace_id=${$('#project_namespace_id').val()}&path=${$projectPath.val()}`);
});
$('.btn_import_gitlab_project').attr('disabled', !$projectPath.val().trim().length);
$importBtnWrapper.attr('title', importBtnTooltip);
$newProjectForm.on('submit', () => {
$projectPath.val($projectPath.val().trim());
});
$projectPath.on('keyup', () => {
hasUserDefinedProjectPath = $projectPath.val().trim().length > 0;
if (hasUserDefinedProjectPath) {
$('.btn_import_gitlab_project').attr('disabled', false);
$importBtnWrapper.attr('title', '');
$importBtnWrapper.removeClass('has-tooltip');
} else {
$('.btn_import_gitlab_project').attr('disabled', true);
$importBtnWrapper.addClass('has-tooltip');
}
});
$projectImportUrl.disable();
$projectImportUrl.keyup(() => deriveProjectPathFromUrl($projectImportUrl, $projectPath));
$('.import_git').on('click', () => {
$projectImportUrl.attr('disabled', !$projectImportUrl.attr('disabled'));
});
};
document.addEventListener('DOMContentLoaded', bindEvents);
......
<script>
/* global Flash */
import editForm from './edit_form.vue';
export default {
components: {
editForm,
},
props: {
isConfidential: {
required: true,
type: Boolean,
},
isEditable: {
required: true,
type: Boolean,
},
service: {
required: true,
type: Object,
},
},
data() {
return {
edit: false,
};
},
computed: {
faEye() {
const eye = this.isConfidential ? 'fa-eye-slash' : 'fa-eye';
return {
[eye]: true,
};
},
},
methods: {
toggleForm() {
this.edit = !this.edit;
},
updateConfidentialAttribute(confidential) {
this.service.update('issue', { confidential })
.then(() => location.reload())
.catch(() => new Flash('Something went wrong trying to change the confidentiality of this issue'));
},
},
};
</script>
<template>
<div class="block confidentiality">
<div class="sidebar-collapsed-icon">
<i class="fa" :class="faEye" aria-hidden="true" data-hidden="true"></i>
</div>
<div class="title hide-collapsed">
Confidentiality
<a
v-if="isEditable"
class="pull-right confidential-edit"
href="#"
@click.prevent="toggleForm"
>
Edit
</a>
</div>
<div class="value confidential-value hide-collapsed">
<editForm
v-if="edit"
:toggle-form="toggleForm"
:is-confidential="isConfidential"
:update-confidential-attribute="updateConfidentialAttribute"
/>
<div v-if="!isConfidential" class="no-value confidential-value">
<i class="fa fa-eye is-not-confidential"></i>
None
</div>
<div v-else class="value confidential-value hide-collapsed">
<i aria-hidden="true" data-hidden="true" class="fa fa-eye-slash is-confidential"></i>
This issue is confidential
</div>
</div>
</div>
</template>
<script>
import editFormButtons from './edit_form_buttons.vue';
export default {
components: {
editFormButtons,
},
props: {
isConfidential: {
required: true,
type: Boolean,
},
toggleForm: {
required: true,
type: Function,
},
updateConfidentialAttribute: {
required: true,
type: Function,
},
},
};
</script>
<template>
<div class="dropdown open">
<div class="dropdown-menu confidential-warning-message">
<div>
<p v-if="!isConfidential">
You are going to turn on the confidentiality. This means that only team members with
<strong>at least Reporter access</strong>
are able to see and leave comments on the issue.
</p>
<p v-else>
You are going to turn off the confidentiality. This means
<strong>everyone</strong>
will be able to see and leave a comment on this issue.
</p>
<edit-form-buttons
:is-confidential="isConfidential"
:toggle-form="toggleForm"
:update-confidential-attribute="updateConfidentialAttribute"
/>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
isConfidential: {
required: true,
type: Boolean,
},
toggleForm: {
required: true,
type: Function,
},
updateConfidentialAttribute: {
required: true,
type: Function,
},
},
computed: {
onOrOff() {
return this.isConfidential ? 'Turn Off' : 'Turn On';
},
updateConfidentialBool() {
return !this.isConfidential;
},
},
};
</script>
<template>
<div class="confidential-warning-message-actions">
<button
type="button"
class="btn btn-default append-right-10"
@click="toggleForm"
>
Cancel
</button>
<button
type="button"
class="btn btn-close"
@click.prevent="updateConfidentialAttribute(updateConfidentialBool)"
>
{{ onOrOff }}
</button>
</div>
</template>
import Vue from 'vue';
import sidebarTimeTracking from './components/time_tracking/sidebar_time_tracking';
import sidebarAssignees from './components/assignees/sidebar_assignees';
import confidential from './components/confidential/confidential_issue_sidebar.vue';
import Mediator from './sidebar_mediator';
......@@ -10,13 +11,28 @@ function domContentLoaded() {
mediator.fetch();
const sidebarAssigneesEl = document.querySelector('#js-vue-sidebar-assignees');
const confidentialEl = document.querySelector('#js-confidential-entry-point');
// Only create the sidebarAssignees vue app if it is found in the DOM
// We currently do not use sidebarAssignees for the MR page
if (sidebarAssigneesEl) {
new Vue(sidebarAssignees).$mount(sidebarAssigneesEl);
}
if (confidentialEl) {
const dataNode = document.getElementById('js-confidential-issue-data');
const initialData = JSON.parse(dataNode.innerHTML);
const ConfidentialComp = Vue.extend(confidential);
new ConfidentialComp({
propsData: {
isConfidential: initialData.is_confidential,
isEditable: initialData.is_editable,
service: mediator.service,
},
}).$mount(confidentialEl);
}
new Vue(sidebarTimeTracking).$mount('#issuable-time-tracker');
}
......
import statusIcon from '../mr_widget_status_icon';
export default {
name: 'MRWidgetLocked',
name: 'MRWidgetMerging',
props: {
mr: { type: Object, required: true },
},
......@@ -13,7 +13,7 @@ export default {
<status-icon status="loading" />
<div class="media-body">
<h4>
This merge request is in the process of being merged, during which time it is locked and cannot be closed
This merge request is in the process of being merged
</h4>
<section class="mr-info-list">
<p>
......
......@@ -19,7 +19,7 @@ export { default as WidgetRelatedLinks } from './components/mr_widget_related_li
export { default as MergedState } from './components/states/mr_widget_merged';
export { default as FailedToMerge } from './components/states/mr_widget_failed_to_merge';
export { default as ClosedState } from './components/states/mr_widget_closed';
export { default as LockedState } from './components/states/mr_widget_locked';
export { default as MergingState } from './components/states/mr_widget_merging';
export { default as WipState } from './components/states/mr_widget_wip';
export { default as ArchivedState } from './components/states/mr_widget_archived';
export { default as ConflictsState } from './components/states/mr_widget_conflicts';
......
......@@ -8,7 +8,7 @@ import {
WidgetRelatedLinks,
MergedState,
ClosedState,
LockedState,
MergingState,
WipState,
ArchivedState,
ConflictsState,
......@@ -212,7 +212,7 @@ export default {
'mr-widget-related-links': WidgetRelatedLinks,
'mr-widget-merged': MergedState,
'mr-widget-closed': ClosedState,
'mr-widget-locked': LockedState,
'mr-widget-merging': MergingState,
'mr-widget-failed-to-merge': FailedToMerge,
'mr-widget-wip': WipState,
'mr-widget-archived': ArchivedState,
......
......@@ -73,6 +73,7 @@ export default class MergeRequestStore {
this.canCancelAutomaticMerge = !!data.cancel_merge_when_pipeline_succeeds_path;
this.hasSHAChanged = this.sha !== data.diff_head_sha;
this.canBeMerged = data.can_be_merged || false;
this.mergeOngoing = data.merge_ongoing;
// Cherry-pick and Revert actions related
this.canCherryPickInCurrentMR = currentUser.can_cherry_pick_on_current_merge_request || false;
......@@ -94,6 +95,11 @@ export default class MergeRequestStore {
}
setState(data) {
if (this.mergeOngoing) {
this.state = 'merging';
return;
}
if (this.isOpen) {
this.state = getStateKey.call(this, data);
} else {
......@@ -104,9 +110,6 @@ export default class MergeRequestStore {
case 'closed':
this.state = 'closed';
break;
case 'locked':
this.state = 'locked';
break;
default:
this.state = null;
}
......
const stateToComponentMap = {
merged: 'mr-widget-merged',
closed: 'mr-widget-closed',
locked: 'mr-widget-locked',
merging: 'mr-widget-merging',
conflicts: 'mr-widget-conflicts',
missingBranch: 'mr-widget-missing-branch',
workInProgress: 'mr-widget-wip',
......@@ -20,7 +20,7 @@ const stateToComponentMap = {
};
const statesToShowHelpWidget = [
'locked',
'merging',
'conflicts',
'workInProgress',
'readyToMerge',
......
/* global Breakpoints */
import 'vendor/jquery.nicescroll';
import './breakpoints';
export default class Wikis {
......@@ -8,7 +7,6 @@ export default class Wikis {
this.bp = Breakpoints.get();
this.sidebarEl = document.querySelector('.js-wiki-sidebar');
this.sidebarExpanded = false;
$(this.sidebarEl).niceScroll();
const sidebarToggles = document.querySelectorAll('.js-sidebar-wiki-toggle');
for (let i = 0; i < sidebarToggles.length; i += 1) {
......
......@@ -100,6 +100,8 @@
margin: 0;
align-self: center;
}
&.s40 { min-width: 40px; min-height: 40px; }
}
.avatar-counter {
......
......@@ -109,16 +109,8 @@ body {
}
}
/* The following prevents side effects related to iOS Safari's implementation of -webkit-overflow-scrolling: touch,
which is applied to the body by jquery.nicescroling plugin to force hardware acceleration for momentum scrolling. Side
effects are commonly related to inconsisent z-index behavior (e.g. tooltips). By applying the following to direct children
of the body element here, we negate cascading side effects but allow momentum scrolling to be applied to the body */
.navbar,
.page-gutter,
.page-with-sidebar {
-webkit-overflow-scrolling: auto;
.page-with-sidebar > .content-wrapper {
min-height: calc(100vh - #{$header-height});
}
.with-performance-bar .page-with-sidebar {
......
......@@ -251,7 +251,6 @@
// Applies on /dashboard/issues
.project-item-select-holder {
display: block;
margin: 0;
}
}
......@@ -283,6 +282,31 @@
}
}
.project-item-select-holder.btn-group {
display: flex;
max-width: 350px;
overflow: hidden;
@media(max-width: $screen-xs-max) {
width: 100%;
max-width: none;
}
.new-project-item-link {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.new-project-item-select-button {
width: 32px;
}
}
.new-project-item-select-button .fa-caret-down {
margin-left: 2px;
}
.layout-nav {
width: 100%;
background: $gray-light;
......
......@@ -78,15 +78,12 @@
.right-sidebar {
border-left: 1px solid $border-color;
height: calc(100% - #{$header-height});
&.affix {
position: fixed;
top: $header-height;
}
&:not(.affix-top) {
min-height: 100%;
}
}
.with-performance-bar .right-sidebar.affix {
......
......@@ -637,3 +637,11 @@ $perf-bar-bucket-bg: #111;
$perf-bar-bucket-color: #ccc;
$perf-bar-bucket-box-shadow-from: rgba($white-light, .2);
$perf-bar-bucket-box-shadow-to: rgba($black, .25);
/*
Project Templates Icons
*/
$rails: #c00;
$node: #353535;
$java: #70ad51;
......@@ -15,7 +15,9 @@ $new-sidebar-width: 220px;
$new-sidebar-collapsed-width: 50px;
.page-with-new-sidebar {
padding-left: $new-sidebar-collapsed-width;
@media (min-width: $screen-md-min) {
padding-left: $new-sidebar-collapsed-width;
}
@media (min-width: $screen-lg-min) {
padding-left: $new-sidebar-width;
......@@ -24,7 +26,7 @@ $new-sidebar-collapsed-width: 50px;
// Override position: absolute
.right-sidebar {
position: fixed;
height: 100%;
height: calc(100% - #{$header-height});
}
.issues-bulk-update.right-sidebar.right-sidebar-expanded .issuable-sidebar-header {
......@@ -49,10 +51,6 @@ $new-sidebar-collapsed-width: 50px;
align-items: center;
padding: 10px 16px 10px 10px;
color: $gl-text-color;
@media (max-width: $screen-xs-max) {
padding-right: 30px;
}
}
&:hover,
......@@ -77,26 +75,6 @@ $new-sidebar-collapsed-width: 50px;
overflow: hidden;
text-overflow: ellipsis;
}
.close-nav-button {
display: none;
position: absolute;
top: 0;
right: 0;
height: 100%;
background-color: transparent;
border: 0;
padding: 0 10px;
color: $gl-text-color-secondary;
@media (max-width: $screen-xs-max) {
display: block;
}
&:hover {
color: $gl-text-color;
}
}
}
.settings-avatar {
......@@ -339,21 +317,19 @@ $new-sidebar-collapsed-width: 50px;
// Collapsed nav
.toggle-sidebar-button {
.toggle-sidebar-button,
.close-nav-button {
width: $new-sidebar-width - 2px;
position: fixed;
bottom: 0;
padding: 16px;
background-color: $gray-normal;
border: 0;
border-top: 2px solid $border-color;
color: $gl-text-color-secondary;
display: flex;
align-items: center;
@media (max-width: $screen-xs-max) {
display: none;
}
i {
font-size: 20px;
margin-right: 8px;
......@@ -369,6 +345,13 @@ $new-sidebar-collapsed-width: 50px;
}
}
.toggle-sidebar-button {
@media (max-width: $screen-xs-max) {
display: none;
}
}
.sidebar-icons-only {
.context-header {
height: 60px;
......@@ -400,6 +383,7 @@ $new-sidebar-collapsed-width: 50px;
.toggle-sidebar-button {
width: $new-sidebar-collapsed-width - 2px;
padding: 16px 18px;
.collapse-text,
.fa-angle-double-left {
......@@ -415,6 +399,10 @@ $new-sidebar-collapsed-width: 50px;
// Mobile nav
.close-nav-button {
display: none;
}
.toggle-mobile-nav {
display: none;
background-color: transparent;
......@@ -434,6 +422,12 @@ $new-sidebar-collapsed-width: 50px;
}
}
@media (max-width: $screen-xs-max) {
.close-nav-button {
display: flex;
}
}
.mobile-overlay {
display: none;
......
......@@ -165,6 +165,7 @@
.board-title {
padding-top: ($gl-padding - 3px);
padding-bottom: $gl-padding;
}
}
}
......@@ -178,6 +179,7 @@
position: relative;
margin: 0;
padding: $gl-padding;
padding-bottom: ($gl-padding + 3px);
font-size: 1em;
border-bottom: 1px solid $border-color;
}
......
......@@ -235,8 +235,18 @@
display: none;
}
.sidebar-container {
width: calc(100% + 100px);
padding-right: 100px;
height: 100%;
overflow-y: scroll;
overflow-x: hidden;
-webkit-overflow-scrolling: touch;
}
.blocks-container {
padding: 0 $gl-padding;
width: 289px;
}
.block {
......@@ -259,7 +269,15 @@
padding: 16px 0;
}
.trigger-build-variables {
margin: 0;
overflow-x: auto;
-ms-overflow-style: scrollbar;
-webkit-overflow-scrolling: touch;
}
.trigger-build-variable {
font-weight: normal;
color: $code-color;
}
......@@ -326,6 +344,7 @@
border-top: 1px solid $border-color;
border-bottom: 1px solid $border-color;
max-height: 300px;
width: 289px;
overflow: auto;
svg {
......
......@@ -5,6 +5,30 @@
margin-right: auto;
}
.is-confidential {
color: $orange-600;
background-color: $orange-50;
border-radius: 3px;
padding: 5px;
margin: 0 3px 0 -4px;
}
.is-not-confidential {
border-radius: 3px;
padding: 5px;
margin: 0 3px 0 -4px;
}
.confidentiality {
.is-not-confidential {
margin: auto;
}
.is-confidential {
margin: auto;
}
}
.limit-container-width {
.detail-page-header,
.page-content-header,
......
......@@ -104,40 +104,51 @@
}
.confidential-issue-warning {
background-color: $gray-normal;
border-radius: 3px;
color: $orange-600;
background-color: $orange-50;
border-radius: $border-radius-default $border-radius-default 0 0;
border: 1px solid $border-gray-normal;
padding: 3px 12px;
margin: auto;
margin-top: 0;
text-align: center;
font-size: 12px;
align-items: center;
}
@media (max-width: $screen-md-max) {
// On smaller devices the warning becomes the fourth item in the list,
// rather than centering, and grows to span the full width of the
// comment area.
order: 4;
margin: 6px auto;
width: 100%;
.confidential-value {
.fa {
background-color: inherit;
}
}
.fa {
margin-right: 8px;
.confidential-warning-message {
line-height: 1.5;
padding: 16px;
.confidential-warning-message-actions {
display: flex;
button {
flex-grow: 1;
}
}
}
.not-confidential {
padding: 0;
border-top: none;
}
.right-sidebar-expanded {
.confidential-issue-warning {
// When the sidebar is open the warning becomes the fourth item in the list,
// rather than centering, and grows to span the full width of the
// comment area.
order: 4;
margin: 6px auto;
width: 100%;
.md-area {
border-radius: 0;
border-top: none;
}
}
.right-sidebar-collapsed {
.confidential-issue-warning {
border-bottom: none;
}
}
.discussion-form {
padding: $gl-padding-top $gl-padding $gl-padding;
......
......@@ -7,7 +7,8 @@
}
.new_project,
.edit-project {
.edit-project,
.import-project {
.sharing-and-permissions {
.header {
......@@ -457,6 +458,7 @@ a.deploy-project-label {
}
}
.project-template,
.project-import {
.form-group {
margin-bottom: 5px;
......@@ -471,7 +473,44 @@ a.deploy-project-label {
.btn {
padding: 8px;
margin-left: 10px;
margin-right: 10px;
}
.blank-option {
min-width: 70px;
}
.btn-template-icon {
height: 24px;
width: inherit;
display: block;
margin: 0 auto 4px;
font-size: 24px;
@media (min-width: $screen-xs-max) {
top: 0;
}
}
@media (max-width: $screen-xs-max) {
.btn-template-icon {
display: inline-block;
height: 14px;
font-size: 14px;
margin: 0;
}
}
.icon-rails path {
fill: $rails;
}
.icon-node-express path {
fill: $node;
}
.icon-java-spring path {
fill: $java;
}
> div {
......@@ -481,6 +520,97 @@ a.deploy-project-label {
}
}
.project-templates-buttons .btn:last-child {
margin-right: 0;
}
.create-project-options {
display: flex;
@media (max-width: $screen-xs-max) {
display: block;
}
.first-column {
@media(min-width: $screen-xs-min) {
max-width: 50%;
padding-right: 30px;
}
@media(max-width: $screen-xs-max) {
max-width: 100%;
width: 100%;
}
}
.second-column {
@media(min-width: $screen-xs-min) {
width: 50%;
flex: 1;
padding-left: 30px;
position: relative;
}
@media(max-width: $screen-xs-max) {
max-width: 100%;
width: 100%;
padding-left: 0;
position: relative;
}
// Mobile
@media (max-width: $screen-xs-max) {
padding-top: 30px;
}
&::before {
content: "OR";
position: absolute;
left: 0;
top: 40%;
z-index: 10;
padding: 8px 0;
text-align: center;
background-color: $white-light;
color: $gl-text-color-tertiary;
transform: translateX(-50%);
font-size: 12px;
font-weight: bold;
line-height: 20px;
// Mobile
@media (max-width: $screen-xs-max) {
left: 50%;
top: 10px;
transform: translateY(-50%);
padding: 0 8px;
}
}
&::after {
content: "";
position: absolute;
background-color: $border-color;
bottom: 0;
left: 0;
right: auto;
height: 100%;
width: 1px;
top: 0;
// Mobile
@media (max-width: $screen-xs-max) {
top: 10px;
left: 10px;
right: 10px;
height: 1px;
width: auto;
}
}
}
}
.project-stats {
font-size: 0;
text-align: center;
......
......@@ -95,12 +95,22 @@
}
.right-sidebar.wiki-sidebar {
padding: $gl-padding 0;
padding: 0;
&.right-sidebar-collapsed {
display: none;
}
.sidebar-container {
padding: $gl-padding 0;
width: calc(100% + 100px);
padding-right: 100px;
height: 100%;
overflow-y: scroll;
overflow-x: hidden;
-webkit-overflow-scrolling: touch;
}
.blocks-container {
padding: 0 $gl-padding;
}
......
......@@ -12,15 +12,7 @@ class Import::GitlabProjectsController < Import::BaseController
return redirect_back_or_default(options: { alert: "You need to upload a GitLab project export archive." })
end
import_upload_path = Gitlab::ImportExport.import_upload_path(filename: project_params[:file].original_filename)
FileUtils.mkdir_p(File.dirname(import_upload_path))
FileUtils.copy_entry(project_params[:file].path, import_upload_path)
@project = Gitlab::ImportExport::ProjectCreator.new(project_params[:namespace_id],
current_user,
import_upload_path,
project_params[:path]).execute
@project = ::Projects::GitlabProjectsImportService.new(current_user, project_params).execute
if @project.saved?
redirect_to(
......
......@@ -34,12 +34,11 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
if @user.two_factor_enabled?
prompt_for_two_factor(@user)
else
log_audit_event(@user, with: :ldap)
log_audit_event(@user, with: oauth['provider'])
sign_in_and_redirect(@user)
end
else
flash[:alert] = "Access denied for your LDAP account."
redirect_to new_user_session_path
fail_ldap_login
end
end
......@@ -123,9 +122,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
sign_in_and_redirect(@user)
end
else
error_message = @user.errors.full_messages.to_sentence
return redirect_to omniauth_error_path(oauth['provider'], error: error_message)
fail_login
end
end
......@@ -145,6 +142,18 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
def oauth
@oauth ||= request.env['omniauth.auth']
end
def fail_login
error_message = @user.errors.full_messages.to_sentence
return redirect_to omniauth_error_path(oauth['provider'], error: error_message)
end
def fail_ldap_login
flash[:alert] = 'Access denied for your LDAP account.'
redirect_to new_user_session_path
end
def log_audit_event(user, options = {})
AuditEventService.new(user, user, options)
......
......@@ -67,11 +67,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
@noteable = @merge_request
@commits_count = @merge_request.commits_count
if @merge_request.locked_long_ago?
@merge_request.unlock_mr
@merge_request.close
end
labels
set_pipeline_variables
......
......@@ -337,6 +337,7 @@ class ProjectsController < Projects::ApplicationController
:runners_token,
:tag_list,
:visibility_level,
:template_name,
project_feature_attributes: %i[
builds_access_level
......
......@@ -306,6 +306,10 @@ module ApplicationHelper
cookies["new_nav"] == "true"
end
def collapsed_sidebar?
cookies["sidebar_collapsed"] == "true"
end
def show_new_repo?
cookies["new_repo"] == "true" && body_data_page != 'projects:show'
end
......
......@@ -19,7 +19,8 @@ module AvatarsHelper
class: %W[avatar has-tooltip s#{avatar_size}].push(*options[:css_class]),
alt: "#{user_name}'s avatar",
title: user_name,
data: data_attributes
data: data_attributes,
lazy: true
)
end
......
......@@ -47,14 +47,6 @@ module GitlabRoutingHelper
project_pipeline_path(pipeline.project, pipeline.id, *args)
end
def milestone_path(entity, *args)
if entity.is_group_milestone?
group_milestone_path(entity.group, entity, *args)
elsif entity.is_project_milestone?
project_milestone_path(entity.project, entity, *args)
end
end
def issue_url(entity, *args)
project_issue_url(entity.project, entity, *args)
end
......@@ -67,14 +59,6 @@ module GitlabRoutingHelper
project_pipeline_url(pipeline.project, pipeline.id, *args)
end
def milestone_url(entity, *args)
if entity.is_group_milestone?
group_milestone_url(entity.group, entity, *args)
elsif entity.is_project_milestone?
project_milestone_url(entity.project, entity, *args)
end
end
def pipeline_job_url(pipeline, build, *args)
project_job_url(pipeline.project, build.id, *args)
end
......
module MilestonesRoutingHelper
def milestone_path(milestone, *args)
if milestone.is_group_milestone?
group_milestone_path(milestone.group, milestone, *args)
elsif milestone.is_project_milestone?
project_milestone_path(milestone.project, milestone, *args)
end
end
def milestone_url(milestone, *args)
if milestone.is_group_milestone?
group_milestone_url(milestone.group, milestone, *args)
elsif milestone.is_project_milestone?
project_milestone_url(milestone.project, milestone, *args)
end
end
end
......@@ -2,6 +2,7 @@ module NavHelper
def page_with_sidebar_class
class_name = page_gutter_class
class_name << 'page-with-new-sidebar' if defined?(@new_sidebar) && @new_sidebar
class_name << 'page-with-icon-sidebar' if collapsed_sidebar? && @new_sidebar
class_name
end
......
......@@ -16,6 +16,7 @@ module Issuable
include TimeTrackable
include Importable
include Editable
include AfterCommitQueue
# This object is used to gather issuable meta data for displaying
# upvotes, downvotes, notes and closing merge requests count for issues and merge requests
......
......@@ -13,9 +13,7 @@ module ConversationalDevelopmentIndex
end
def percentage_score(feature)
return 100 if leader_score(feature).zero?
100 * instance_score(feature) / leader_score(feature)
self["percentage_#{feature}"]
end
end
end
......@@ -8,6 +8,7 @@ class MergeRequest < ActiveRecord::Base
include CreatedAtFilterable
ignore_column :position
ignore_column :locked_at
belongs_to :target_project, class_name: "Project"
belongs_to :source_project, class_name: "Project"
......@@ -61,16 +62,6 @@ class MergeRequest < ActiveRecord::Base
transition locked: :opened
end
after_transition any => :locked do |merge_request, transition|
merge_request.locked_at = Time.now
merge_request.save
end
after_transition locked: (any - :locked) do |merge_request, transition|
merge_request.locked_at = nil
merge_request.save
end
state :opened
state :closed
state :merged
......@@ -392,6 +383,12 @@ class MergeRequest < ActiveRecord::Base
'Source project is not a fork of the target project'
end
def merge_ongoing?
return false unless merge_jid
Gitlab::SidekiqStatus.num_running([merge_jid]) > 0
end
def closed_without_fork?
closed? && source_project_missing?
end
......@@ -725,12 +722,6 @@ class MergeRequest < ActiveRecord::Base
end
end
def locked_long_ago?
return false unless locked?
locked_at.nil? || locked_at < (Time.now - 1.day)
end
def has_ci?
has_ci_integration = source_project.try(:ci_service)
uses_gitlab_ci = all_pipelines.any?
......
......@@ -149,7 +149,9 @@ class Milestone < ActiveRecord::Base
end
##
# Returns the String necessary to reference this Milestone in Markdown
# Returns the String necessary to reference this Milestone in Markdown. Group
# milestones only support name references, and do not support cross-project
# references.
#
# format - Symbol format to use (default: :iid, optional: :name)
#
......@@ -161,12 +163,16 @@ class Milestone < ActiveRecord::Base
# Milestone.first.to_reference(same_namespace_project) # => "gitlab-ce%1"
#
def to_reference(from_project = nil, format: :iid, full: false)
return if is_group_milestone?
return if is_group_milestone? && format != :name
format_reference = milestone_format_reference(format)
reference = "#{self.class.reference_prefix}#{format_reference}"
"#{project.to_reference(from_project, full: full)}#{reference}"
if project
"#{project.to_reference(from_project, full: full)}#{reference}"
else
reference
end
end
def reference_link_text(from_project = nil)
......
......@@ -75,6 +75,7 @@ class Project < ActiveRecord::Base
attr_accessor :new_default_branch
attr_accessor :old_path_with_namespace
attr_accessor :template_name
attr_writer :pipeline_status
alias_attribute :title, :name
......@@ -163,7 +164,7 @@ class Project < ActiveRecord::Base
has_many :todos
has_many :notification_settings, as: :source, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
has_one :import_data, class_name: 'ProjectImportData'
has_one :import_data, class_name: 'ProjectImportData', inverse_of: :project, autosave: true
has_one :project_feature
has_one :statistics, class_name: 'ProjectStatistics'
......@@ -192,6 +193,7 @@ class Project < ActiveRecord::Base
accepts_nested_attributes_for :variables, allow_destroy: true
accepts_nested_attributes_for :project_feature
accepts_nested_attributes_for :import_data
delegate :name, to: :owner, allow_nil: true, prefix: true
delegate :count, to: :forks, prefix: true
......@@ -588,8 +590,6 @@ class Project < ActiveRecord::Base
project_import_data.credentials ||= {}
project_import_data.credentials = project_import_data.credentials.merge(credentials)
end
project_import_data.save
end
def import?
......
require 'carrierwave/orm/activerecord'
class ProjectImportData < ActiveRecord::Base
belongs_to :project
belongs_to :project, inverse_of: :import_data
attr_encrypted :credentials,
key: Gitlab::Application.secrets.db_key_base,
marshal: true,
......
......@@ -130,17 +130,13 @@ class Repository
return []
end
ref ||= root_ref
args = %W(
log #{ref} --pretty=%H --skip #{offset}
--max-count #{limit} --grep=#{query} --regexp-ignore-case
)
args = args.concat(%W(-- #{path})) if path.present?
git_log_results = run_git(args).first.lines
git_log_results.map { |c| commit(c.chomp) }.compact
raw_repository.gitaly_migrate(:commits_by_message) do |is_enabled|
if is_enabled
find_commits_by_message_by_gitaly(query, ref, path, limit, offset)
else
find_commits_by_message_by_shelling_out(query, ref, path, limit, offset)
end
end
end
def find_branch(name, fresh_repo: true)
......@@ -1184,4 +1180,25 @@ class Repository
def circuit_breaker
@circuit_breaker ||= Gitlab::Git::Storage::CircuitBreaker.for_storage(project.repository_storage)
end
def find_commits_by_message_by_shelling_out(query, ref, path, limit, offset)
ref ||= root_ref
args = %W(
log #{ref} --pretty=%H --skip #{offset}
--max-count #{limit} --grep=#{query} --regexp-ignore-case
)
args = args.concat(%W(-- #{path})) if path.present?
git_log_results = run_git(args).first.lines
git_log_results.map { |c| commit(c.chomp) }.compact
end
def find_commits_by_message_by_gitaly(query, ref, path, limit, offset)
raw_repository
.gitaly_commit_client
.commits_by_message(query, revision: ref, path: path, limit: limit, offset: offset)
.map { |c| commit(c) }
end
end
......@@ -2,7 +2,6 @@ class MergeRequestEntity < IssuableEntity
include RequestAwareEntity
expose :in_progress_merge_commit_sha
expose :locked_at
expose :merge_commit_sha
expose :merge_error
expose :merge_params
......@@ -32,6 +31,7 @@ class MergeRequestEntity < IssuableEntity
expose :head_pipeline, with: PipelineDetailsEntity, as: :pipeline
# Booleans
expose :merge_ongoing?, as: :merge_ongoing
expose :work_in_progress?, as: :work_in_progress
expose :source_branch_exists?, as: :source_branch_exists
expose :mergeable_discussions_state?, as: :mergeable_discussions_state
......
......@@ -30,6 +30,7 @@ module Ci
# with StateMachines::InvalidTransition or StaleObjectError when doing run! or save method.
build.runner_id = runner.id
build.run!
register_success(build)
return Result.new(build, true)
rescue StateMachines::InvalidTransition, ActiveRecord::StaleObjectError
......@@ -46,6 +47,7 @@ module Ci
end
end
register_failure
Result.new(nil, valid)
end
......@@ -81,5 +83,27 @@ module Ci
def shared_runner_build_limits_feature_enabled?
ENV['DISABLE_SHARED_RUNNER_BUILD_MINUTES_LIMIT'].to_s != 'true'
end
def register_failure
failed_attempt_counter.increase
attempt_counter.increase
end
def register_success(job)
job_queue_duration_seconds.observe({ shared_runner: @runner.shared? }, Time.now - job.created_at)
attempt_counter.increase
end
def failed_attempt_counter
@failed_attempt_counter ||= Gitlab::Metrics.counter(:job_register_attempts_failed_total, "Counts the times a runner tries to register a job")
end
def attempt_counter
@attempt_counter ||= Gitlab::Metrics.counter(:job_register_attempts_total, "Counts the times a runner tries to register a job")
end
def job_queue_duration_seconds
@job_queue_duration_seconds ||= Gitlab::Metrics.histogram(:job_queue_duration_seconds, 'Request handling execution time')
end
end
end
......@@ -2,11 +2,8 @@ class IssuableBaseService < BaseService
private
def create_milestone_note(issuable)
milestone = issuable.milestone
return if milestone && milestone.is_group_milestone?
SystemNoteService.change_milestone(
issuable, issuable.project, current_user, milestone)
issuable, issuable.project, current_user, issuable.milestone)
end
def create_labels_note(issuable, old_labels)
......@@ -182,7 +179,6 @@ class IssuableBaseService < BaseService
if params.present? && create_issuable(issuable, params, label_ids: label_ids)
after_create(issuable)
issuable.create_cross_references!(current_user)
execute_hooks(issuable)
invalidate_cache_counts(issuable, users: issuable.assignees)
end
......
......@@ -15,11 +15,14 @@ module Issues
def before_create(issue)
spam_check(issue, current_user)
issue.move_to_end
user = current_user
issue.run_after_commit do
NewIssueWorker.perform_async(issue.id, user.id)
end
end
def after_create(issuable)
event_service.open_issue(issuable, current_user)
notification_service.new_issue(issuable, current_user)
todo_service.new_issue(issuable, current_user)
user_agent_detail_service.create
resolve_discussions_with_issue(issuable)
......
......@@ -16,9 +16,15 @@ module MergeRequests
create(merge_request)
end
def before_create(merge_request)
user = current_user
merge_request.run_after_commit do
NewMergeRequestWorker.perform_async(merge_request.id, user.id)
end
end
def after_create(issuable)
event_service.open_mr(issuable, current_user)
notification_service.new_merge_request(issuable, current_user)
todo_service.new_merge_request(issuable, current_user)
issuable.cache_merge_request_closes_issues!(current_user)
update_merge_requests_head_pipeline(issuable)
......
......@@ -5,7 +5,15 @@ module Projects
end
def milestones
@project.milestones.active.reorder(due_date: :asc, title: :asc).select([:iid, :title])
finder_params = {
project_ids: [@project.id],
state: :active,
order: { due_date: :asc, title: :asc }
}
finder_params[:group_ids] = [@project.group.id] if @project.group
MilestonesFinder.new(finder_params).execute.select([:iid, :title])
end
def merge_requests
......
module Projects
class CreateFromTemplateService < BaseService
def initialize(user, params)
@current_user, @params = user, params.dup
end
def execute
params[:file] = Gitlab::ProjectTemplate.find(params[:template_name]).file
GitlabProjectsImportService.new(@current_user, @params).execute
ensure
params[:file]&.close
end
end
end
......@@ -5,6 +5,10 @@ module Projects
end
def execute
if @params[:template_name]&.present?
return ::Projects::CreateFromTemplateService.new(current_user, params).execute
end
forked_from_project_id = params.delete(:forked_from_project_id)
import_data = params.delete(:import_data)
@skip_wiki = params.delete(:skip_wiki)
......
# This service is an adapter used to for the GitLab Import feature, and
# creating a project from a template.
# The latter will under the hood just import an archive supplied by GitLab.
module Projects
class GitlabProjectsImportService
attr_reader :current_user, :params
def initialize(user, params)
@current_user, @params = user, params.dup
end
def execute
FileUtils.mkdir_p(File.dirname(import_upload_path))
FileUtils.copy_entry(file.path, import_upload_path)
Gitlab::ImportExport::ProjectCreator.new(params[:namespace_id],
current_user,
import_upload_path,
params[:path]).execute
end
private
def import_upload_path
@import_upload_path ||= Gitlab::ImportExport.import_upload_path(filename: tmp_filename)
end
def tmp_filename
"#{SecureRandom.hex}_#{params[:path]}"
end
def file
params[:file]
end
end
end
......@@ -34,8 +34,12 @@ module Projects
def import_repository
raise Error, 'Blocked import URL.' if Gitlab::UrlBlocker.blocked_url?(project.import_url)
# We should return early for a GitHub import because the new GitHub
# importer fetch the project repositories for us.
return if project.github_import?
begin
if project.github_import? || project.gitea_import?
if project.gitea_import?
fetch_repository
else
clone_repository
......@@ -55,7 +59,7 @@ module Projects
end
def fetch_repository
project.create_repository
project.ensure_repository
project.repository.add_remote(project.import_type, project.import_url)
project.repository.set_remote_as_mirror(project.import_type)
project.repository.fetch_remote(project.import_type, forced: true)
......
......@@ -33,8 +33,10 @@ module Projects
success
end
rescue => e
register_failure
error(e.message)
ensure
register_attempt
build.erase_artifacts! unless build.has_expiring_artifacts?
end
......@@ -168,5 +170,21 @@ module Projects
def sha
build.sha
end
def register_attempt
pages_deployments_total_counter.increase
end
def register_failure
pages_deployments_failed_total_counter.increase
end
def pages_deployments_total_counter
@pages_deployments_total_counter ||= Gitlab::Metrics.counter(:pages_deployments_total, "Counter of GitLab Pages deployments triggered")
end
def pages_deployments_failed_total_counter
@pages_deployments_failed_total_counter ||= Gitlab::Metrics.counter(:pages_deployments_failed_total, "Counter of GitLab Pages deployments which failed")
end
end
end
class SubmitUsagePingService
URL = 'https://version.gitlab.com/usage_data'.freeze
METRICS = %w[leader_issues instance_issues percentage_issues leader_notes instance_notes
percentage_notes leader_milestones instance_milestones percentage_milestones
leader_boards instance_boards percentage_boards leader_merge_requests
instance_merge_requests percentage_merge_requests leader_ci_pipelines
instance_ci_pipelines percentage_ci_pipelines leader_environments instance_environments
percentage_environments leader_deployments instance_deployments percentage_deployments
leader_projects_prometheus_active instance_projects_prometheus_active
percentage_projects_prometheus_active leader_service_desk_issues instance_service_desk_issues
percentage_service_desk_issues].freeze
include Gitlab::CurrentSettings
def execute
......@@ -27,15 +37,7 @@ class SubmitUsagePingService
return unless response['conv_index'].present?
ConversationalDevelopmentIndex::Metric.create!(
response['conv_index'].slice(
'leader_issues', 'instance_issues', 'leader_notes', 'instance_notes',
'leader_milestones', 'instance_milestones', 'leader_boards', 'instance_boards',
'leader_merge_requests', 'instance_merge_requests', 'leader_ci_pipelines',
'instance_ci_pipelines', 'leader_environments', 'instance_environments',
'leader_deployments', 'instance_deployments', 'leader_projects_prometheus_active',
'instance_projects_prometheus_active', 'leader_service_desk_issues',
'instance_service_desk_issues'
)
response['conv_index'].slice(*METRICS)
)
end
end
......@@ -142,7 +142,8 @@ module SystemNoteService
#
# Returns the created Note object
def change_milestone(noteable, project, author, milestone)
body = milestone.nil? ? 'removed milestone' : "changed milestone to #{milestone.to_reference(project)}"
format = milestone&.is_group_milestone? ? :name : :iid
body = milestone.nil? ? 'removed milestone' : "changed milestone to #{milestone.to_reference(project, format: format)}"
create_note(NoteSummary.new(noteable, project, author, body, action: 'milestone'))
end
......
- page_title "GitLab Import"
- header_title "Projects", root_path
- content_for :page_specific_javascripts do
= webpack_bundle_tag 'project_import_gl'
%h3.page-title
= icon('gitlab')
Import an exported GitLab project
%hr
= form_tag import_gitlab_project_path, class: 'form-horizontal', multipart: true do
%p
Project will be imported as
%strong
#{@namespace.name}/#{@path}
= form_tag import_gitlab_project_path, class: 'new_project', multipart: true do
.row
.form-group.col-xs-12.col-sm-6
= label_tag :namespace_id, 'Project path', class: 'label-light'
.form-group
.input-group
- if current_user.can_select_namespace?
.input-group-addon
= root_url
= select_tag :namespace_id, namespaces_options(namespace_id_from(params) || :current_user, display_path: true, extra_group: namespace_id_from(params)), class: 'select2 js-select-namespace', tabindex: 1
%p
To move or copy an entire GitLab project from another GitLab installation to this one, navigate to the original project's settings page, generate an export file, and upload it here.
.form-group
= hidden_field_tag :namespace_id, @namespace.id
= hidden_field_tag :path, @path
= label_tag :file, class: 'control-label' do
%span GitLab project export
.col-sm-10
= file_field_tag :file, class: ''
- else
.input-group-addon.static-namespace
#{root_url}#{current_user.username}/
= hidden_field_tag :namespace_id, value: current_user.namespace_id
.form-group.col-xs-12.col-sm-6.project-path
= label_tag :path, 'Project name', class: 'label-light'
= text_field_tag :path, nil, placeholder: "my-awesome-project", class: "js-path-name form-control", tabindex: 2, autofocus: true, required: true
.form-actions
= submit_tag 'Import project', class: 'btn btn-create'
.row
.form-group.col-md-12
To move or copy an entire GitLab project from another GitLab installation to this one, navigate to the original project's settings page, generate an export file, and upload it here.
.row
.form-group.col-sm-12
= hidden_field_tag :namespace_id, @namespace.id
= hidden_field_tag :path, @path
= label_tag :file, 'GitLab project export', class: 'label-light'
.form-group
= file_field_tag :file, class: ''
.row
.form-actions
= submit_tag 'Import project', class: 'btn btn-create'
= link_to 'Cancel', new_project_path, class: 'btn btn-cancel'
.nav-sidebar
.nav-sidebar{ class: ("sidebar-icons-only" if collapsed_sidebar?) }
.context-header
= link_to admin_root_path, title: 'Admin Overview' do
.avatar-container.s40.settings-avatar
= icon('wrench')
.project-title Admin Area
= button_tag class: 'close-nav-button', type: 'button' do
%span.sr-only Close sidebar
= icon ('times')
%ul.sidebar-top-level-items
= nav_link(controller: %w(dashboard admin projects users groups jobs runners cohorts), html_options: {class: 'home'}) do
= link_to admin_root_path, title: 'Overview', class: 'shortcuts-tree' do
......
.nav-sidebar
.nav-sidebar{ class: ("sidebar-icons-only" if collapsed_sidebar?) }
.context-header
= link_to group_path(@group), title: @group.name do
.avatar-container.s40.group-avatar
= image_tag group_icon(@group), class: "avatar s40 avatar-tile"
.group-title
= @group.name
= button_tag class: 'close-nav-button', type: 'button' do
%span.sr-only Close sidebar
= icon ('times')
%ul.sidebar-top-level-items
= nav_link(path: ['groups#show', 'groups#activity', 'groups#subgroups'], html_options: { class: 'home' }) do
= link_to group_path(@group), title: 'Group overview' do
......
.nav-sidebar
.nav-sidebar{ class: ("sidebar-icons-only" if collapsed_sidebar?) }
.context-header
= link_to profile_path, title: 'Profile Settings' do
.avatar-container.s40.settings-avatar
= icon('user')
.project-title User Settings
= button_tag class: 'close-nav-button', type: 'button' do
%span.sr-only Close sidebar
= icon ('times')
%ul.sidebar-top-level-items
= nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
= link_to profile_path, title: 'Profile Settings' do
......
.nav-sidebar
.nav-sidebar{ class: ("sidebar-icons-only" if collapsed_sidebar?) }
- can_edit = can?(current_user, :admin_project, @project)
.context-header
= link_to project_path(@project), title: @project.name do
......@@ -6,9 +6,6 @@
= project_icon(@project, alt: @project.name, class: 'avatar s40 avatar-tile')
.project-title
= @project.name
= button_tag class: 'close-nav-button', type: 'button' do
%span.sr-only Close sidebar
= icon ('times')
%ul.sidebar-top-level-items
= nav_link(path: ['projects#show', 'projects#activity', 'cycle_analytics#show'], html_options: { class: 'home' }) do
= link_to project_path(@project), title: 'Project overview', class: 'shortcuts-project' do
......@@ -219,7 +216,7 @@
= link_to project_settings_members_path(@project), title: 'Members', class: 'shortcuts-tree' do
.nav-icon-container
= custom_icon('members')
%span
%span.nav-item-name
Members
= render 'shared/sidebar_toggle_button'
......
- referenced_users = local_assigns.fetch(:referenced_users, nil)
- if defined?(@issue) && @issue.confidential?
%li.confidential-issue-warning
= confidential_icon(@issue)
%span This is a confidential issue. Your comment will not be visible to the public.
- else
%li.confidential-issue-warning.not-confidential
.md-area
.md-header
%ul.nav-links.clearfix
......@@ -10,11 +17,6 @@
%a.js-md-preview-button{ href: "#md-preview-holder", tabindex: -1 }
Preview
- if defined?(@issue) && @issue.confidential?
%li.confidential-issue-warning
= icon('warning')
%span This is a confidential issue. Your comment will not be visible to the public.
%li.pull-right
.toolbar-group
= markdown_toolbar_button({ icon: "bold fw", data: { "md-tag" => "**" }, title: "Add bold text" })
......
.project-templates-buttons.import-buttons{ data: { toggle: "buttons" } }
.btn.blank-option.active
%input{ type: "radio", autocomplete: "off", name: "project_templates", id: "blank", checked: "true" }
= icon('file-o', class: 'btn-template-icon')
Blank
- Gitlab::ProjectTemplate.all.each do |template|
.btn
%input{ type: "radio", autocomplete: "off", name: "project_templates", id: template.name }
= custom_icon(template.logo)
= template.title
......@@ -6,8 +6,16 @@
%i.fa.fa-fw.board-title-expandable-toggle{ "v-if": "list.isExpandable",
":class": "{ \"fa-caret-down\": list.isExpanded, \"fa-caret-right\": !list.isExpanded && list.position === -1, \"fa-caret-left\": !list.isExpanded && list.position !== -1 }",
"aria-hidden": "true" }
%span.has-tooltip{ ":title" => '(list.label ? list.label.description : "")',
data: { container: "body", placement: "bottom" } }
%span.has-tooltip{ "v-if": "list.type !== \"label\"",
":title" => '(list.label ? list.label.description : "")' }
{{ list.title }}
%span.has-tooltip{ "v-if": "list.type === \"label\"",
":title" => '(list.label ? list.label.description : "")',
data: { container: "body", placement: "bottom" },
class: "label color-label title",
":style" => "{ backgroundColor: (list.label && list.label.color ? list.label.color : null), color: (list.label && list.label.color ? list.label.text_color : \"#2e2e2e\") }" }
{{ list.title }}
.issue-count-badge.pull-right.clearfix{ "v-if" => 'list.type !== "blank"' }
%span.issue-count-badge-count.pull-left{ ":class" => '{ "has-btn": list.type !== "closed" && !disabled }' }
......
......@@ -19,7 +19,8 @@
= icon('angle-double-left')
.issuable-meta
= confidential_icon(@issue)
- if @issue.confidential
= icon('eye-slash', class: 'is-confidential')
= issuable_meta(@issue, @project, "Issue")
.issuable-actions
......
- builds = @build.pipeline.builds.to_a
%aside.right-sidebar.right-sidebar-expanded.build-sidebar.js-build-sidebar.js-right-sidebar{ data: { "offset-top" => "101", "spy" => "affix" } }
.blocks-container
.block
%strong
= @build.name
%a.gutter-toggle.pull-right.visible-xs-block.visible-sm-block.js-sidebar-build-toggle{ href: "#", 'aria-label': 'Toggle Sidebar', role: 'button' }
= icon('angle-double-right')
#js-details-block-vue
- if can?(current_user, :read_build, @project) && (@build.artifacts? || @build.artifacts_expired?)
.sidebar-container
.blocks-container
.block
.title
Job artifacts
- if @build.artifacts_expired?
%p.build-detail-row
The artifacts were removed
#{time_ago_with_tooltip(@build.artifacts_expire_at)}
- elsif @build.has_expiring_artifacts?
%p.build-detail-row
The artifacts will be removed in
%span.js-artifacts-remove= @build.artifacts_expire_at
%strong
= @build.name
%a.gutter-toggle.pull-right.visible-xs-block.visible-sm-block.js-sidebar-build-toggle{ href: "#", 'aria-label': 'Toggle Sidebar', role: 'button' }
= icon('angle-double-right')
- if @build.artifacts?
.btn-group.btn-group-justified{ role: :group }
- if @build.has_expiring_artifacts? && can?(current_user, :update_build, @build)
= link_to keep_project_job_artifacts_path(@project, @build), class: 'btn btn-sm btn-default', method: :post do
Keep
#js-details-block-vue
= link_to download_project_job_artifacts_path(@project, @build), rel: 'nofollow', download: '', class: 'btn btn-sm btn-default' do
Download
- if can?(current_user, :read_build, @project) && (@build.artifacts? || @build.artifacts_expired?)
.block
.title
Job artifacts
- if @build.artifacts_expired?
%p.build-detail-row
The artifacts were removed
#{time_ago_with_tooltip(@build.artifacts_expire_at)}
- elsif @build.has_expiring_artifacts?
%p.build-detail-row
The artifacts will be removed in
%span.js-artifacts-remove= @build.artifacts_expire_at
- if @build.artifacts_metadata?
= link_to browse_project_job_artifacts_path(@project, @build), class: 'btn btn-sm btn-default' do
Browse
- if @build.artifacts?
.btn-group.btn-group-justified{ role: :group }
- if @build.has_expiring_artifacts? && can?(current_user, :update_build, @build)
= link_to keep_project_job_artifacts_path(@project, @build), class: 'btn btn-sm btn-default', method: :post do
Keep
- if @build.trigger_request
.build-widget.block
%h4.title
Trigger
= link_to download_project_job_artifacts_path(@project, @build), rel: 'nofollow', download: '', class: 'btn btn-sm btn-default' do
Download
%p
%span.build-light-text Token:
#{@build.trigger_request.trigger.short_token}
- if @build.artifacts_metadata?
= link_to browse_project_job_artifacts_path(@project, @build), class: 'btn btn-sm btn-default' do
Browse
- if @build.trigger_request
.build-widget.block
%h4.title
Trigger
- if @build.trigger_request.variables
%p
%button.btn.group.btn-group-justified.reveal-variables Reveal Variables
%span.build-light-text Token:
#{@build.trigger_request.trigger.short_token}
- if @build.trigger_request.variables
%p
%button.btn.group.btn-group-justified.reveal-variables Reveal Variables
- @build.trigger_request.variables.each do |key, value|
.hide.js-build
.js-build-variable.trigger-build-variable= key
.js-build-value.trigger-build-value= value
%dl.js-build-variables.trigger-build-variables.hide
- @build.trigger_request.variables.each do |key, value|
%dt.js-build-variable.trigger-build-variable= key
%dd.js-build-value.trigger-build-value= value
%div{ class: (@build.pipeline.stages_count > 1 ? "block" : "block-last") }
%p
Commit
= link_to @build.pipeline.short_sha, project_commit_path(@project, @build.pipeline.sha), class: 'commit-sha link-commit'
= clipboard_button(text: @build.pipeline.short_sha, title: "Copy commit SHA to clipboard")
- if @build.merge_request
in
= link_to "#{@build.merge_request.to_reference}", merge_request_path(@build.merge_request), class: 'link-commit'
%div{ class: (@build.pipeline.stages_count > 1 ? "block" : "block-last") }
%p
Commit
= link_to @build.pipeline.short_sha, project_commit_path(@project, @build.pipeline.sha), class: 'commit-sha link-commit'
= clipboard_button(text: @build.pipeline.short_sha, title: "Copy commit SHA to clipboard")
- if @build.merge_request
in
= link_to "#{@build.merge_request.to_reference}", merge_request_path(@build.merge_request), class: 'link-commit'
%p.build-light-text.append-bottom-0
#{@build.pipeline.git_commit_title}
%p.build-light-text.append-bottom-0
#{@build.pipeline.git_commit_title}
- if @build.pipeline.stages_count > 1
.dropdown.build-dropdown
%div
%span{ class: "ci-status-icon-#{@build.pipeline.status}" }
= ci_icon_for_status(@build.pipeline.status)
Pipeline
= link_to "##{@build.pipeline.id}", project_pipeline_path(@project, @build.pipeline), class: 'link-commit'
from
= link_to "#{@build.pipeline.ref}", project_branch_path(@project, @build.pipeline.ref), class: 'link-commit'
%button.dropdown-menu-toggle{ type: 'button', 'data-toggle' => 'dropdown' }
%span.stage-selection More
= icon('chevron-down')
%ul.dropdown-menu
- @build.pipeline.legacy_stages.each do |stage|
%li
%a.stage-item= stage.name
- if @build.pipeline.stages_count > 1
.block-last.dropdown.build-dropdown
%div
%span{ class: "ci-status-icon-#{@build.pipeline.status}" }
= ci_icon_for_status(@build.pipeline.status)
Pipeline
= link_to "##{@build.pipeline.id}", project_pipeline_path(@project, @build.pipeline), class: 'link-commit'
from
= link_to "#{@build.pipeline.ref}", project_branch_path(@project, @build.pipeline.ref), class: 'link-commit'
%button.dropdown-menu-toggle{ type: 'button', 'data-toggle' => 'dropdown' }
%span.stage-selection More
= icon('chevron-down')
%ul.dropdown-menu
- @build.pipeline.legacy_stages.each do |stage|
%li
%a.stage-item= stage.name
.builds-container
- HasStatus::ORDERED_STATUSES.each do |build_status|
- builds.select{|build| build.status == build_status}.each do |build|
.build-job{ class: sidebar_build_class(build, @build), data: { stage: build.stage } }
= link_to project_job_path(@project, build) do
= icon('arrow-right')
%span{ class: "ci-status-icon-#{build.status}" }
= ci_icon_for_status(build.status)
%span
- if build.name
= build.name
- else
= build.id
- if build.retried?
%i.fa.fa-refresh.has-tooltip{ data: { container: 'body', placement: 'bottom' }, title: 'Job was retried' }
.builds-container
- HasStatus::ORDERED_STATUSES.each do |build_status|
- builds.select{|build| build.status == build_status}.each do |build|
.build-job{ class: sidebar_build_class(build, @build), data: { stage: build.stage } }
= link_to project_job_path(@project, build) do
= icon('arrow-right')
%span{ class: "ci-status-icon-#{build.status}" }
= ci_icon_for_status(build.status)
%span
- if build.name
= build.name
- else
= build.id
- if build.retried?
%i.fa.fa-refresh.has-tooltip{ data: { container: 'body', placement: 'bottom' }, title: 'Job was retried' }
......@@ -17,8 +17,68 @@
- if import_sources_enabled?
%p
Create or Import your project from popular Git services
.col-lg-9
.col-lg-9.js-toggle-container
= form_for @project, html: { class: 'new_project' } do |f|
.create-project-options
.first-column
.project-template
.form-group
= f.label :template_project, class: 'label-light' do
Create from template
= link_to icon('question-circle'), help_page_path("public_access/public_access"), aria: { label: "What’s included in a template?" }, title: "What’s included in a template?", class: 'has-tooltip', data: { placement: 'top'}
%div
= render 'project_templates', f: f
.second-column
- if import_sources_enabled?
.project-import
.form-group.clearfix
= f.label :visibility_level, class: 'label-light' do #the label here seems wrong
Import project from
.col-sm-12.import-buttons
%div
- if github_import_enabled?
= link_to new_import_github_path, class: 'btn import_github' do
= icon('github', text: 'GitHub')
%div
- if bitbucket_import_enabled?
= link_to status_import_bitbucket_path, class: "btn import_bitbucket #{'how_to_import_link' unless bitbucket_import_configured?}" do
= icon('bitbucket', text: 'Bitbucket')
- unless bitbucket_import_configured?
= render 'bitbucket_import_modal'
%div
- if gitlab_import_enabled?
= link_to status_import_gitlab_path, class: "btn import_gitlab #{'how_to_import_link' unless gitlab_import_configured?}" do
= icon('gitlab', text: 'GitLab.com')
- unless gitlab_import_configured?
= render 'gitlab_import_modal'
%div
- if google_code_import_enabled?
= link_to new_import_google_code_path, class: 'btn import_google_code' do
= icon('google', text: 'Google Code')
%div
- if fogbugz_import_enabled?
= link_to new_import_fogbugz_path, class: 'btn import_fogbugz' do
= icon('bug', text: 'Fogbugz')
%div
- if gitea_import_enabled?
= link_to new_import_gitea_url, class: 'btn import_gitea' do
= custom_icon('go_logo')
Gitea
%div
- if git_import_enabled?
%button.btn.js-toggle-button.import_git{ type: "button" }
= icon('git', text: 'Repo by URL')
.import_gitlab_project.has-tooltip{ data: { container: 'body' } }
= link_to new_import_gitlab_project_path, class: 'btn btn_import_gitlab_project project-submit' do
= icon('gitlab', text: 'GitLab export')
.row
.col-lg-12
.js-toggle-content.hide
%hr
= render "shared/import_form", f: f
%hr
.row
.form-group.col-xs-12.col-sm-6
= f.label :namespace_id, class: 'label-light' do
......@@ -45,53 +105,6 @@
Want to house several dependent projects under the same namespace?
= link_to "Create a group", new_group_path
- if import_sources_enabled?
.project-import.js-toggle-container
.form-group.clearfix
= f.label :visibility_level, class: 'label-light' do
Import project from
.col-sm-12.import-buttons
%div
- if github_import_enabled?
= link_to new_import_github_path, class: 'btn import_github' do
= icon('github', text: 'GitHub')
%div
- if bitbucket_import_enabled?
= link_to status_import_bitbucket_path, class: "btn import_bitbucket #{'how_to_import_link' unless bitbucket_import_configured?}" do
= icon('bitbucket', text: 'Bitbucket')
- unless bitbucket_import_configured?
= render 'bitbucket_import_modal'
%div
- if gitlab_import_enabled?
= link_to status_import_gitlab_path, class: "btn import_gitlab #{'how_to_import_link' unless gitlab_import_configured?}" do
= icon('gitlab', text: 'GitLab.com')
- unless gitlab_import_configured?
= render 'gitlab_import_modal'
%div
- if google_code_import_enabled?
= link_to new_import_google_code_path, class: 'btn import_google_code' do
= icon('google', text: 'Google Code')
%div
- if fogbugz_import_enabled?
= link_to new_import_fogbugz_path, class: 'btn import_fogbugz' do
= icon('bug', text: 'FogBugz')
%div
- if gitea_import_enabled?
= link_to new_import_gitea_url, class: 'btn import_gitea' do
= custom_icon('go_logo')
Gitea
%div
- if git_import_enabled?
%button.btn.js-toggle-button.import_git{ type: "button" }
= icon('git', text: 'Repo by URL')
.import_gitlab_project.has-tooltip{ data: { container: 'body' } }
- if gitlab_project_import_enabled?
= link_to new_import_gitlab_project_path, class: 'btn btn_import_gitlab_project project-submit' do
= icon('gitlab', text: 'GitLab export')
.js-toggle-content.hide
= render "shared/import_form", f: f
.form-group
= f.label :description, class: 'label-light' do
Project description
......
%aside.right-sidebar.right-sidebar-expanded.wiki-sidebar.js-wiki-sidebar.js-right-sidebar{ data: { "offset-top" => "50", "spy" => "affix" } }
.block.wiki-sidebar-header.append-bottom-default
%a.gutter-toggle.pull-right.visible-xs-block.visible-sm-block.js-sidebar-wiki-toggle{ href: "#" }
= icon('angle-double-right')
.sidebar-container
.block.wiki-sidebar-header.append-bottom-default
%a.gutter-toggle.pull-right.visible-xs-block.visible-sm-block.js-sidebar-wiki-toggle{ href: "#" }
= icon('angle-double-right')
- git_access_url = project_wikis_git_access_path(@project)
= link_to git_access_url, class: active_nav_link?(path: 'wikis#git_access') ? 'active' : '' do
= succeed '&nbsp;' do
= icon('cloud-download')
Clone repository
- git_access_url = project_wikis_git_access_path(@project)
= link_to git_access_url, class: active_nav_link?(path: 'wikis#git_access') ? 'active' : '' do
= succeed '&nbsp;' do
= icon('cloud-download')
Clone repository
.blocks-container
.block.block-first
%ul.wiki-pages
= render @sidebar_wiki_entries, context: 'sidebar'
.blocks-container
.block.block-first
%ul.wiki-pages
= render @sidebar_wiki_entries, context: 'sidebar'
.block
= link_to project_wikis_pages_path(@project), class: 'btn btn-block' do
More Pages
.block
= link_to project_wikis_pages_path(@project), class: 'btn btn-block' do
More Pages
= render 'projects/wikis/new'
.form-group.import-url-data
= f.label :import_url, class: 'control-label' do
= f.label :import_url, class: 'label-light' do
%span Git repository URL
.col-sm-10
= f.text_field :import_url, autocomplete: 'off', class: 'form-control', placeholder: 'https://username:password@gitlab.company.com/group/project.git'
.well.prepend-top-20
%ul
%li
The repository must be accessible over <code>http://</code>, <code>https://</code> or <code>git://</code>.
%li
If your HTTP repository is not publicly accessible, add authentication information to the URL: <code>https://username:password@gitlab.company.com/group/project.git</code>.
%li
The import will time out after 15 minutes. For repositories that take longer, use a clone/push combination.
%li
To migrate an SVN repository, check out #{link_to "this document", help_page_path('workflow/importing/migrating_from_svn')}.
= f.text_field :import_url, autocomplete: 'off', class: 'form-control', placeholder: 'https://username:password@gitlab.company.com/group/project.git'
.well.prepend-top-20
%ul
%li
The repository must be accessible over <code>http://</code>, <code>https://</code> or <code>git://</code>.
%li
If your HTTP repository is not publicly accessible, add authentication information to the URL: <code>https://username:password@gitlab.company.com/group/project.git</code>.
%li
The import will time out after 15 minutes. For repositories that take longer, use a clone/push combination.
%li
To migrate an SVN repository, check out #{link_to "this document", help_page_path('workflow/importing/migrating_from_svn')}.
- if any_projects?(@projects)
.project-item-select-holder
.project-item-select-holder.btn-group.pull-right
%a.btn.btn-new.new-project-item-link{ href: '', data: { label: local_assigns[:label] } }
= icon('spinner spin')
= project_select_tag :project_path, class: "project-item-select", data: { include_groups: local_assigns[:include_groups], order_by: 'last_activity_at', relative_path: local_assigns[:path] }, with_feature_enabled: local_assigns[:with_feature_enabled]
%a.btn.btn-new.new-project-item-select-button
= local_assigns[:label]
%button.btn.btn-new.new-project-item-select-button
= icon('caret-down')
......@@ -2,3 +2,7 @@
= icon('angle-double-left')
= icon('angle-double-right')
%span.collapse-text Collapse sidebar
= button_tag class: 'close-nav-button', type: 'button' do
= icon ('times')
%span.collapse-text Close sidebar
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" class="btn-template-icon icon-java-spring">
<g fill="none" fill-rule="evenodd">
<rect width="32" height="32"/>
<path fill="#70AD51" d="M5.46647617,27.9932117 C6.0517027,28.4658996 6.91159892,28.3777063 7.38425926,27.7914452 C7.85922261,27.2048452 7.76991326,26.3449044 7.18398981,25.8699411 C6.59874295,25.3956543 5.74015536,25.4869934 5.26383884,26.0722403 C4.81393367,26.6267596 4.87238621,27.4284565 5.37913494,27.9159868 L5.11431334,27.6818383 C1.97157151,24.7616933 0,20.5966301 0,15.9782542 C0,7.16842834 7.16775175,0 15.9796074,0 C20.4586065,0 24.5113565,1.8565519 27.4145869,4.8362365 C28.0749348,3.93840692 28.6466499,2.93435335 29.115524,1.82069284 C31.1513712,7.93770658 32.3482517,13.0811131 31.909824,17.1311567 C31.3178113,25.4044499 24.4017495,31.9585382 15.9796074,31.9585382 C12.0682639,31.9585382 8.48438805,30.5444735 5.7042963,28.2034861 L5.46647617,27.9932117 Z M29.0471888,23.0106888 C33.0546075,17.6737787 30.8211972,9.04527781 28.9612624,3.529749 C27.3029502,6.98304378 23.2217836,9.62375882 19.6981239,10.4613722 C16.3950312,11.2482417 13.4715032,10.6021021 10.4153644,11.7780085 C3.44517575,14.457289 3.55613585,22.7698242 7.39373146,24.6365249 C7.39711439,24.6392312 7.62444728,24.7616933 7.62174094,24.7576338 C7.62309411,24.7562806 13.2658211,23.6358542 16.3862356,22.4843049 C20.9450718,20.7996058 25.9524846,16.6494275 27.5986182,11.8273993 C26.723116,16.8415779 22.4179995,21.6669891 18.093262,23.8828081 C15.7908399,25.0648038 14.0005934,25.3279957 10.2123886,26.6385428 C9.74892722,26.798217 9.38492397,26.9538318 9.38492397,26.9538318 C10.3463526,26.7948341 11.301692,26.7420604 11.301692,26.7420604 C16.6954354,26.4869875 25.1087819,28.2582896 29.0471888,23.0106888 Z"/>
</g>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="27" height="32" viewBox="0 0 27 32" class="btn-template-icon icon-node-express">
<g fill="none" fill-rule="evenodd" transform="translate(-3)">
<rect width="32" height="32"/>
<path fill="#353535" d="M4.19170065,16.2667139 C4.23142421,18.3323387 4.47969269,20.2489714 4.93651356,22.0166696 C5.39333443,23.7843677 6.09841693,25.3236323 7.05178222,26.6345096 C8.00514751,27.9453869 9.23655921,28.9781838 10.7460543,29.7329313 C12.2555493,30.4876788 14.1026668,30.8650469 16.2874623,30.8650469 C19.5050701,30.8650469 22.1764391,30.0209341 24.3016492,28.3326831 C26.4268593,26.644432 27.7476477,24.1120935 28.2640539,20.7355914 L29.4557545,20.7355914 C29.0187954,24.3107112 27.6086304,27.0813875 25.2252172,29.0477034 C22.841804,31.0140194 19.9023051,31.9971626 16.4066324,31.9971626 C14.0232191,32.0368861 11.9874175,31.659518 10.2991665,30.8650469 C8.61091547,30.0705759 7.23054269,28.9484023 6.15800673,27.4984926 C5.08547078,26.0485829 4.29101162,24.3404957 3.77460543,22.3741798 C3.25819923,20.4078639 3,18.2926164 3,16.0283738 C3,13.4860664 3.3773681,11.2218578 4.13211562,9.23568007 C4.88686314,7.24950238 5.87993709,5.57120741 7.11136726,4.20074481 C8.34279742,2.8302822 9.77282391,1.78755456 11.4014896,1.07253059 C13.0301553,0.357506621 14.6985195,0 16.4066324,0 C18.7900456,0 20.8457087,0.456814016 22.5736832,1.37045575 C24.3016578,2.28409749 25.7118228,3.4956477 26.8042206,5.00514275 C27.8966183,6.51463779 28.6910775,8.24258646 29.1876219,10.1890406 C29.6841663,12.1354947 29.8927118,14.1613656 29.8132647,16.2667139 L4.19170065,16.2667139 Z M28.6215641,15.0750133 C28.6215641,13.2080062 28.3633648,11.4304039 27.8469586,9.74215285 C27.3305524,8.05390181 26.5658855,6.57422163 25.5529349,5.30306791 C24.5399843,4.03191419 23.2787803,3.0289095 21.7692853,2.29402376 C20.2597903,1.55913801 18.5119801,1.19170065 16.5258024,1.19170065 C14.8574132,1.19170065 13.2982871,1.50948432 11.8483774,2.14506118 C10.3984676,2.78063804 9.12733299,3.70419681 8.03493526,4.9157652 C6.94253754,6.12733359 6.05870172,7.58715229 5.38340131,9.2952651 C4.70810089,11.0033779 4.31087132,12.9299414 4.19170065,15.0750133 L28.6215641,15.0750133 Z"/>
</g>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="20" viewBox="0 0 32 20" class="btn-template-icon icon-rails">
<g fill="none" fill-rule="evenodd" transform="translate(0 -6)">
<rect width="32" height="32"/>
<path fill="#C00" fill-rule="nonzero" d="M0.984615385,25.636044 C0.984615385,25.636044 1.40659341,21.4725275 4.36043956,16.5494505 C7.31428571,11.6263736 12.3498901,7.8989011 16.4430769,7.53318681 C24.5872527,6.71736264 31.9015385,14.0175824 31.9015385,14.0175824 C31.9015385,14.0175824 31.6624176,14.1863736 31.4092308,14.3973626 C23.4197802,8.48967033 18.5389011,11.2747253 17.0057143,12.0202198 C9.97274725,15.9446154 12.0967033,25.636044 12.0967033,25.636044 L0.984615385,25.636044 Z M24.1371429,8.32087912 C23.687033,8.13802198 23.2369231,7.96923077 22.7727473,7.81450549 L22.829011,6.88615385 C23.7151648,7.13934066 24.0668132,7.30813187 24.1934066,7.37846154 L24.1371429,8.32087912 Z M22.8008791,11.3028571 C23.250989,11.330989 23.7151648,11.3872527 24.1934066,11.4857143 L24.1371429,12.3578022 C23.672967,12.2593407 23.2087912,12.2030769 22.7446154,12.189011 L22.8008791,11.3028571 Z M17.5964835,6.91428571 C17.1885714,6.91428571 16.7806593,6.92835165 16.3727473,6.97054945 L16.1054945,6.14065934 C16.5696703,6.0843956 17.0197802,6.05626374 17.4558242,6.05626374 L17.7371429,6.91428571 C17.6949451,6.91428571 17.6386813,6.91428571 17.5964835,6.91428571 Z M18.2716484,12.0905495 C18.6232967,11.9358242 19.0312088,11.7810989 19.5094505,11.6404396 L19.8189011,12.5687912 C19.410989,12.6953846 19.0030769,12.8641758 18.5951648,13.0610989 L18.2716484,12.0905495 Z M11.8857143,8.39120879 C11.52,8.57406593 11.1683516,8.78505495 10.8026374,9.01010989 L10.1556044,8.02549451 C10.5353846,7.80043956 10.9010989,7.60351648 11.2527473,7.42065934 L11.8857143,8.39120879 Z M14.7692308,14.7208791 C15.0224176,14.3973626 15.3178022,14.0738462 15.6413187,13.7784615 L16.2742857,14.7349451 C15.9648352,15.0584615 15.6835165,15.381978 15.4443956,15.7336264 L14.7692308,14.7208791 Z M12.7296703,19.2501099 C12.8421978,18.7437363 12.9687912,18.2232967 13.1516484,17.7028571 L14.1643956,18.5046154 C14.0237363,19.0531868 13.9252747,19.6017582 13.869011,20.1503297 L12.7296703,19.2501099 Z M6.56879121,12.5687912 C6.23120879,12.9204396 5.90769231,13.3002198 5.61230769,13.68 L4.52923077,12.7516484 C4.85274725,12.4 5.2043956,12.0483516 5.57010989,11.6967033 L6.56879121,12.5687912 Z M2.32087912,18.8562637 C2.09582418,19.3767033 1.80043956,20.0659341 1.61758242,20.5441758 L0,19.9534066 C0.140659341,19.5736264 0.436043956,18.8703297 0.703296703,18.2654945 L2.32087912,18.8562637 Z M12.5186813,22.8228571 L14.0378022,23.3714286 C14.1221978,24.0325275 14.2487912,24.6514286 14.3753846,25.2 L12.6874725,24.5951648 C12.6171429,24.1731868 12.5468132,23.5683516 12.5186813,22.8228571 Z"/>
</g>
</svg>
......@@ -115,6 +115,10 @@
- if can? current_user, :admin_label, @project and @project
= render partial: "shared/issuable/label_page_create"
- if issuable.has_attribute?(:confidential)
%script#js-confidential-issue-data{ type: "application/json" }= { is_confidential: @issue.confidential, is_editable: can_edit_issuable }.to_json.html_safe
#js-confidential-entry-point
= render "shared/issuable/participants", participants: issuable.participants(current_user)
- if current_user
- subscribed = issuable.subscribed?(current_user, @project)
......
......@@ -3,7 +3,8 @@
%li.filter-dropdown-item{ class: ('js-current-user' if user == current_user) }
%button.btn.btn-link.dropdown-user{ type: :button }
= user_avatar_without_link(user: user, lazy: avatar[:lazy], url: avatar[:url], size: 30)
.avatar-container.s40
= user_avatar_without_link(user: user, lazy: avatar[:lazy], url: avatar[:url], size: 40).gsub('/images/{{avatar_url}}','{{avatar_url}}').html_safe
.dropdown-user-details
%span
= user.name
......
module NewIssuable
attr_reader :issuable, :user
def ensure_objects_found(issuable_id, user_id)
@issuable = issuable_class.find_by(id: issuable_id)
unless @issuable
log_error(issuable_class, issuable_id)
return false
end
@user = User.find_by(id: user_id)
unless @user
log_error(User, user_id)
return false
end
true
end
def log_error(record_class, record_id)
Rails.logger.error("#{self.class}: couldn't find #{record_class} with ID=#{record_id}, skipping job")
end
end
......@@ -7,6 +7,8 @@ class MergeWorker
current_user = User.find(current_user_id)
merge_request = MergeRequest.find(merge_request_id)
merge_request.update_column(:merge_jid, jid)
MergeRequests::MergeService.new(merge_request.target_project, current_user, params)
.execute(merge_request)
end
......
class NewIssueWorker
include Sidekiq::Worker
include DedicatedSidekiqQueue
include NewIssuable
def perform(issue_id, user_id)
return unless ensure_objects_found(issue_id, user_id)
EventCreateService.new.open_issue(issuable, user)
NotificationService.new.new_issue(issuable, user)
issuable.create_cross_references!(user)
end
def issuable_class
Issue
end
end
class NewMergeRequestWorker
include Sidekiq::Worker
include DedicatedSidekiqQueue
include NewIssuable
def perform(merge_request_id, user_id)
return unless ensure_objects_found(merge_request_id, user_id)
EventCreateService.new.open_mr(issuable, user)
NotificationService.new.new_merge_request(issuable, user)
issuable.create_cross_references!(user)
end
def issuable_class
MergeRequest
end
end
class StuckMergeJobsWorker
include Sidekiq::Worker
include CronjobQueue
def perform
stuck_merge_requests.find_in_batches(batch_size: 100) do |group|
jids = group.map(&:merge_jid)
# Find the jobs that aren't currently running or that exceeded the threshold.
completed_jids = Gitlab::SidekiqStatus.completed_jids(jids)
if completed_jids.any?
completed_ids = group.select { |merge_request| completed_jids.include?(merge_request.merge_jid) }.map(&:id)
apply_current_state!(completed_jids, completed_ids)
end
end
end
private
def apply_current_state!(completed_jids, completed_ids)
merge_requests = MergeRequest.where(id: completed_ids)
merge_requests.where.not(merge_commit_sha: nil).update_all(state: :merged)
merge_requests.where(merge_commit_sha: nil).update_all(state: :opened)
Rails.logger.info("Updated state of locked merge jobs. JIDs: #{completed_jids.join(', ')}")
end
def stuck_merge_requests
MergeRequest.select('id, merge_jid').with_state(:locked).where.not(merge_jid: nil).reorder(nil)
end
end
---
title: Unlock stuck merge request and set the proper state
merge_request: 13207
author:
---
title: Move some code from services to workers in order to improve performance
merge_request: 13326
author:
---
title: Update confidential issue UI - add confidential visibility and settings to
sidebar
merge_request:
author:
---
title: Improve mobile sidebar
merge_request:
author:
---
title: Store & use ConvDev percentages returned by the Version app
merge_request:
author:
---
title: Reduce memory usage of the GitHub importer
merge_request: 12886
author:
---
title: Support Markdown references, autocomplete, and quick actions for group milestones
merge_request:
author:
---
title: Cache recent projects for group-level new resource creation.
merge_request: !13058
author:
---
title: Add Prometheus metrics exporter to Sidekiq
merge_request: 13082
author:
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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