Commit 25521a0a authored by Ruben Davila's avatar Ruben Davila

Merge remote-tracking branch 'ce/8-11-stable' into 8-11-stable-ee

parents 4894540a 4bb1db90
Please view this file on the master branch, on stable branches it's out of date.
v 8.11.0 (unreleased)
- Use test coverage value from the latest successful pipeline in badge. !5862
- Add test coverage report badge. !5708
- Remove the http_parser.rb dependency by removing the tinder gem. !5758 (tbalthazar)
- Add Koding (online IDE) integration
- Ability to specify branches for Pivotal Tracker integration (Egor Lynko)
- Fix don't pass a local variable called `i` to a partial. !20510 (herminiotorres)
- Fix rename `add_users_into_project` and `projects_ids`. !20512 (herminiotorres)
- Fix adding line comments on the initial commit to a repo !5900
- Fix the title of the toggle dropdown button. !5515 (herminiotorres)
- Rename `markdown_preview` routes to `preview_markdown`. (Christopher Bartz)
- Update to Ruby 2.3.1. !4948
......@@ -17,6 +20,7 @@ v 8.11.0 (unreleased)
- API: Endpoints for enabling and disabling deploy keys
- API: List access requests, request access, approve, and deny access requests to a project or a group. !4833
- Use long options for curl examples in documentation !5703 (winniehell)
- Added tooltip listing label names to the labels value in the collapsed issuable sidebar
- Remove magic comments (`# encoding: UTF-8`) from Ruby files. !5456 (winniehell)
- GitLab Performance Monitoring can now track custom events such as the number of tags pushed to a repository
- Add support for relative links starting with ./ or / to RelativeLinkFilter (winniehell)
......@@ -24,6 +28,8 @@ v 8.11.0 (unreleased)
- Ignore URLs starting with // in Markdown links !5677 (winniehell)
- Fix CI status icon link underline (ClemMakesApps)
- The Repository class is now instrumented
- Fix commit mention font inconsistency (ClemMakesApps)
- Do not escape URI when extracting path !5878 (winniehell)
- Fix filter label tooltip HTML rendering (ClemMakesApps)
- Cache the commit author in RequestStore to avoid extra lookups in PostReceive
- Expand commit message width in repo view (ClemMakesApps)
......@@ -33,6 +39,7 @@ v 8.11.0 (unreleased)
- API: Add deployment endpoints
- API: Add Play endpoint on Builds
- Fix of 'Commits being passed to custom hooks are already reachable when using the UI'
- Show wall clock time when showing a pipeline. !5734
- Show member roles to all users on members page
- Project.visible_to_user is instrumented again
- Fix awardable button mutuality loading spinners (ClemMakesApps)
......@@ -90,6 +97,7 @@ v 8.11.0 (unreleased)
- The overhead of instrumented method calls has been reduced
- Remove `search_id` of labels dropdown filter to fix 'Missleading URI for labels in Merge Requests and Issues view'. !5368 (Scott Le)
- Load project invited groups and members eagerly in `ProjectTeam#fetch_members`
- Add pipeline events hook
- Bump gitlab_git to speedup DiffCollection iterations
- Rewrite description of a blocked user in admin settings. (Elias Werberich)
- Make branches sortable without push permission !5462 (winniehell)
......@@ -120,9 +128,11 @@ v 8.11.0 (unreleased)
- Change requests_profiles resource constraint to catch virtually any file
- Bump gitlab_git to lazy load compare commits
- Reduce number of queries made for merge_requests/:id/diffs
- Add the option to set the expiration date for the project membership when giving a user access to a project. !5599 (Adam Niedzielski)
- Sensible state specific default sort order for issues and merge requests !5453 (tomb0y)
- Fix bug where destroying a namespace would not always destroy projects
- Fix RequestProfiler::Middleware error when code is reloaded in development
- Allow horizontal scrolling of code blocks in issue body
- Catch what warden might throw when profiling requests to re-throw it
- Avoid commit lookup on diff_helper passing existing local variable to the helper method
- Add description to new_issue email and new_merge_request_email in text/plain content type. !5663 (dixpac)
......@@ -137,6 +147,7 @@ v 8.11.0 (unreleased)
- Each `File::exists?` replaced to `File::exist?` because of deprecate since ruby version 2.2.0
- Add auto-completition in pipeline (Katarzyna Kobierska Ula Budziszewska)
- Add pipelines tab to merge requests
- Fix notification_service argument error of declined invitation emails
- Fix a memory leak caused by Banzai::Filter::SanitizationFilter
- Speed up todos queries by limiting the projects set we join with
- Ensure file editing in UI does not overwrite commited changes without warning user
......
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 14">
<g fill="#d6d7d9">
<path d="M8.7 0L5.3.3l3.2 6.8-3.2 6.6 3.5.3L12 6.9z"/>
<ellipse cx="1.7" cy="11.1" rx="1.7" ry="1.7"/>
<ellipse cx="1.7" cy="5.6" rx="1.7" ry="1.7"/>
</g>
</svg>
\ No newline at end of file
......@@ -27,8 +27,6 @@
/*= require bootstrap/tooltip */
/*= require bootstrap/popover */
/*= require select2 */
/*= require ace-rails-ap */
/*= require ace/ext-searchbox */
/*= require underscore */
/*= require dropzone */
/*= require mousetrap */
......
/*= require_tree . */
(function() {
$(function() {
var url = $(".js-edit-blob-form").data("relative-url-root");
url += $(".js-edit-blob-form").data("assets-prefix");
var blob = new EditBlob(url, $('.js-edit-blob-form').data('blob-language'));
new NewCommitForm($('.js-edit-blob-form'));
});
}).call(this);
......@@ -55,7 +55,7 @@
draggable: '.is-draggable',
handle: '.js-board-handle',
onEnd: (e) => {
document.body.classList.remove('is-dragging');
gl.issueBoards.onEnd();
if (e.newIndex !== undefined && e.oldIndex !== e.newIndex) {
const order = this.sortable.toArray(),
......@@ -72,10 +72,6 @@
}
});
if (bp.getBreakpointSize() === 'xs') {
options.handle = '.js-board-drag-handle';
}
this.sortable = Sortable.create(this.$el.parentNode, options);
},
beforeDestroy () {
......
......@@ -63,6 +63,8 @@
Store.moving.issue = card.issue;
Store.moving.list = card.list;
gl.issueBoards.onStart();
},
onAdd: (e) => {
gl.issueBoards.BoardsStore.moveIssueToList(Store.moving.list, this.list, Store.moving.issue);
......@@ -72,10 +74,6 @@
}
});
if (bp.getBreakpointSize() === 'xs') {
options.handle = '.js-card-drag-handle';
}
this.sortable = Sortable.create(this.$els.list, options);
// Scroll event on list to load more
......
......@@ -2,6 +2,19 @@
window.gl = window.gl || {};
window.gl.issueBoards = window.gl.issueBoards || {};
gl.issueBoards.onStart = () => {
$('.has-tooltip').tooltip('hide')
.tooltip('disable');
document.body.classList.add('is-dragging');
};
gl.issueBoards.onEnd = () => {
$('.has-tooltip').tooltip('enable');
document.body.classList.remove('is-dragging');
};
gl.issueBoards.touchEnabled = ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch;
gl.issueBoards.getBoardSortableDefaultOptions = (obj) => {
let defaultSortOptions = {
forceFallback: true,
......@@ -9,14 +22,11 @@
fallbackOnBody: true,
ghostClass: 'is-ghost',
filter: '.has-tooltip',
scrollSensitivity: 100,
delay: gl.issueBoards.touchEnabled ? 100 : 0,
scrollSensitivity: gl.issueBoards.touchEnabled ? 60 : 100,
scrollSpeed: 20,
onStart () {
document.body.classList.add('is-dragging');
},
onEnd () {
document.body.classList.remove('is-dragging');
}
onStart: gl.issueBoards.onStart,
onEnd: gl.issueBoards.onEnd
}
Object.keys(obj).forEach((key) => { defaultSortOptions[key] = obj[key]; });
......
......@@ -3,6 +3,7 @@ class ListLabel {
this.id = obj.id;
this.title = obj.title;
this.color = obj.color;
this.textColor = obj.text_color;
this.description = obj.description;
this.priority = (obj.priority !== null) ? obj.priority : Infinity;
}
......
......@@ -20,6 +20,9 @@
path = page.split(':');
shortcut_handler = null;
switch (page) {
case 'projects:boards:show':
shortcut_handler = new ShortcutsNavigation();
break;
case 'projects:issues:index':
Issuable.init();
new IssuableBulkActions();
......@@ -126,10 +129,12 @@
new NotificationsDropdown();
break;
case 'groups:group_members:index':
new gl.MemberExpirationDate();
new GroupMembers();
new UsersSelect();
break;
case 'projects:project_members:index':
new gl.MemberExpirationDate();
new ProjectMembers();
new UsersSelect();
break;
......@@ -171,6 +176,7 @@
new BuildArtifacts();
break;
case 'projects:group_links:index':
new gl.MemberExpirationDate();
new GroupsSelect();
break;
case 'search:show':
......
......@@ -4,7 +4,7 @@
var _this;
_this = this;
$('.js-label-select').each(function(i, dropdown) {
var $block, $colorPreview, $dropdown, $form, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, defaultLabel, enableLabelCreateButton, issueURLSplit, issueUpdateURL, labelHTMLTemplate, labelNoneHTMLTemplate, labelUrl, projectId, saveLabelData, selectedLabel, showAny, showNo;
var $block, $colorPreview, $dropdown, $form, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, defaultLabel, enableLabelCreateButton, issueURLSplit, issueUpdateURL, labelHTMLTemplate, labelNoneHTMLTemplate, labelUrl, projectId, saveLabelData, selectedLabel, showAny, showNo, $sidebarLabelTooltip;
$dropdown = $(dropdown);
projectId = $dropdown.data('project-id');
labelUrl = $dropdown.data('labels');
......@@ -21,6 +21,7 @@
$block = $selectbox.closest('.block');
$form = $dropdown.closest('form');
$sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon span');
$sidebarLabelTooltip = $block.find('.js-sidebar-labels-tooltip');
$value = $block.find('.value');
$loading = $block.find('.block-loading').fadeOut();
if (issueUpdateURL != null) {
......@@ -31,7 +32,11 @@
labelNoneHTMLTemplate = '<span class="no-value">None</span>';
}
$sidebarLabelTooltip.tooltip();
if ($dropdown.closest('.dropdown').find('.dropdown-new-label').length) {
new gl.CreateLabelDropdown($dropdown.closest('.dropdown').find('.dropdown-new-label'), projectId);
}
saveLabelData = function() {
var data, selected;
......@@ -52,7 +57,7 @@
dataType: 'JSON',
data: data
}).done(function(data) {
var labelCount, template;
var labelCount, template, labelTooltipTitle, labelTitles;
$loading.fadeOut();
$dropdown.trigger('loaded.gl.dropdown');
$selectbox.hide();
......@@ -66,6 +71,27 @@
}
$value.removeAttr('style').html(template);
$sidebarCollapsedValue.text(labelCount);
if (data.labels.length) {
labelTitles = data.labels.map(function(label) {
return label.title;
});
if (labelTitles.length > 5) {
labelTitles = labelTitles.slice(0, 5);
labelTitles.push('and ' + (data.labels.length - 5) + ' more');
}
labelTooltipTitle = labelTitles.join(', ');
} else {
labelTooltipTitle = '';
$sidebarLabelTooltip.tooltip('destroy');
}
$sidebarLabelTooltip
.attr('title', labelTooltipTitle)
.tooltip('fixTitle');
$('.has-tooltip', $value).tooltip({
container: 'body'
});
......
/*= require ace-rails-ap */
/*= require ace/ext-searchbox */
(function() {
// Add datepickers to all `js-access-expiration-date` elements. If those elements are
// children of an element with the `clearable-input` class, and have a sibling
// `js-clear-input` element, then show that element when there is a value in the
// datepicker, and make clicking on that element clear the field.
//
gl.MemberExpirationDate = function() {
function toggleClearInput() {
$(this).closest('.clearable-input').toggleClass('has-value', $(this).val() !== '');
}
var inputs = $('.js-access-expiration-date');
inputs.datepicker({
dateFormat: 'yy-mm-dd',
minDate: 1,
onSelect: toggleClearInput
});
inputs.next('.js-clear-input').on('click', function(event) {
event.preventDefault();
var input = $(this).closest('.clearable-input').find('.js-access-expiration-date');
input.datepicker('setDate', null);
toggleClearInput.call(input);
});
inputs.on('blur', toggleClearInput);
inputs.each(toggleClearInput);
};
}).call(this);
......@@ -5,9 +5,6 @@
return $(this).fadeOut();
});
}
return ProjectMembers;
})();
}).call(this);
/*= require_tree . */
(function() {
$(function() {
var editor = ace.edit("editor")
$(".snippet-form-holder form").on('submit', function() {
$(".snippet-file-content").val(editor.getValue());
});
});
}).call(this);
......@@ -14,12 +14,20 @@
margin-top: 0;
}
// Single code lines should wrap
code {
font-family: $monospace_font;
white-space: pre;
white-space: pre-wrap;
word-wrap: normal;
}
// Multi-line code blocks should scroll horizontally
pre {
code {
white-space: pre;
}
}
kbd {
display: inline-block;
padding: 3px 5px;
......
......@@ -8,9 +8,13 @@
}
.is-dragging {
// Important because plugin sets inline CSS
opacity: 1!important;
* {
cursor: -webkit-grabbing;
cursor: grabbing;
// !important to make sure no style can override this when dragging
cursor: -webkit-grabbing!important;
cursor: grabbing!important;
}
}
......@@ -101,8 +105,8 @@
.board {
display: -webkit-flex;
display: flex;
min-width: calc(100vw - 15px);
max-width: calc(100vw - 15px);
min-width: calc(85vw - 15px);
max-width: calc(85vw - 15px);
margin-bottom: 25px;
padding-right: ($gl-padding / 2);
padding-left: ($gl-padding / 2);
......@@ -154,14 +158,6 @@
padding: $gl-padding;
font-size: 1em;
border-bottom: 1px solid $border-color;
.board-mobile-handle {
position: relative;
left: 0;
top: 1px;
margin-top: 0;
margin-right: 5px;
}
}
.board-search-container {
......@@ -254,11 +250,6 @@
opacity: 0.3;
}
.is-dragging {
// Important because plugin sets inline CSS
opacity: 1!important;
}
.card {
position: relative;
width: 100%;
......@@ -269,12 +260,8 @@
list-style: none;
&.user-can-drag {
padding-left: ($gl-padding * 2);
@media (min-width: $screen-sm-min) {
padding-left: $gl-padding;
}
}
&:not(:last-child) {
margin-bottom: 5px;
......@@ -294,17 +281,6 @@
}
}
.board-mobile-handle {
position: absolute;
left: 10px;
top: 50%;
margin-top: (-15px / 2);
@media (min-width: $screen-sm-min) {
display: none;
}
}
.card-title {
margin: 0;
font-size: 1em;
......@@ -316,6 +292,7 @@
.card-footer {
margin-top: 5px;
line-height: 25px;
.label {
margin-right: 4px;
......
......@@ -168,7 +168,6 @@
text-overflow: ellipsis;
&:hover {
background-color: $row-hover;
color: $gl-text-color;
}
}
......@@ -190,6 +189,10 @@
display: block;
}
}
&:hover {
background-color: $row-hover;
}
}
}
}
......
......@@ -66,6 +66,15 @@
margin-left: 8px;
}
}
.ci-status-link {
svg {
position: relative;
top: 2px;
margin: 0 2px 0 3px;
}
}
}
.ci-status-link {
......
......@@ -34,11 +34,4 @@
}
}
}
.wiki {
code {
white-space: pre-wrap;
word-break: keep-all;
}
}
}
......@@ -374,3 +374,10 @@
}
}
}
.merge-request-details {
.title {
margin-bottom: 20px;
}
}
......@@ -300,6 +300,17 @@
&.playable {
background-color: $gray-light;
svg {
height: 12px;
width: 12px;
position: relative;
top: 1px;
path {
fill: $layout-link-gray;
}
}
}
.build-content {
......@@ -319,10 +330,6 @@
margin-right: 5px;
}
.fa {
font-size: 13px;
}
// Connect first build in each stage with right horizontal line
&:first-child {
&::after {
......
......@@ -739,3 +739,29 @@ pre.light-well {
width: 300px;
}
}
.clearable-input {
position: relative;
.clear-icon {
@extend .fa-times;
display: none;
position: absolute;
right: 7px;
top: 7px;
color: $location-icon-color;
&:before {
font-family: FontAwesome;
font-weight: normal;
font-style: normal;
}
}
&.has-value {
.clear-icon {
cursor: pointer;
display: block;
}
}
}
......@@ -113,6 +113,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:sentry_dsn,
:akismet_enabled,
:akismet_api_key,
:koding_enabled,
:koding_url,
:email_author_in_body,
:repository_checks_enabled,
:metrics_packet_size,
......
......@@ -42,7 +42,7 @@ class Admin::GroupsController < Admin::ApplicationController
end
def members_update
@group.add_users(params[:user_ids].split(','), params[:access_level], current_user)
@group.add_users(params[:user_ids].split(','), params[:access_level], current_user: current_user)
redirect_to [:admin, @group], notice: 'Users were successfully added.'
end
......
......@@ -7,11 +7,16 @@ module ServiceParams
:build_key, :server, :teamcity_url, :drone_url, :build_type,
:description, :issues_url, :new_issue_url, :restrict_to_branch, :channel,
:colorize_messages, :channels,
:push_events, :issues_events, :merge_requests_events, :tag_push_events,
:note_events, :build_events, :wiki_page_events,
:notify_only_broken_builds, :add_pusher,
:send_from_committer_email, :disable_diffs, :external_wiki_url,
:notify, :color,
# We're using `issues_events` and `merge_requests_events`
# in the view so we still need to explicitly state them
# here. `Service#event_names` would only give
# `issue_events` and `merge_request_events` (singular!)
# See app/helpers/services_helper.rb for how we
# make those event names plural as special case.
:issues_events, :merge_requests_events,
:notify_only_broken_builds, :notify_only_broken_pipelines,
:add_pusher, :send_from_committer_email, :disable_diffs,
:external_wiki_url, :notify, :color,
:server_host, :server_port, :default_irc_uri, :enable_ssl_verification,
:jira_issue_transition_id]
......@@ -19,9 +24,7 @@ module ServiceParams
FILTER_BLANK_PARAMS = [:password]
def service_params
dynamic_params = []
dynamic_params.concat(@service.event_channel_names)
dynamic_params = @service.event_channel_names + @service.event_names
service_params = params.permit(:id, service: ALLOWED_PARAMS + dynamic_params)
if service_params[:service].is_a?(Hash)
......
......@@ -21,10 +21,15 @@ class Groups::GroupMembersController < Groups::ApplicationController
end
def create
access_level = params[:access_level]
user_ids = params[:user_ids].split(',')
@group.add_users(user_ids, access_level, current_user)
@group.add_users(
user_ids,
params[:access_level],
current_user: current_user,
expires_at: params[:expires_at]
)
group_members = @group.group_members.where(user_id: user_ids)
group_members.each do |group_member|
......@@ -76,7 +81,7 @@ class Groups::GroupMembersController < Groups::ApplicationController
protected
def member_params
params.require(:group_member).permit(:access_level, :user_id)
params.require(:group_member).permit(:access_level, :user_id, :expires_at)
end
# MembershipActions concern
......
class KodingController < ApplicationController
before_action :check_integration!, :authenticate_user!, :reject_blocked!
layout 'koding'
def index
path = File.join(Rails.root, 'doc/integration/koding-usage.md')
@markdown = File.read(path)
end
private
def check_integration!
render_404 unless current_application_settings.koding_enabled?
end
end
......@@ -12,7 +12,7 @@ module Projects
only: [:iid, :title, :confidential],
include: {
assignee: { only: [:id, :name, :username], methods: [:avatar_url] },
labels: { only: [:id, :title, :description, :color, :priority] }
labels: { only: [:id, :title, :description, :color, :priority], methods: [:text_color] }
})
end
......
......@@ -11,7 +11,9 @@ class Projects::GroupLinksController < Projects::ApplicationController
return render_404 unless can?(current_user, :read_group, group)
project.project_group_links.create(
group: group, group_access: params[:link_group_access]
group: group,
group_access: params[:link_group_access],
expires_at: params[:expires_at]
)
redirect_to namespace_project_group_links_path(project.namespace, project)
......
......@@ -56,6 +56,7 @@ class Projects::HooksController < Projects::ApplicationController
def hook_params
params.require(:hook).permit(
:build_events,
:pipeline_events,
:enable_ssl_verification,
:issues_events,
:merge_requests_events,
......
......@@ -36,7 +36,13 @@ class Projects::ProjectMembersController < Projects::ApplicationController
end
def create
@project.team.add_users(params[:user_ids].split(','), params[:access_level], current_user)
@project.team.add_users(
params[:user_ids].split(','),
params[:access_level],
expires_at: params[:expires_at],
current_user: current_user
)
members = @project.project_members.where(user_id: params[:user_ids].split(','))
members.each do |member|
......@@ -105,7 +111,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController
protected
def member_params
params.require(:project_member).permit(:user_id, :access_level)
params.require(:project_member).permit(:user_id, :access_level, :expires_at)
end
# MembershipActions concern
......
......@@ -35,6 +35,10 @@ module ApplicationSettingsHelper
current_application_settings.akismet_enabled?
end
def koding_enabled?
current_application_settings.koding_enabled?
end
def allowed_protocols_present?
current_application_settings.enabled_git_access_protocol.present?
end
......
......@@ -217,4 +217,12 @@ module BlobHelper
def gitlab_ci_ymls
@gitlab_ci_ymls ||= Gitlab::Template::GitlabCiYmlTemplate.dropdown_names
end
def blob_editor_paths
{
'relative-url-root' => Rails.application.config.relative_url_root,
'assets-prefix' => Gitlab::Application.config.assets.prefix,
'blob-language' => @blob && @blob.language.try(:ace_mode)
}
end
end
......@@ -39,7 +39,7 @@ module CiStatusHelper
when 'running'
'icon_status_running'
when 'play'
return icon('play fw')
'icon_play'
when 'created'
'icon_status_pending'
else
......
......@@ -72,6 +72,15 @@ module IssuablesHelper
end
end
def issuable_labels_tooltip(labels, limit: 5)
first, last = labels.partition.with_index{ |_, i| i < limit }
label_names = first.collect(&:name)
label_names << "and #{last.size} more" unless last.empty?
label_names.join(', ')
end
private
def sidebar_gutter_collapsed?
......
......@@ -245,6 +245,60 @@ module ProjectsHelper
)
end
def add_koding_stack_path(project)
namespace_project_new_blob_path(
project.namespace,
project,
project.default_branch || 'master',
file_name: '.koding.yml',
commit_message: "Add Koding stack script",
content: <<-CONTENT.strip_heredoc
provider:
aws:
access_key: '${var.aws_access_key}'
secret_key: '${var.aws_secret_key}'
resource:
aws_instance:
#{project.path}-vm:
instance_type: t2.nano
user_data: |-
# Created by GitLab UI for :>
echo _KD_NOTIFY_@Installing Base packages...@
apt-get update -y
apt-get install git -y
echo _KD_NOTIFY_@Cloning #{project.name}...@
export KODING_USER=${var.koding_user_username}
export REPO_URL=#{root_url}${var.koding_queryString_repo}.git
export BRANCH=${var.koding_queryString_branch}
sudo -i -u $KODING_USER git clone $REPO_URL -b $BRANCH
echo _KD_NOTIFY_@#{project.name} cloned.@
CONTENT
)
end
def koding_project_url(project = nil, branch = nil, sha = nil)
if project
import_path = "/Home/Stacks/import"
repo = project.path_with_namespace
branch ||= project.default_branch
sha ||= project.commit.short_id
path = "#{import_path}?repo=#{repo}&branch=#{branch}&sha=#{sha}"
return URI.join(current_application_settings.koding_url, path).to_s
end
current_application_settings.koding_url
end
def contribution_guide_path(project)
if project && contribution_guide = project.repository.contribution_guide
namespace_project_blob_path(
......
......@@ -15,20 +15,9 @@ module TimeHelper
"#{from.to_s(:short)} - #{to.to_s(:short)}"
end
def duration_in_numbers(finished_at, started_at)
interval = interval_in_seconds(started_at, finished_at)
time_format = interval < 1.hour ? "%M:%S" : "%H:%M:%S"
def duration_in_numbers(duration)
time_format = duration < 1.hour ? "%M:%S" : "%H:%M:%S"
Time.at(interval).utc.strftime(time_format)
end
private
def interval_in_seconds(started_at, finished_at = nil)
if started_at && finished_at
finished_at.to_i - started_at.to_i
elsif started_at
Time.now.to_i - started_at.to_i
end
Time.at(duration).utc.strftime(time_format)
end
end
......@@ -55,6 +55,10 @@ class ApplicationSetting < ActiveRecord::Base
presence: true,
if: :akismet_enabled
validates :koding_url,
presence: true,
if: :koding_enabled
validates :max_attachment_size,
presence: true,
numericality: { only_integer: true, greater_than: 0 }
......@@ -157,6 +161,8 @@ class ApplicationSetting < ActiveRecord::Base
two_factor_grace_period: 48,
recaptcha_enabled: false,
akismet_enabled: false,
koding_enabled: false,
koding_url: nil,
repository_checks_enabled: true,
disabled_oauth_sign_in_sources: [],
send_user_confirmation_email: false,
......
......@@ -62,6 +62,7 @@ module Ci
status_event: 'enqueue'
)
MergeRequests::AddTodoWhenBuildFailsService.new(build.project, nil).close(new_build)
build.pipeline.mark_as_processable_after_stage(build.stage_idx)
new_build
end
end
......@@ -344,7 +345,7 @@ module Ci
def execute_hooks
return unless project
build_data = Gitlab::BuildDataBuilder.build(self)
build_data = Gitlab::DataBuilder::Build.build(self)
project.execute_hooks(build_data.dup, :build_hooks)
project.execute_services(build_data.dup, :build_hooks)
PagesService.new(build_data).execute
......
......@@ -19,6 +19,8 @@ module Ci
after_save :keep_around_commits
delegate :stages, to: :statuses
state_machine :status, initial: :created do
event :enqueue do
transition created: :pending
......@@ -56,6 +58,10 @@ module Ci
before_transition do |pipeline|
pipeline.update_duration
end
after_transition do |pipeline, transition|
pipeline.execute_hooks unless transition.loopback?
end
end
# ref can't be HEAD or SHA, can only be branch/tag name
......@@ -72,6 +78,10 @@ module Ci
CommitStatus.where(pipeline: pluck(:id)).stages
end
def self.total_duration
where.not(duration: nil).sum(:duration)
end
def stages_with_latest_statuses
statuses.latest.order(:stage_idx).group_by(&:stage)
end
......@@ -140,6 +150,10 @@ module Ci
end
end
def mark_as_processable_after_stage(stage_idx)
builds.skipped.where('stage_idx > ?', stage_idx).find_each(&:process)
end
def latest?
return false unless ref
commit = project.commit(ref)
......@@ -244,11 +258,21 @@ module Ci
end
def update_duration
self.duration = statuses.latest.duration
self.duration = calculate_duration
end
def execute_hooks
data = pipeline_data
project.execute_hooks(data, :pipeline_hooks)
project.execute_services(data, :pipeline_hooks)
end
private
def pipeline_data
Gitlab::DataBuilder::Pipeline.build(self)
end
def latest_builds_status
return 'failed' unless yaml_errors.blank?
......
......@@ -229,7 +229,7 @@ class Commit
def diff_refs
Gitlab::Diff::DiffRefs.new(
base_sha: self.parent_id || self.sha,
base_sha: self.parent_id || Gitlab::Git::BLANK_SHA,
head_sha: self.sha
)
end
......
......@@ -21,6 +21,7 @@ class CommitStatus < ActiveRecord::Base
where(id: max_id.group(:name, :commit_id))
end
scope :retried, -> { where.not(id: latest) }
scope :ordered, -> { order(:name) }
scope :ignored, -> { where(allow_failure: true, status: [:failed, :canceled]) }
......@@ -30,6 +31,10 @@ class CommitStatus < ActiveRecord::Base
transition [:created, :skipped] => :pending
end
event :process do
transition skipped: :created
end
event :run do
transition pending: :running
end
......@@ -107,13 +112,7 @@ class CommitStatus < ActiveRecord::Base
end
def duration
duration =
if started_at && finished_at
finished_at - started_at
elsif started_at
Time.now - started_at
end
duration
calculate_duration
end
def stuck?
......
module Expirable
extend ActiveSupport::Concern
included do
scope :expired, -> { where('expires_at <= ?', Time.current) }
end
def expires?
expires_at.present?
end
def expires_soon?
expires_at < 7.days.from_now
end
end
......@@ -17,6 +17,10 @@ module NoteOnDiff
raise NotImplementedError
end
def original_line_code
raise NotImplementedError
end
def diff_attributes
raise NotImplementedError
end
......
......@@ -35,11 +35,6 @@ module Statuseable
all.pluck(self.status_sql).first
end
def duration
duration_array = all.map(&:duration).compact
duration_array.reduce(:+)
end
def started_at
all.minimum(:started_at)
end
......@@ -85,4 +80,14 @@ module Statuseable
def complete?
COMPLETED_STATUSES.include?(status)
end
private
def calculate_duration
if started_at && finished_at
finished_at - started_at
elsif started_at
Time.now - started_at
end
end
end
......@@ -57,6 +57,10 @@ class DiffNote < Note
diff_file.position(line) == self.original_position
end
def original_line_code
self.diff_file.line_code(self.diff_line)
end
def active?(diff_refs = nil)
return false unless supported?
return true if for_commit?
......
......@@ -12,6 +12,7 @@ class Discussion
:for_merge_request?,
:line_code,
:original_line_code,
:diff_file,
:for_line?,
:active?,
......
......@@ -107,34 +107,41 @@ class Group < Namespace
end
end
def add_users(user_ids, access_level, current_user = nil, skip_notification: false)
def add_users(user_ids, access_level, current_user: nil, skip_notification: false, expires_at: nil)
user_ids.each do |user_id|
Member.add_user(self.group_members, user_id, access_level, current_user, skip_notification: skip_notification)
Member.add_user(
self.group_members,
user_id,
access_level,
current_user: current_user,
skip_notification: skip_notification,
expires_at: expires_at
)
end
end
def add_user(user, access_level, current_user = nil, skip_notification: false)
add_users([user], access_level, current_user, skip_notification: skip_notification)
def add_user(user, access_level, current_user: nil, skip_notification: false, expires_at: nil)
add_users([user], access_level, current_user: current_user, skip_notification: skip_notification, expires_at: expires_at)
end
def add_owner(user, current_user = nil, skip_notification: false)
self.add_user(user, Gitlab::Access::OWNER, current_user, skip_notification: skip_notification)
add_user(user, Gitlab::Access::OWNER, current_user: current_user, skip_notification: skip_notification)
end
def add_guest(user, current_user = nil)
add_user(user, Gitlab::Access::GUEST, current_user)
add_user(user, Gitlab::Access::GUEST, current_user: current_user)
end
def add_reporter(user, current_user = nil)
add_user(user, Gitlab::Access::REPORTER, current_user)
add_user(user, Gitlab::Access::REPORTER, current_user: current_user)
end
def add_developer(user, current_user = nil)
add_user(user, Gitlab::Access::DEVELOPER, current_user)
add_user(user, Gitlab::Access::DEVELOPER, current_user: current_user)
end
def add_master(user, current_user = nil)
add_user(user, Gitlab::Access::MASTER, current_user)
add_user(user, Gitlab::Access::MASTER, current_user: current_user)
end
def has_owner?(user)
......
......@@ -9,5 +9,6 @@ class ProjectHook < WebHook
scope :note_hooks, -> { where(note_events: true) }
scope :merge_request_hooks, -> { where(merge_requests_events: true) }
scope :build_hooks, -> { where(build_events: true) }
scope :pipeline_hooks, -> { where(pipeline_events: true) }
scope :wiki_page_hooks, -> { where(wiki_page_events: true) }
end
......@@ -8,6 +8,7 @@ class WebHook < ActiveRecord::Base
default_value_for :merge_requests_events, false
default_value_for :tag_push_events, false
default_value_for :build_events, false
default_value_for :pipeline_events, false
default_value_for :enable_ssl_verification, true
scope :push_hooks, -> { where(push_events: true) }
......
......@@ -49,6 +49,10 @@ class LegacyDiffNote < Note
!line.meta? && diff_file.line_code(line) == self.line_code
end
def original_line_code
self.line_code
end
# Check if this note is part of an "active" discussion
#
# This will always return true for anything except MergeRequest noteables,
......
class Member < ActiveRecord::Base
include Sortable
include Importable
include Expirable
include Gitlab::Access
attr_accessor :raw_invite_token
......@@ -74,7 +75,7 @@ class Member < ActiveRecord::Base
user
end
def add_user(members, user_id, access_level, current_user = nil, skip_notification: false)
def add_user(members, user_id, access_level, current_user: nil, skip_notification: false, expires_at: nil)
user = user_for_id(user_id)
# `user` can be either a User object or an email to be invited
......@@ -88,7 +89,7 @@ class Member < ActiveRecord::Base
if can_update_member?(current_user, member) || project_creator?(member, access_level)
member.created_by ||= current_user
member.access_level = access_level
member.expires_at = expires_at
member.skip_notification = skip_notification
member.save
......
......@@ -34,7 +34,7 @@ class ProjectMember < Member
# :master
# )
#
def add_users_to_projects(project_ids, user_ids, access, current_user = nil)
def add_users_to_projects(project_ids, user_ids, access, current_user: nil, expires_at: nil)
access_level = if roles_hash.has_key?(access)
roles_hash[access]
elsif roles_hash.values.include?(access.to_i)
......@@ -50,7 +50,13 @@ class ProjectMember < Member
project = Project.find(project_id)
users.each do |user|
Member.add_user(project.project_members, user, access_level, current_user)
Member.add_user(
project.project_members,
user,
access_level,
current_user: current_user,
expires_at: expires_at
)
end
end
end
......
......@@ -1110,8 +1110,8 @@ class Project < ActiveRecord::Base
project_members.find_by(user_id: user)
end
def add_user(user, access_level, current_user = nil)
team.add_user(user, access_level, current_user)
def add_user(user, access_level, current_user: nil, expires_at: nil)
team.add_user(user, access_level, current_user: current_user, expires_at: expires_at)
end
def default_branch
......
class ProjectGroupLink < ActiveRecord::Base
include Expirable
GUEST = 10
REPORTER = 20
DEVELOPER = 30
......
......@@ -51,8 +51,7 @@ class BuildsEmailService < Service
end
def test_data(project = nil, user = nil)
build = project.builds.last
Gitlab::BuildDataBuilder.build(build)
Gitlab::DataBuilder::Build.build(project.builds.last)
end
def fields
......
......@@ -15,9 +15,9 @@ class ProjectTeam
users, access, current_user = *args
if users.respond_to?(:each)
add_users(users, access, current_user)
add_users(users, access, current_user: current_user)
else
add_user(users, access, current_user)
add_user(users, access, current_user: current_user)
end
end
......@@ -33,19 +33,20 @@ class ProjectTeam
member
end
def add_users(users, access, current_user = nil)
def add_users(users, access, current_user: nil, expires_at: nil)
return false if group_member_lock
ProjectMember.add_users_to_projects(
[project.id],
users,
access,
current_user
current_user: current_user,
expires_at: expires_at
)
end
def add_user(user, access, current_user = nil)
add_users([user], access, current_user)
def add_user(user, access, current_user: nil, expires_at: nil)
add_users([user], access, current_user: current_user, expires_at: expires_at)
end
# Remove all users from project team
......
......@@ -342,7 +342,7 @@ class Repository
def cache_keys
%i(size commit_count
readme version contribution_guide changelog
license_blob license_key gitignore)
license_blob license_key gitignore koding_yml)
end
# Keys for data on branch/tag operations.
......@@ -618,6 +618,14 @@ class Repository
end
end
def koding_yml
return nil unless head_exists?
cache.fetch(:koding_yml) do
file_on_head(/\A\.koding\.yml\z/)
end
end
def gitlab_ci_yml
return nil unless head_exists?
......
......@@ -36,6 +36,7 @@ class Service < ActiveRecord::Base
scope :merge_request_hooks, -> { where(merge_requests_events: true, active: true) }
scope :note_hooks, -> { where(note_events: true, active: true) }
scope :build_hooks, -> { where(build_events: true, active: true) }
scope :pipeline_hooks, -> { where(pipeline_events: true, active: true) }
scope :wiki_page_hooks, -> { where(wiki_page_events: true, active: true) }
scope :external_issue_trackers, -> { issue_trackers.active.without_defaults }
......@@ -79,13 +80,17 @@ class Service < ActiveRecord::Base
end
def test_data(project, user)
Gitlab::PushDataBuilder.build_sample(project, user)
Gitlab::DataBuilder::Push.build_sample(project, user)
end
def event_channel_names
[]
end
def event_names
supported_events.map { |event| "#{event}_events" }
end
def event_field(event)
nil
end
......
......@@ -39,7 +39,12 @@ class DeleteBranchService < BaseService
end
def build_push_data(branch)
Gitlab::PushDataBuilder
.build(project, current_user, branch.target.sha, Gitlab::Git::BLANK_SHA, "#{Gitlab::Git::BRANCH_REF_PREFIX}#{branch.name}", [])
Gitlab::DataBuilder::Push.build(
project,
current_user,
branch.target.sha,
Gitlab::Git::BLANK_SHA,
"#{Gitlab::Git::BRANCH_REF_PREFIX}#{branch.name}",
[])
end
end
......@@ -33,7 +33,12 @@ class DeleteTagService < BaseService
end
def build_push_data(tag)
Gitlab::PushDataBuilder
.build(project, current_user, tag.target.sha, Gitlab::Git::BLANK_SHA, "#{Gitlab::Git::TAG_REF_PREFIX}#{tag.name}", [])
Gitlab::DataBuilder::Push.build(
project,
current_user,
tag.target.sha,
Gitlab::Git::BLANK_SHA,
"#{Gitlab::Git::TAG_REF_PREFIX}#{tag.name}",
[])
end
end
......@@ -143,13 +143,23 @@ class GitPushService < BaseService
end
def build_push_data
@push_data ||= Gitlab::PushDataBuilder.
build(@project, current_user, params[:oldrev], params[:newrev], params[:ref], push_commits)
@push_data ||= Gitlab::DataBuilder::Push.build(
@project,
current_user,
params[:oldrev],
params[:newrev],
params[:ref],
push_commits)
end
def build_push_data_system_hook
@push_data_system ||= Gitlab::PushDataBuilder.
build(@project, current_user, params[:oldrev], params[:newrev], params[:ref], [])
@push_data_system ||= Gitlab::DataBuilder::Push.build(
@project,
current_user,
params[:oldrev],
params[:newrev],
params[:ref],
[])
end
def push_to_existing_branch?
......
......@@ -34,12 +34,24 @@ class GitTagPushService < BaseService
end
end
Gitlab::PushDataBuilder.
build(project, current_user, params[:oldrev], params[:newrev], params[:ref], commits, message)
Gitlab::DataBuilder::Push.build(
project,
current_user,
params[:oldrev],
params[:newrev],
params[:ref],
commits,
message)
end
def build_system_push_data
Gitlab::PushDataBuilder.
build(project, current_user, params[:oldrev], params[:newrev], params[:ref], [], '')
Gitlab::DataBuilder::Push.build(
project,
current_user,
params[:oldrev],
params[:newrev],
params[:ref],
[],
'')
end
end
module Members
class AuthorizedDestroyService < BaseService
attr_accessor :member, :user
def initialize(member, user = nil)
@member, @user = member, user
end
def execute
return false if member.is_a?(GroupMember) && member.source.last_owner?(member.user)
member.destroy
if member.request? && member.user != user
notification_service.decline_access_request(member)
end
end
end
end
......@@ -11,12 +11,7 @@ module Members
unless member && can?(current_user, "destroy_#{member.type.underscore}".to_sym, member)
raise Gitlab::Access::AccessDeniedError
end
member.destroy
if member.request? && member.user != current_user
notification_service.decline_access_request(member)
end
AuthorizedDestroyService.new(member, current_user).execute
end
end
end
......@@ -16,7 +16,7 @@ module Notes
end
def hook_data
Gitlab::NoteDataBuilder.build(@note, @note.author)
Gitlab::DataBuilder::Note.build(@note, @note.author)
end
def execute_note_hooks
......
......@@ -255,7 +255,6 @@ class NotificationService
project_member.real_source_type,
project_member.project.id,
project_member.invite_email,
project_member.access_level,
project_member.created_by_id
).deliver_later
end
......@@ -282,7 +281,6 @@ class NotificationService
group_member.real_source_type,
group_member.group.id,
group_member.invite_email,
group_member.access_level,
group_member.created_by_id
).deliver_later
end
......
class TestHookService
def execute(hook, current_user)
data = Gitlab::PushDataBuilder.build_sample(project(hook), current_user)
data = Gitlab::DataBuilder::Push.build_sample(hook.project, current_user)
hook.execute(data, 'push_hooks')
end
......
......@@ -439,6 +439,25 @@
.col-sm-10
= f.text_field :elasticsearch_port, class: 'form-control', placeholder: ApplicationSetting.current.elasticsearch_port
%fieldset
%legend Koding
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.label :koding_enabled do
= f.check_box :koding_enabled
Enable Koding
.form-group
= f.label :koding_url, 'Koding URL', class: 'control-label col-sm-2'
.col-sm-10
= f.text_field :koding_url, class: 'form-control', placeholder: 'http://gitlab.your-koding-instance.com:8090'
.help-block
Koding has integration enabled out of the box for the
%strong gitlab
team, and you need to provide that team's URL here. Learn more in the
= succeed "." do
= link_to "Koding integration documentation", help_page_path("integration/koding")
.form-actions
= f.submit 'Save', class: 'btn btn-save'
......@@ -51,7 +51,7 @@
- if build.duration
%p.duration
= custom_icon("icon_timer")
= duration_in_numbers(build.finished_at, build.started_at)
= duration_in_numbers(build.duration)
- if build.finished_at
%p.finished-at
......
......@@ -7,7 +7,7 @@
.diff-content.code.js-syntax-highlight
%table
- discussions = { discussion.line_code => discussion }
- discussions = { discussion.original_line_code => discussion }
= render partial: "projects/diffs/line",
collection: discussion.truncated_diff_lines,
as: :line,
......
......@@ -14,5 +14,14 @@
Read more about role permissions
%strong= link_to "here", help_page_path("user/permissions"), class: "vlink"
.form-group
= f.label :expires_at, 'Access expiration date', class: 'control-label'
.col-sm-10
.clearable-input
= text_field_tag :expires_at, nil, class: 'form-control js-access-expiration-date', placeholder: 'Select access expiration date'
%i.clear-icon.js-clear-input
.help-block
On this date, the user(s) will automatically lose access to this group and all of its projects.
.form-actions
= f.submit 'Add users to group', class: "btn btn-create"
:plain
$("##{dom_id(@group_member)}").replaceWith('#{escape_javascript(render('shared/members/member', member: @group_member))}');
new MemberExpirationDate();
.row-content-block.second-block.center
%p
= icon('circle', class: 'cgreen')
Integration is active for
= link_to koding_project_url, target: '_blank' do
#{current_application_settings.koding_url}
.documentation.wiki
= markdown @markdown
- page_title "Koding"
- page_description "Koding Dashboard"
- header_title "Koding", koding_path
= render template: "layouts/application"
......@@ -12,6 +12,11 @@
= link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity', title: 'Activity' do
%span
Activity
- if koding_enabled?
= nav_link(controller: :koding) do
= link_to koding_path, title: 'Koding' do
%span
Koding
= nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do
= link_to dashboard_groups_path, title: 'Groups' do
%span
......
......@@ -65,7 +65,7 @@
Graphs
- if project_nav_tab? :issues
= nav_link(controller: [:issues, :labels, :milestones]) do
= nav_link(controller: [:issues, :labels, :milestones, :boards]) do
= link_to namespace_project_issues_path(@project.namespace, @project), title: 'Issues', class: 'shortcuts-issues' do
%span
Issues
......
- page_title "Edit", @blob.path, @ref
- content_for :page_specific_javascripts do
= page_specific_javascript_tag('lib/ace.js')
= page_specific_javascript_tag('blob_edit/blob_edit_bundle.js')
- if @conflict
.alert.alert-danger
......@@ -16,14 +19,10 @@
= link_to '#preview', 'data-preview-url' => namespace_project_preview_blob_path(@project.namespace, @project, @id) do
= editing_preview_title(@blob.name)
= form_tag(namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put, class: 'form-horizontal js-quick-submit js-requires-input js-edit-blob-form') do
= form_tag(namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put, class: 'form-horizontal js-quick-submit js-requires-input js-edit-blob-form', data: blob_editor_paths) do
= render 'projects/blob/editor', ref: @ref, path: @path, blob_data: @blob.data
= render 'shared/new_commit_form', placeholder: "Update #{@blob.name}"
= hidden_field_tag 'last_commit_sha', @last_commit_sha
= hidden_field_tag 'content', '', id: "file-content"
= hidden_field_tag 'from_merge_request_id', params[:from_merge_request_id]
= render 'projects/commit_button', ref: @ref, cancel_path: namespace_project_blob_path(@project.namespace, @project, @id)
:javascript
blob = new EditBlob(gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}", "#{@blob.language.try(:ace_mode)}")
new NewCommitForm($('.js-edit-blob-form'))
- page_title "New File", @path.presence, @ref
- content_for :page_specific_javascripts do
= page_specific_javascript_tag('lib/ace.js')
= page_specific_javascript_tag('blob_edit/blob_edit_bundle.js')
%h3.page-title
New File
.file-editor
= form_tag(namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post, class: 'form-horizontal js-new-blob-form js-quick-submit js-requires-input') do
= form_tag(namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post, class: 'form-horizontal js-edit-blob-form js-new-blob-form js-quick-submit js-requires-input', data: blob_editor_paths) do
= render 'projects/blob/editor', ref: @ref
= render 'shared/new_commit_form', placeholder: "Add new file"
= hidden_field_tag 'content', '', id: 'file-content'
= render 'projects/commit_button', ref: @ref,
cancel_path: namespace_project_tree_path(@project.namespace, @project, @id)
:javascript
blob = new EditBlob(gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}")
new NewCommitForm($('.js-new-blob-form'))
......@@ -11,7 +11,6 @@
.board-inner
%header.board-header{ ":class" => "{ 'has-border': list.label }", ":style" => "{ borderTopColor: (list.label ? list.label.color : null) }" }
%h3.board-title.js-board-handle{ ":class" => "{ 'user-can-drag': (!disabled && !list.preset) }" }
= icon("align-justify", class: "board-mobile-handle js-board-drag-handle", "v-if" => "(!disabled && !list.preset)")
{{ list.title }}
%span.pull-right{ "v-if" => "list.type !== 'blank'" }
{{ list.issues.length }}
......
......@@ -9,7 +9,6 @@
"track-by" => "id" }
%li.card{ ":class" => "{ 'user-can-drag': !disabled }",
":index" => "index" }
= icon("align-justify", class: "board-mobile-handle js-card-drag-handle", "v-if" => "!disabled")
%h4.card-title
= icon("eye-slash", class: "confidential-icon", "v-if" => "issue.confidential")
%a{ ":href" => "issueLinkBase + '/' + issue.id",
......
- if koding_enabled? && current_user && can_push_branch?(@project, @project.default_branch)
- if @repository.koding_yml
= link_to koding_project_url(@project), class: 'btn', target: '_blank' do
Run in IDE (Koding)
- else
= link_to add_koding_stack_path(@project), class: 'btn' do
Set Up Koding
......@@ -63,7 +63,7 @@
- if build.duration
%p.duration
= custom_icon("icon_timer")
= duration_in_numbers(build.finished_at, build.started_at)
= duration_in_numbers(build.duration)
- if build.finished_at
%p.finished-at
= icon("calendar")
......
......@@ -48,10 +48,10 @@
\-
%td
- if pipeline.started_at && pipeline.finished_at
- if pipeline.duration
%p.duration
= custom_icon("icon_timer")
= duration_in_numbers(pipeline.finished_at, pipeline.started_at)
= duration_in_numbers(pipeline.duration)
- if pipeline.finished_at
%p.finished-at
= icon("calendar")
......
......@@ -56,10 +56,10 @@
= pluralize(@commit.pipelines.count, 'pipeline')
= link_to builds_namespace_project_commit_path(@project.namespace, @project, @commit.id), class: "ci-status-link ci-status-icon-#{@commit.status}" do
= ci_icon_for_status(@commit.status)
%span.ci-status-label
= ci_label_for_status(@commit.status)
- if @commit.pipelines.duration
in
= time_interval_in_words @commit.pipelines.duration
= time_interval_in_words @commit.pipelines.total_duration
.commit-box.content-block
%h3.commit-title
......
......@@ -17,6 +17,13 @@
.select-wrapper
= select_tag :link_group_access, options_for_select(ProjectGroupLink.access_options, ProjectGroupLink.default_access), class: "form-control select-control"
%span.caret
.form-group
= label_tag :expires_at, 'Access expiration date', class: 'label-light'
.clearable-input
= text_field_tag :expires_at, nil, class: 'form-control js-access-expiration-date', placeholder: 'Select access expiration date'
%i.clear-icon.js-clear-input
.help-block
On this date, all users in the group will automatically lose access to this project.
= submit_tag "Share", class: "btn btn-create"
.col-lg-9.col-lg-offset-3
%hr
......@@ -35,6 +42,10 @@
= group.name
%br
up to #{group_link.human_access}
- if group_link.expires?
·
%span{ class: ('text-warning' if group_link.expires_soon?) }
expires in #{distance_of_time_in_words_to_now(group_link.expires_at)}
.pull-right
= link_to namespace_project_group_link_path(@project.namespace, @project, group_link), method: :delete, class: "btn btn-transparent" do
%span.sr-only disable sharing
......
......@@ -3,7 +3,7 @@
.col-md-8.col-lg-7
%strong.light-header= hook.url
%div
- %w(push_events tag_push_events issues_events note_events merge_requests_events build_events wiki_page_events).each do |trigger|
- %w(push_events tag_push_events issues_events note_events merge_requests_events build_events pipeline_events wiki_page_events).each do |trigger|
- if hook.send(trigger)
%span.label.label-gray.deploy-project-label= trigger.titleize
.col-md-4.col-lg-5.text-right-lg.prepend-top-5
......
......@@ -16,6 +16,9 @@
- if @merge_request.open?
.pull-right
- if @merge_request.source_branch_exists?
- if koding_enabled? && @repository.koding_yml
= link_to koding_project_url(@merge_request.source_project, @merge_request.source_branch, @merge_request.commits.first.short_id), class: "btn inline btn-grouped btn-sm", target: '_blank' do
Run in IDE (Koding)
= link_to "#modal_merge_info", class: "btn inline btn-grouped btn-sm", "data-toggle" => "modal" do
Check out branch
......
......@@ -9,7 +9,7 @@
= link_to @pipeline.ref, namespace_project_commits_path(@project.namespace, @project, @pipeline.ref), class: "monospace"
- if @pipeline.duration
in
= time_interval_in_words @pipeline.duration
= time_interval_in_words(@pipeline.duration)
.pull-right
= link_to namespace_project_pipeline_path(@project.namespace, @project, @pipeline), class: "ci-status ci-#{@pipeline.status}" do
......
......@@ -14,5 +14,14 @@
Read more about role permissions
%strong= link_to "here", help_page_path("user/permissions"), class: "vlink"
.form-group
= f.label :expires_at, 'Access expiration date', class: 'control-label'
.col-sm-10
.clearable-input
= text_field_tag :expires_at, nil, class: 'form-control js-access-expiration-date', placeholder: 'Select access expiration date'
%i.clear-icon.js-clear-input
.help-block
On this date, the user(s) will automatically lose access to this project.
.form-actions
= f.submit 'Add users to project', class: "btn btn-create"
......@@ -4,7 +4,7 @@
.alert.alert-warning
Adding new users is disabled at group level
.project-members-page.prepend-top-default
.project-members-page.js-project-members-page.prepend-top-default
- if can?(current_user, :admin_project_member, @project) && !membership_locked?
.panel.panel-default
.panel-heading
......
:plain
$("##{dom_id(@project_member)}").replaceWith('#{escape_javascript(render('shared/members/member', member: @project_member))}');
new MemberExpirationDate();
- @no_container = true
- page_title "Edit", @tag.name, "Tags"
= render "projects/commits/head"
.row-content-block
%div{ class: container_class }
.sub-header-block.no-bottom-space
.oneline
.title
Release notes for tag
%strong #{@tag.name}
.prepend-top-default
= form_for(@release, method: :put, url: namespace_project_tag_release_path(@project.namespace, @project, @tag.name), html: { class: 'form-horizontal common-note-form release-form js-quick-submit' }) do |f|
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do
= render 'projects/zen', f: f, attr: :description, classes: 'note-textarea', placeholder: "Write your release notes or drag files here..."
= render 'projects/notes/hints'
.error-alert
.form-actions.prepend-top-default
.prepend-top-default
= f.submit 'Save changes', class: 'btn btn-save'
= link_to "Cancel", namespace_project_tag_path(@project.namespace, @project, @tag.name), class: "btn btn-default btn-cancel"
......@@ -64,10 +64,12 @@
%li.missing
= link_to add_special_file_path(@project, file_name: '.gitlab-ci.yml') do
Set Up CI
%li.project-repo-buttons-right
.project-repo-buttons.project-right-buttons
- if current_user
= render 'shared/members/access_request_buttons', source: @project
= render "projects/buttons/koding"
.btn-group.project-repo-btn-group
= render "projects/buttons/download"
......
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 11"><path fill-rule="evenodd" d="m9.283 6.47l-7.564 4.254c-.949.534-1.719.266-1.719-.576v-9.292c0-.852.756-1.117 1.719-.576l7.564 4.254c.949.534.963 1.392 0 1.934"/></svg>
\ No newline at end of file
......@@ -109,7 +109,7 @@
- if issuable.project.labels.any?
.block.labels
.sidebar-collapsed-icon
.sidebar-collapsed-icon.js-sidebar-labels-tooltip{ title: issuable_labels_tooltip(issuable.labels_array), data: { placement: "left", container: "body" } }
= icon('tags')
%span
= issuable.labels_array.size
......
......@@ -16,7 +16,7 @@
= button_tag icon('pencil'),
type: 'button',
class: 'btn inline js-toggle-button',
title: 'Edit access level'
title: 'Edit'
- if member.request?
= link_to icon('check inverse'), polymorphic_path([:approve_access_request, member]),
......@@ -59,6 +59,10 @@
= time_ago_with_tooltip(member.requested_at)
- else
Joined #{time_ago_with_tooltip(member.created_at)}
- if member.expires?
·
%span{ class: ('text-warning' if member.expires_soon?) }
Expires in #{distance_of_time_in_words_to_now(member.expires_at)}
- else
= image_tag avatar_icon(member.invite_email, 40), class: "avatar s40", alt: ''
......@@ -73,8 +77,16 @@
- if show_roles
.edit-member.hide.js-toggle-content
%br
= form_for member, remote: true do |f|
.prepend-top-10
= f.select :access_level, options_for_select(member.class.access_level_roles, member.access_level), {}, class: 'form-control'
= form_for member, remote: true, html: { class: 'form-horizontal' } do |f|
.form-group
= label_tag "member_access_level_#{member.id}", 'Project access', class: 'control-label'
.col-sm-10
= f.select :access_level, options_for_select(member.class.access_level_roles, member.access_level), {}, class: 'form-control', id: "member_access_level_#{member.id}"
.form-group
= label_tag "member_expires_at_#{member.id}", 'Access expiration date', class: 'control-label'
.col-sm-10
.clearable-input
= f.text_field :expires_at, class: 'form-control js-access-expiration-date', placeholder: 'Select access expiration date', id: "member_expires_at_#{member.id}"
%i.clear-icon.js-clear-input
.prepend-top-10
= f.submit 'Save', class: 'btn btn-save btn-sm'
- content_for :page_specific_javascripts do
= page_specific_javascript_tag('lib/ace.js')
= page_specific_javascript_tag('snippet/snippet_bundle.js')
.snippet-form-holder
= form_for @snippet, url: url, html: { class: "form-horizontal snippet-form js-requires-input" } do |f|
= form_errors(@snippet)
......@@ -31,8 +35,3 @@
- else
= link_to "Cancel", snippets_path(@project), class: "btn btn-cancel"
:javascript
var editor = ace.edit("editor");
$(".snippet-form-holder form").submit(function(){
$(".snippet-file-content").val(editor.getValue());
});
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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