Commit 70b7e6ba authored by Valery Sizov's avatar Valery Sizov

Merge branch 'ce_upstream' into 'master'

CE upstream

Closes gitlab-ce#33877, gitlab-ce#33594, gitlab-ce#33598, and gitlab-ce#27070

See merge request !2178
parents a51378de 9f837fa9
...@@ -427,19 +427,23 @@ gitlab:assets:compile: ...@@ -427,19 +427,23 @@ gitlab:assets:compile:
- webpack-report/ - webpack-report/
karma: karma:
image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.3-golang-1.8-git-2.7-chrome-59.0-node-7.1-postgresql-9.6"
stage: test stage: test
<<: *use-pg <<: *use-pg
<<: *dedicated-runner <<: *dedicated-runner
<<: *except-docs <<: *except-docs
variables: variables:
BABEL_ENV: "coverage" BABEL_ENV: "coverage"
CHROME_LOG_FILE: "chrome_debug.log"
script: script:
- bundle exec rake karma - bundle exec rake karma
coverage: '/^Statements *: (\d+\.\d+%)/' coverage: '/^Statements *: (\d+\.\d+%)/'
artifacts: artifacts:
name: coverage-javascript name: coverage-javascript
expire_in: 31d expire_in: 31d
when: always
paths: paths:
- chrome_debug.log
- coverage-javascript/ - coverage-javascript/
codeclimate: codeclimate:
......
...@@ -88,7 +88,7 @@ gem 'kaminari', '~> 0.17.0' ...@@ -88,7 +88,7 @@ gem 'kaminari', '~> 0.17.0'
gem 'hamlit', '~> 2.6.1' gem 'hamlit', '~> 2.6.1'
# Files attachments # Files attachments
gem 'carrierwave', '~> 1.0' gem 'carrierwave', '~> 1.1'
# Drag and Drop UI # Drag and Drop UI
gem 'dropzonejs-rails', '~> 0.7.1' gem 'dropzonejs-rails', '~> 0.7.1'
...@@ -167,7 +167,7 @@ gem 'rufus-scheduler', '~> 3.4' ...@@ -167,7 +167,7 @@ gem 'rufus-scheduler', '~> 3.4'
gem 'httparty', '~> 0.13.3' gem 'httparty', '~> 0.13.3'
# Colored output to console # Colored output to console
gem 'rainbow', '~> 2.1.0' gem 'rainbow', '~> 2.2'
# GitLab settings # GitLab settings
gem 'settingslogic', '~> 2.0.9' gem 'settingslogic', '~> 2.0.9'
...@@ -383,7 +383,7 @@ gem 'ruby-prof', '~> 0.16.2' ...@@ -383,7 +383,7 @@ gem 'ruby-prof', '~> 0.16.2'
gem 'oauth2', '~> 1.4' gem 'oauth2', '~> 1.4'
# Soft deletion # Soft deletion
gem 'paranoia', '~> 2.2' gem 'paranoia', '~> 2.3.1'
# Health check # Health check
gem 'health_check', '~> 2.6.0' gem 'health_check', '~> 2.6.0'
......
...@@ -116,7 +116,7 @@ GEM ...@@ -116,7 +116,7 @@ GEM
capybara-screenshot (1.0.14) capybara-screenshot (1.0.14)
capybara (>= 1.0, < 3) capybara (>= 1.0, < 3)
launchy launchy
carrierwave (1.0.0) carrierwave (1.1.0)
activemodel (>= 4.0.0) activemodel (>= 4.0.0)
activesupport (>= 4.0.0) activesupport (>= 4.0.0)
mime-types (>= 1.16) mime-types (>= 1.16)
...@@ -574,8 +574,8 @@ GEM ...@@ -574,8 +574,8 @@ GEM
rubypants (~> 0.2) rubypants (~> 0.2)
orm_adapter (0.5.0) orm_adapter (0.5.0)
os (0.9.6) os (0.9.6)
paranoia (2.2.0) paranoia (2.3.1)
activerecord (>= 4.0, < 5.1) activerecord (>= 4.0, < 5.2)
parser (2.4.0.0) parser (2.4.0.0)
ast (~> 2.2) ast (~> 2.2)
path_expander (1.0.1) path_expander (1.0.1)
...@@ -679,7 +679,8 @@ GEM ...@@ -679,7 +679,8 @@ GEM
activesupport (= 4.2.8) activesupport (= 4.2.8)
rake (>= 0.8.7) rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0) thor (>= 0.18.1, < 2.0)
rainbow (2.1.0) rainbow (2.2.2)
rake
raindrops (0.17.0) raindrops (0.17.0)
rake (10.5.0) rake (10.5.0)
rblineprof (0.3.6) rblineprof (0.3.6)
...@@ -961,7 +962,7 @@ DEPENDENCIES ...@@ -961,7 +962,7 @@ DEPENDENCIES
bundler-audit (~> 0.5.0) bundler-audit (~> 0.5.0)
capybara (~> 2.6.2) capybara (~> 2.6.2)
capybara-screenshot (~> 1.0.0) capybara-screenshot (~> 1.0.0)
carrierwave (~> 1.0) carrierwave (~> 1.1)
charlock_holmes (~> 0.7.3) charlock_holmes (~> 0.7.3)
chronic (~> 0.10.2) chronic (~> 0.10.2)
chronic_duration (~> 0.10.6) chronic_duration (~> 0.10.6)
...@@ -1067,7 +1068,7 @@ DEPENDENCIES ...@@ -1067,7 +1068,7 @@ DEPENDENCIES
omniauth-twitter (~> 1.2.0) omniauth-twitter (~> 1.2.0)
omniauth_crowd (~> 2.2.0) omniauth_crowd (~> 2.2.0)
org-ruby (~> 0.9.12) org-ruby (~> 0.9.12)
paranoia (~> 2.2) paranoia (~> 2.3.1)
peek (~> 1.0.1) peek (~> 1.0.1)
peek-gc (~> 0.0.2) peek-gc (~> 0.0.2)
peek-host (~> 1.0.0) peek-host (~> 1.0.0)
...@@ -1089,7 +1090,7 @@ DEPENDENCIES ...@@ -1089,7 +1090,7 @@ DEPENDENCIES
rack-proxy (~> 0.6.0) rack-proxy (~> 0.6.0)
rails (= 4.2.8) rails (= 4.2.8)
rails-deprecated_sanitizer (~> 1.0.3) rails-deprecated_sanitizer (~> 1.0.3)
rainbow (~> 2.1.0) rainbow (~> 2.2)
rblineprof (~> 0.3.6) rblineprof (~> 0.3.6)
rdoc (~> 4.2) rdoc (~> 4.2)
recaptcha (~> 3.0) recaptcha (~> 3.0)
...@@ -1155,4 +1156,4 @@ DEPENDENCIES ...@@ -1155,4 +1156,4 @@ DEPENDENCIES
wikicloth (= 0.8.1) wikicloth (= 0.8.1)
BUNDLED WITH BUNDLED WITH
1.15.0 1.15.1
...@@ -77,7 +77,7 @@ const Api = { ...@@ -77,7 +77,7 @@ const Api = {
dataType: 'json', dataType: 'json',
}) })
.done(label => callback(label)) .done(label => callback(label))
.error(message => callback(message.responseJSON)); .fail(message => callback(message.responseJSON));
}, },
// Return group projects list. Filtered by query // Return group projects list. Filtered by query
...@@ -134,7 +134,7 @@ const Api = { ...@@ -134,7 +134,7 @@ const Api = {
dataType: 'json', dataType: 'json',
}) })
.done(file => callback(null, file)) .done(file => callback(null, file))
.error(callback); .fail(callback);
}, },
users(query, options) { users(query, options) {
......
...@@ -287,6 +287,10 @@ window.DropzoneInput = (function() { ...@@ -287,6 +287,10 @@ window.DropzoneInput = (function() {
$uploadingErrorMessage.html(message); $uploadingErrorMessage.html(message);
}; };
closeAlertMessage = function() {
return form.find('.div-dropzone-alert').alert('close');
};
form.find('.markdown-selector').click(function(e) { form.find('.markdown-selector').click(function(e) {
e.preventDefault(); e.preventDefault();
$(this).closest('.gfm-form').find('.div-dropzone').click(); $(this).closest('.gfm-form').find('.div-dropzone').click();
......
...@@ -551,10 +551,10 @@ export default { ...@@ -551,10 +551,10 @@ export default {
</span> </span>
</div> </div>
<div class="table-section section-30 environments-actions table-button-footer" role="gridcell"> <div class="table-section section-30 table-button-footer" role="gridcell">
<div <div
v-if="!model.isFolder" v-if="!model.isFolder"
class="btn-group environment-action-buttons" class="btn-group table-action-buttons"
role="group"> role="group">
<actions-component <actions-component
......
...@@ -27,7 +27,7 @@ export default { ...@@ -27,7 +27,7 @@ export default {
if (this.group.hasSubgroups) { if (this.group.hasSubgroups) {
eventHub.$emit('toggleSubGroups', this.group); eventHub.$emit('toggleSubGroups', this.group);
} else { } else {
window.location.href = this.group.webUrl; window.location.href = this.group.groupPath;
} }
} }
}, },
...@@ -192,7 +192,7 @@ export default { ...@@ -192,7 +192,7 @@ export default {
<div <div
class="avatar-container s40 hidden-xs"> class="avatar-container s40 hidden-xs">
<a <a
:href="group.webUrl"> :href="group.groupPath">
<img <img
class="avatar s40" class="avatar s40"
:src="group.avatarUrl" :src="group.avatarUrl"
...@@ -202,7 +202,7 @@ export default { ...@@ -202,7 +202,7 @@ export default {
<div <div
class="title"> class="title">
<a <a
:href="group.webUrl">{{fullPath}}</a> :href="group.groupPath">{{fullPath}}</a>
<template v-if="group.permissions.humanGroupAccess"> <template v-if="group.permissions.humanGroupAccess">
as as
<span class="access-type">{{group.permissions.humanGroupAccess}}</span> <span class="access-type">{{group.permissions.humanGroupAccess}}</span>
......
...@@ -122,6 +122,7 @@ export default class GroupsStore { ...@@ -122,6 +122,7 @@ export default class GroupsStore {
canEdit: rawGroup.can_edit, canEdit: rawGroup.can_edit,
description: rawGroup.description, description: rawGroup.description,
webUrl: rawGroup.web_url, webUrl: rawGroup.web_url,
groupPath: rawGroup.group_path,
parentId: rawGroup.parent_id, parentId: rawGroup.parent_id,
visibility: rawGroup.visibility, visibility: rawGroup.visibility,
leavePath: rawGroup.leave_path, leavePath: rawGroup.leave_path,
......
...@@ -53,7 +53,7 @@ ...@@ -53,7 +53,7 @@
}, },
methods: { methods: {
renderGFM() { renderGFM() {
$(this.$refs['gfm-entry-content']).renderGFM(); $(this.$refs['gfm-content']).renderGFM();
if (this.canUpdate) { if (this.canUpdate) {
// eslint-disable-next-line no-new // eslint-disable-next-line no-new
......
...@@ -34,7 +34,7 @@ window.dateFormat = dateFormat; ...@@ -34,7 +34,7 @@ window.dateFormat = dateFormat;
w.gl.utils.localTimeAgo = function($timeagoEls, setTimeago = true) { w.gl.utils.localTimeAgo = function($timeagoEls, setTimeago = true) {
$timeagoEls.each((i, el) => { $timeagoEls.each((i, el) => {
el.setAttribute('title', gl.utils.formatDate(el.getAttribute('datetime'))); el.setAttribute('title', el.getAttribute('title'));
if (setTimeago) { if (setTimeago) {
// Recreate with custom template // Recreate with custom template
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
var locales = locales || {}; locales['fr'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","PO-Revision-Date":"2017-06-14 04:21-0400","Last-Translator":"Dremor <egeorget@opmbx.org>","Language-Team":"French (https://www.transifex.com/gitlab-fr/teams/75145/fr/)","Language":"fr","Plural-Forms":"nplurals=2; plural=(n > 1);","X-Generator":"Zanata 3.9.6","lang":"fr","domain":"app","plural_forms":"nplurals=2; plural=(n > 1);"},"ByAuthor|by":["par"],"Commit":["Validation","Validations"],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["L’analyseur de cycle permet d’avoir une vue d’ensemble du temps nécessaire pour aller d’une idée à sa mise en production pour votre projet."],"CycleAnalyticsStage|Code":["Code"],"CycleAnalyticsStage|Issue":["Incident"],"CycleAnalyticsStage|Plan":["Planification"],"CycleAnalyticsStage|Production":["Production"],"CycleAnalyticsStage|Review":["Examen"],"CycleAnalyticsStage|Staging":["Pré-production"],"CycleAnalyticsStage|Test":["Test"],"Deploy":["Déploiement","Déploiements"],"FirstPushedBy|First":["En premier"],"FirstPushedBy|pushed by":["poussé par"],"From issue creation until deploy to production":["Depuis la création de l'incident jusqu'au déploiement en production"],"From merge request merge until deploy to production":["Depuis la fusion de la demande de fusion jusqu'au déploiement en production"],"Introducing Cycle Analytics":["Introduction à l'analyseur de cycle"],"Last %d day":["Le dernier %d jour","Les derniers %d jours"],"Limited to showing %d event at most":["Limiter l'affichage au plus à %d évènement","Limiter l'affichage au plus à %d évènements"],"Median":["Médian"],"New Issue":["Nouvel incident","Nouveaux incidents"],"Not available":["Indisponible"],"Not enough data":["Données insuffisantes"],"OpenedNDaysAgo|Opened":["Ouvert"],"Pipeline Health":["Santé du Pipeline"],"ProjectLifecycle|Stage":["Étape"],"Read more":["Lire plus"],"Related Commits":["Validations liés"],"Related Deployed Jobs":["Tâches de déploiement liés"],"Related Issues":["Incidents liés"],"Related Jobs":["Tâches liées"],"Related Merge Requests":["Demandes de fusion liées"],"Related Merged Requests":["Demandes fusionnées liées"],"Showing %d event":["Affichage de %d évènement","Affichage de %d évènements"],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["L’étape de développement montre le temps entre la première validation et la création de la demande de fusion. Les données seront automatiquement ajoutées ici une fois que vous aurez créé votre première demande de fusion."],"The collection of events added to the data gathered for that stage.":["L’ensemble d’évènements ajoutés aux données récupérées pour cette étape."],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["L'étape des incidents montre le temps nécessaire entre la création d'un incident et son assignation à un jalon, ou son ajout à une liste d'un tableau d'incident. Débutez à créer des incidents pour voir des données pour cette étape."],"The phase of the development lifecycle.":["Les étapes du cycle de développement."],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["L’étape de planification montre le temps entre l’étape précédente et l’envoi de votre première validation. Ce temps sera automatiquement ajouté quand vous pousserez votre première validation."],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["L’étape de mise en production montre le temps nécessaire entre la création d’un incident et le déploiement du code en production. Les données seront automatiquement ajoutées une fois que vous aurez complété le cycle complet, depuis l’idée jusqu’à la mise en production."],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["L’étape d’évaluation montre le temps entre la création de la demande de fusion et la fusion effective de celle-ci. Ces données seront automatiquement ajoutées après que vous ayez fusionné votre première demande de fusion."],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["L’étape de pré-production indique le temps entre la fusion de la RF et le déploiement du code dans l’environnent de production. Les données seront automatiquement ajoutées une fois que vous déploierez en production pour la première fois."],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["L’étape de test montre le temps que le CI de GitLab met pour exécuter chaque pipeline liés à la demande de fusion. Les données seront automatiquement ajoutées après que votre premier pipeline s’achèvera."],"The time taken by each data entry gathered by that stage.":["Le temps pris par chaque entrée récoltée durant cette étape."],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["La valeur située au point médian d’une série de valeur observée. C.à.d., entre 3, 5, 9, le médian est 5. Entre 3, 5, 7, 8, le médian est (5+7)/2 = 6."],"Time before an issue gets scheduled":["Temps avant qu’un incident ne soit planifié"],"Time before an issue starts implementation":["Temps avant que résolution ne débute"],"Time between merge request creation and merge/close":["Temps entre la création d'une demande de fusion et sa fusion/clôture"],"Time until first merge request":["Temps jusqu’à la première demande de fusion"],"Time|hr":["hr","hrs"],"Time|min":["min","mins"],"Time|s":["s"],"Total Time":["Temps total"],"Total test time for all commits/merges":["Temps total de test pour toutes les validations/fusions"],"Want to see the data? Please ask an administrator for access.":["Vous voulez voir les données ? Merci de contacter un administrateur pour en obtenir l’accès."],"We don't have enough data to show this stage.":["Nous n'avons pas suffisamment de données pour afficher cette étape."],"You need permission.":["Vous avez besoin d’une autorisation."],"day":["jour","jours"]}}};
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
...@@ -4,87 +4,7 @@ ...@@ -4,87 +4,7 @@
(function() { (function() {
this.Milestone = (function() { this.Milestone = (function() {
Milestone.updateIssue = function(li, issue_url, data) {
return $.ajax({
type: "PUT",
url: issue_url,
data: data,
success: function(_data) {
return Milestone.successCallback(_data, li);
},
error: function(data) {
return new Flash("Issue update failed", 'alert');
},
dataType: "json"
});
};
Milestone.sortIssues = function(url, data) {
return $.ajax({
type: "PUT",
url,
data: data,
success: function(_data) {
return Milestone.successCallback(_data);
},
error: function() {
return new Flash("Issues update failed", 'alert');
},
dataType: "json"
});
};
Milestone.sortMergeRequests = function(url, data) {
return $.ajax({
type: "PUT",
url,
data: data,
success: function(_data) {
return Milestone.successCallback(_data);
},
error: function(data) {
return new Flash("Issue update failed", 'alert');
},
dataType: "json"
});
};
Milestone.updateMergeRequest = function(li, merge_request_url, data) {
return $.ajax({
type: "PUT",
url: merge_request_url,
data: data,
success: function(_data) {
return Milestone.successCallback(_data, li);
},
error: function(data) {
return new Flash("Issue update failed", 'alert');
},
dataType: "json"
});
};
Milestone.successCallback = function(data, element) {
const $avatarContainer = $(element).find('.assignee-icon');
$avatarContainer.empty();
if (data.assignees && data.assignees.length > 0) {
const $avatars = data.assignees.map((assignee) => {
const img_tag = $('<img/>');
img_tag.attr('src', assignee.avatar_url);
img_tag.addClass('avatar s16');
return img_tag;
});
$avatarContainer.append($avatars);
}
};
function Milestone() { function Milestone() {
this.issuesSortEndpoint = $('#tab-issues').data('sort-endpoint');
this.mergeRequestsSortEndpoint = $('#tab-merge-requests').data('sort-endpoint');
this.bindIssuesSorting();
this.bindTabsSwitching(); this.bindTabsSwitching();
// Load merge request tab if it is active // Load merge request tab if it is active
...@@ -94,22 +14,6 @@ ...@@ -94,22 +14,6 @@
this.loadInitialTab(); this.loadInitialTab();
} }
Milestone.prototype.bindIssuesSorting = function() {
if (!this.issuesSortEndpoint) return;
$('#issues-list-unassigned, #issues-list-ongoing, #issues-list-closed').each(function (i, el) {
this.createSortable(el, {
group: 'issue-list',
listEls: $('.issues-sortable-list'),
fieldName: 'issue',
sortCallback: (data) => {
Milestone.sortIssues(this.issuesSortEndpoint, data);
},
updateCallback: Milestone.updateIssue,
});
}.bind(this));
};
Milestone.prototype.bindTabsSwitching = function() { Milestone.prototype.bindTabsSwitching = function() {
return $('a[data-toggle="tab"]').on('show.bs.tab', (e) => { return $('a[data-toggle="tab"]').on('show.bs.tab', (e) => {
const $target = $(e.target); const $target = $(e.target);
...@@ -119,69 +23,6 @@ ...@@ -119,69 +23,6 @@
}); });
}; };
Milestone.prototype.bindMergeRequestSorting = function() {
if (!this.mergeRequestsSortEndpoint) return;
$("#merge_requests-list-unassigned, #merge_requests-list-ongoing, #merge_requests-list-closed").each(function (i, el) {
this.createSortable(el, {
group: 'merge-request-list',
listEls: $(".merge_requests-sortable-list:not(#merge_requests-list-merged)"),
fieldName: 'merge_request',
sortCallback: (data) => {
Milestone.sortMergeRequests(this.mergeRequestsSortEndpoint, data);
},
updateCallback: Milestone.updateMergeRequest,
});
}.bind(this));
};
Milestone.prototype.createSortable = function(el, opts) {
return Sortable.create(el, {
group: opts.group,
filter: '.is-disabled',
forceFallback: true,
onStart: function(e) {
opts.listEls.css('min-height', e.item.offsetHeight);
},
onEnd: function () {
opts.listEls.css("min-height", "0px");
},
onUpdate: function(e) {
var ids = this.toArray(),
data;
if (ids.length) {
data = ids.map(function(id) {
return 'sortable_' + opts.fieldName + '[]=' + id;
}).join('&');
opts.sortCallback(data);
}
},
onAdd: function (e) {
var data, issuableId, issuableUrl, newState;
newState = e.to.dataset.state;
issuableUrl = e.item.dataset.url;
data = (function() {
switch (newState) {
case 'ongoing':
return `${opts.fieldName}[assignee_ids][]=${gon.current_user_id}`;
case 'unassigned':
return `${opts.fieldName}[assignee_ids][]=0`;
case 'closed':
return opts.fieldName + '[state_event]=close';
}
})();
if (e.from.dataset.state === 'closed') {
data += '&' + opts.fieldName + '[state_event]=reopen';
}
opts.updateCallback(e.item, issuableUrl, data);
this.options.onUpdate.call(this, e);
}
});
};
Milestone.prototype.loadInitialTab = function() { Milestone.prototype.loadInitialTab = function() {
const $target = $(`.js-milestone-tabs a[href="${location.hash}"]`); const $target = $(`.js-milestone-tabs a[href="${location.hash}"]`);
...@@ -203,10 +44,6 @@ ...@@ -203,10 +44,6 @@
.done((data) => { .done((data) => {
$(tabElId).html(data.html); $(tabElId).html(data.html);
$target.addClass('is-loaded'); $target.addClass('is-loaded');
if (tabElId === '#tab-merge-requests') {
this.bindMergeRequestSorting();
}
}); });
} }
}; };
......
...@@ -322,7 +322,9 @@ const normalizeNewlines = function(str) { ...@@ -322,7 +322,9 @@ const normalizeNewlines = function(str) {
Notes.updateNoteTargetSelector = function($note) { Notes.updateNoteTargetSelector = function($note) {
const hash = gl.utils.getLocationHash(); const hash = gl.utils.getLocationHash();
$note.toggleClass('target', hash && $note.filter(`#${hash}`).length > 0); // Needs to be an explicit true/false for the jQuery `toggleClass(force)`
const addTargetClass = Boolean(hash && $note.filter(`#${hash}`).length > 0);
$note.toggleClass('target', addTargetClass);
}; };
/* /*
......
import Vue from 'vue'; import Vue from 'vue';
import Translate from '../../vue_shared/translate';
Vue.use(Translate);
const inputNameAttribute = 'schedule[cron]'; const inputNameAttribute = 'schedule[cron]';
...@@ -72,11 +75,11 @@ export default { ...@@ -72,11 +75,11 @@ export default {
/> />
<label for="custom"> <label for="custom">
Custom {{ s__('PipelineSheduleIntervalPattern|Custom') }}
</label> </label>
<span class="cron-syntax-link-wrap"> <span class="cron-syntax-link-wrap">
(<a :href="cronSyntaxUrl" target="_blank">Cron syntax</a>) (<a :href="cronSyntaxUrl" target="_blank">{{ __('Cron syntax') }}</a>)
</span> </span>
</div> </div>
...@@ -92,7 +95,7 @@ export default { ...@@ -92,7 +95,7 @@ export default {
/> />
<label class="label-light" for="every-day"> <label class="label-light" for="every-day">
Every day (at 4:00am) {{ __('Every day (at 4:00am)') }}
</label> </label>
</div> </div>
...@@ -108,7 +111,7 @@ export default { ...@@ -108,7 +111,7 @@ export default {
/> />
<label class="label-light" for="every-week"> <label class="label-light" for="every-week">
Every week (Sundays at 4:00am) {{ __('Every week (Sundays at 4:00am)') }}
</label> </label>
</div> </div>
...@@ -124,7 +127,7 @@ export default { ...@@ -124,7 +127,7 @@ export default {
/> />
<label class="label-light" for="every-month"> <label class="label-light" for="every-month">
Every month (on the 1st at 4:00am) {{ __('Every month (on the 1st at 4:00am)') }}
</label> </label>
</div> </div>
...@@ -133,7 +136,7 @@ export default { ...@@ -133,7 +136,7 @@ export default {
id="schedule_cron" id="schedule_cron"
class="form-control inline cron-interval-input" class="form-control inline cron-interval-input"
type="text" type="text"
placeholder="Define a custom pattern with cron syntax" :placeholder="__('Define a custom pattern with cron syntax')"
required="true" required="true"
v-model="cronInterval" v-model="cronInterval"
:name="inputNameAttribute" :name="inputNameAttribute"
......
import Vue from 'vue';
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import Translate from '../../vue_shared/translate';
import illustrationSvg from '../icons/intro_illustration.svg'; import illustrationSvg from '../icons/intro_illustration.svg';
Vue.use(Translate);
const cookieKey = 'pipeline_schedules_callout_dismissed'; const cookieKey = 'pipeline_schedules_callout_dismissed';
export default { export default {
...@@ -29,20 +33,18 @@ export default { ...@@ -29,20 +33,18 @@ export default {
</button> </button>
<div class="svg-container" v-html="illustrationSvg"></div> <div class="svg-container" v-html="illustrationSvg"></div>
<div class="user-callout-copy"> <div class="user-callout-copy">
<h4>Scheduling Pipelines</h4> <h4>{{ __('Scheduling Pipelines') }}</h4>
<p> <p>
The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. {{ __('The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user.') }}
Those scheduled pipelines will inherit limited project access based on their associated user.
</p> </p>
<p> Learn more in the <p> {{ __('Learn more in the') }}
<a <a
:href="docsUrl" :href="docsUrl"
target="_blank" target="_blank"
rel="nofollow">pipeline schedules documentation</a>. <!-- oneline to prevent extra space before period --> rel="nofollow">{{ s__('Learn more in the|pipeline schedules documentation') }}</a>. <!-- oneline to prevent extra space before period -->
</p> </p>
</div> </div>
</div> </div>
</div> </div>
`, `,
}; };
...@@ -23,7 +23,7 @@ export default { ...@@ -23,7 +23,7 @@ export default {
}; };
</script> </script>
<template> <template>
<td> <div class="table-section section-15 hidden-xs hidden-sm">
<a <a
:href="pipeline.path" :href="pipeline.path"
class="js-pipeline-url-link"> class="js-pipeline-url-link">
...@@ -42,24 +42,26 @@ export default { ...@@ -42,24 +42,26 @@ export default {
class="js-pipeline-url-api api"> class="js-pipeline-url-api api">
API API
</span> </span>
<span <div class="label-container">
v-if="pipeline.flags.latest" <span
class="js-pipeline-url-lastest label label-success" v-if="pipeline.flags.latest"
title="Latest pipeline for this branch" class="js-pipeline-url-latest label label-success"
ref="tooltip"> title="Latest pipeline for this branch"
latest ref="tooltip">
</span> latest
<span </span>
v-if="pipeline.flags.yaml_errors" <span
class="js-pipeline-url-yaml label label-danger" v-if="pipeline.flags.yaml_errors"
:title="pipeline.yaml_errors" class="js-pipeline-url-yaml label label-danger"
ref="tooltip"> :title="pipeline.yaml_errors"
yaml invalid ref="tooltip">
</span> yaml invalid
<span </span>
v-if="pipeline.flags.stuck" <span
class="js-pipeline-url-stuck label label-warning"> v-if="pipeline.flags.stuck"
stuck class="js-pipeline-url-stuck label label-warning">
</span> stuck
</td> </span>
</div>
</div>
</template> </template>
...@@ -56,7 +56,7 @@ ...@@ -56,7 +56,7 @@
<div class="btn-group"> <div class="btn-group">
<button <button
type="button" type="button"
class="dropdown-toggle btn btn-default has-tooltip js-pipeline-dropdown-manual-actions" class="dropdown-new btn btn-default has-tooltip js-pipeline-dropdown-manual-actions"
title="Manual job" title="Manual job"
data-toggle="dropdown" data-toggle="dropdown"
data-placement="top" data-placement="top"
......
...@@ -55,31 +55,39 @@ ...@@ -55,31 +55,39 @@
}; };
</script> </script>
<template> <template>
<td class="pipelines-time-ago"> <div class="table-section section-15 pipelines-time-ago">
<p <div
class="duration" class="table-mobile-header"
v-if="hasDuration"> role="rowheader">
<span v-html="iconTimerSvg"> Duration
</span> </div>
{{durationFormated}} <div class="table-mobile-content">
</p> <p
class="duration"
v-if="hasDuration">
<span
v-html="iconTimerSvg">
</span>
{{durationFormated}}
</p>
<p <p
class="finished-at" class="finished-at hidden-xs hidden-sm"
v-if="hasFinishedTime"> v-if="hasFinishedTime">
<i <i
class="fa fa-calendar" class="fa fa-calendar"
aria-hidden="true"> aria-hidden="true">
</i> </i>
<time <time
ref="tooltip" ref="tooltip"
data-placement="top" data-placement="top"
data-container="body" data-container="body"
:title="tooltipTitle(finishedTime)"> :title="tooltipTitle(finishedTime)">
{{timeFormated(finishedTime)}} {{timeFormated(finishedTime)}}
</time> </time>
</p> </p>
</td> </div>
</div>
</script> </script>
import statusCodes from '~/lib/utils/http_status'; import statusCodes from '../../lib/utils/http_status';
import { bytesToMiB } from '~/lib/utils/number_utils'; import { bytesToMiB } from '../../lib/utils/number_utils';
import MemoryGraph from '../../vue_shared/components/memory_graph'; import MemoryGraph from '../../vue_shared/components/memory_graph';
import MRWidgetService from '../services/mr_widget_service'; import MRWidgetService from '../services/mr_widget_service';
......
...@@ -110,7 +110,7 @@ ...@@ -110,7 +110,7 @@
</script> </script>
<template> <template>
<div class="branch-commit"> <div class="branch-commit">
<div v-if="hasCommitRef" class="icon-container"> <div v-if="hasCommitRef" class="icon-container hidden-xs">
<i <i
v-if="tag" v-if="tag"
class="fa fa-tag" class="fa fa-tag"
...@@ -125,7 +125,7 @@ ...@@ -125,7 +125,7 @@
<a <a
v-if="hasCommitRef" v-if="hasCommitRef"
class="ref-name" class="ref-name hidden-xs"
:href="commitRef.ref_url"> :href="commitRef.ref_url">
{{commitRef.name}} {{commitRef.name}}
</a> </a>
......
...@@ -28,28 +28,37 @@ ...@@ -28,28 +28,37 @@
}; };
</script> </script>
<template> <template>
<table class="table ci-table"> <div class="ci-table">
<thead> <div
<tr> class="gl-responsive-table-row table-row-header"
<th class="js-pipeline-status pipeline-status">Status</th> role="row">
<th class="js-pipeline-info pipeline-info">Pipeline</th> <div
<th class="js-pipeline-commit pipeline-commit">Commit</th> class="table-section section-10 js-pipeline-status pipeline-status"
<th class="js-pipeline-stages pipeline-stages">Stages</th> role="rowheader">
<th class="js-pipeline-date pipeline-date"></th> Status
<th class="js-pipeline-actions pipeline-actions"></th> </div>
</tr> <div
</thead> class="table-section section-15 js-pipeline-info pipeline-info"
<tbody> role="rowheader">
<template Pipeline
v-for="model in pipelines" </div>
:model="model"> <div
<tr class="table-section section-25 js-pipeline-commit pipeline-commit"
is="pipelines-table-row-component" role="rowheader">
:pipeline="model" Commit
:service="service" </div>
:update-graph-dropdown="updateGraphDropdown" <div
/> class="table-section section-15 js-pipeline-stages pipeline-stages"
</template> role="rowheader">
</tbody> Stages
</table> </div>
</div>
<pipelines-table-row-component
v-for="model in pipelines"
:key="model.id"
:pipeline="model"
:service="service"
:update-graph-dropdown="updateGraphDropdown"
/>
</div>
</template> </template>
...@@ -200,47 +200,74 @@ export default { ...@@ -200,47 +200,74 @@ export default {
} }
return {}; return {};
}, },
displayPipelineActions() {
return this.pipeline.flags.retryable ||
this.pipeline.flags.cancelable ||
this.pipeline.details.manual_actions.length ||
this.pipeline.details.artifacts.length;
},
}, },
}; };
</script> </script>
<template> <template>
<tr class="commit"> <div class="commit gl-responsive-table-row">
<td class="commit-link"> <div class="table-section section-10 commit-link">
<ci-badge :status="pipelineStatus" /> <div class="table-mobile-header"
</td> role="rowheader">
Status
</div>
<div class="table-mobile-content">
<ci-badge :status="pipelineStatus"/>
</div>
</div>
<pipeline-url :pipeline="pipeline" /> <pipeline-url :pipeline="pipeline" />
<td> <div class="table-section section-25">
<commit-component <div
:tag="commitTag" class="table-mobile-header"
:commit-ref="commitRef" role="rowheader">
:commit-url="commitUrl" Commit
:short-sha="commitShortSha" </div>
:title="commitTitle" <div class="table-mobile-content">
:author="commitAuthor" <commit-component
/> :tag="commitTag"
</td> :commit-ref="commitRef"
:commit-url="commitUrl"
<td class="stage-cell"> :short-sha="commitShortSha"
<div class="stage-container dropdown js-mini-pipeline-graph" :title="commitTitle"
v-if="pipeline.details.stages.length > 0" :author="commitAuthor"/>
v-for="stage in pipeline.details.stages"> </div>
</div>
<pipeline-stage <div class="table-section section-wrap section-15 stage-cell">
:stage="stage" <div
:update-dropdown="updateGraphDropdown" class="table-mobile-header"
/> role="rowheader">
Stages
</div>
<div class="table-mobile-content">
<div class="stage-container dropdown js-mini-pipeline-graph"
v-if="pipeline.details.stages.length > 0"
v-for="stage in pipeline.details.stages">
<pipeline-stage
:stage="stage"
:update-dropdown="updateGraphDropdown"
/>
</div>
</div> </div>
</td> </div>
<pipelines-timeago <pipelines-timeago
:duration="pipelineDuration" :duration="pipelineDuration"
:finished-time="pipelineFinishedAt" :finished-time="pipelineFinishedAt"
/> />
<td class="pipeline-actions"> <div
<div class="pull-right btn-group"> v-if="displayPipelineActions"
class="table-section section-20 table-button-footer pipeline-actions">
<div class="btn-group table-action-buttons">
<pipelines-actions-component <pipelines-actions-component
v-if="pipeline.details.manual_actions.length" v-if="pipeline.details.manual_actions.length"
:actions="pipeline.details.manual_actions" :actions="pipeline.details.manual_actions"
...@@ -249,6 +276,7 @@ export default { ...@@ -249,6 +276,7 @@ export default {
<pipelines-artifacts-component <pipelines-artifacts-component
v-if="pipeline.details.artifacts.length" v-if="pipeline.details.artifacts.length"
class="hidden-xs hidden-sm"
:artifacts="pipeline.details.artifacts" :artifacts="pipeline.details.artifacts"
/> />
...@@ -271,6 +299,6 @@ export default { ...@@ -271,6 +299,6 @@ export default {
confirm-action-message="Are you sure you want to cancel this pipeline?" confirm-action-message="Are you sure you want to cancel this pipeline?"
/> />
</div> </div>
</td> </div>
</tr> </div>
</template> </template>
...@@ -63,6 +63,7 @@ ...@@ -63,6 +63,7 @@
background-color: $gray-light; background-color: $gray-light;
text-align: right; text-align: right;
padding: 8px $gl-padding; padding: 8px $gl-padding;
border-bottom: 1px solid $border-color;
@media (max-width: $screen-xs-max) { @media (max-width: $screen-xs-max) {
text-align: left; text-align: left;
......
...@@ -152,7 +152,7 @@ ...@@ -152,7 +152,7 @@
} }
.value-container { .value-container {
background-color: $filter-value-selected-color; box-shadow: inset 0 0 0 100px $filtered-search-term-shadow-color;
} }
} }
......
...@@ -125,10 +125,11 @@ label { ...@@ -125,10 +125,11 @@ label {
.select-wrapper { .select-wrapper {
position: relative; position: relative;
.fa-caret-down { .fa-chevron-down {
position: absolute; position: absolute;
font-size: 10px;
right: 10px; right: 10px;
top: 10px; top: 12px;
color: $gray-darkest; color: $gray-darkest;
pointer-events: none; pointer-events: none;
} }
...@@ -138,6 +139,12 @@ label { ...@@ -138,6 +139,12 @@ label {
padding-left: 10px; padding-left: 10px;
padding-right: 10px; padding-right: 10px;
-webkit-appearance: none; -webkit-appearance: none;
-moz-appearance: none;
appearance: none;
&::-ms-expand {
display: none;
}
} }
.form-control-inline { .form-control-inline {
......
...@@ -174,3 +174,14 @@ ...@@ -174,3 +174,14 @@
white-space: nowrap; white-space: nowrap;
} }
} }
@media(max-width: $screen-xs-max) {
.atwho-view-ul {
width: 350px;
}
.atwho-view ul li {
overflow: hidden;
text-overflow: ellipsis;
}
}
...@@ -59,4 +59,8 @@ ...@@ -59,4 +59,8 @@
margin: 0 2px 0 3px; margin: 0 2px 0 3px;
} }
} }
.ci-status {
margin-right: 10px;
}
} }
...@@ -31,14 +31,6 @@ ...@@ -31,14 +31,6 @@
align-items: center; align-items: center;
} }
.panel-empty-heading {
border-bottom: 0;
}
.panel-body {
padding: $gl-padding;
}
.left { .left {
flex: 1 1 auto; flex: 1 1 auto;
} }
......
...@@ -36,13 +36,58 @@ ...@@ -36,13 +36,58 @@
align-self: stretch; align-self: stretch;
padding: 10px; padding: 10px;
align-items: center; align-items: center;
height: 62px; min-height: 62px;
&:not(:first-of-type) { &:not(:first-of-type) {
border-top: 1px solid $white-normal; border-top: 1px solid $white-normal;
} }
} }
} }
&.section-wrap {
white-space: normal;
@media (max-width: $screen-sm-max) {
flex-wrap: wrap;
}
}
}
}
.table-button-footer {
@media (min-width: $screen-md-min) {
text-align: right;
}
@media (max-width: $screen-sm-max) {
background-color: $gray-normal;
align-self: stretch;
border-top: 1px solid $border-color;
.table-action-buttons {
padding: 10px 5px;
display: flex;
.btn {
border-radius: 3px;
}
> .btn-group,
> .external-url,
> .btn {
flex: 1 1 28px;
margin: 0 5px;
}
.dropdown-new {
width: 100%;
}
.dropdown-menu {
min-width: initial;
}
}
} }
} }
...@@ -56,6 +101,7 @@ ...@@ -56,6 +101,7 @@
.table-mobile-header { .table-mobile-header {
color: $gl-text-color-secondary; color: $gl-text-color-secondary;
text-align: left;
@include flex-max-width(40); @include flex-max-width(40);
@media (min-width: $screen-md-min) { @media (min-width: $screen-md-min) {
......
...@@ -18,19 +18,28 @@ ...@@ -18,19 +18,28 @@
background-image: none; background-image: none;
background-color: transparent; background-color: transparent;
border: none; border: none;
padding-top: 6px; padding-top: 12px;
padding-right: 10px; padding-right: 20px;
font-size: 10px;
b { b {
display: inline-block; display: none;
width: 0; }
height: 0;
margin-left: 2px; &::after {
vertical-align: middle; content: "\f078";
border-top: 5px dashed; position: absolute;
border-right: 5px solid transparent; z-index: 1;
border-left: 5px solid transparent; text-align: center;
pointer-events: none;
box-sizing: border-box;
color: $gray-darkest; color: $gray-darkest;
display: inline-block;
font: normal normal normal 14px/1 FontAwesome;
font-size: inherit;
text-rendering: auto;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
} }
} }
......
...@@ -287,6 +287,7 @@ $dropdown-toggle-active-border-color: darken($border-color, 14%); ...@@ -287,6 +287,7 @@ $dropdown-toggle-active-border-color: darken($border-color, 14%);
/* /*
* Filtered Search * Filtered Search
*/ */
$filtered-search-term-shadow-color: rgba(0, 0, 0, 0.09);
$dropdown-hover-color: $blue-400; $dropdown-hover-color: $blue-400;
/* /*
......
@import "framework/variables";
// NOTE: This stylesheet is for the exclusive use of the `devise_mailer` layout
// used for Devise email templates, and _should not_ be included in any
// application stylesheets.
//
// Styles defined here are embedded directly into the resulting email HTML via
// the `premailer` gem.
$body-background-color: #363636;
$message-background-color: #fafafa;
$header-color: #6b4fbb;
$body-color: #444;
$cta-color: #e14329;
$footer-link-color: #7e7e7e;
$font-family: Helvetica, Arial, sans-serif;
body {
background-color: $body-background-color;
font-family: $font-family;
margin: 0;
padding: 0;
}
table {
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
border: 0;
border-collapse: separate;
&#wrapper {
background-color: $body-background-color;
width: 100%;
}
&#header {
margin: 0 auto;
text-align: left;
width: 600px;
& > td {
text-align: center;
}
}
&#body {
background-color: $message-background-color;
border: 1px solid $black;
border-radius: 4px;
margin: 0 auto;
width: 600px;
}
&#footer {
color: $footer-link-color;
font-size: 14px;
text-align: center;
width: 100%;
}
td {
&#body-container {
padding: 20px 40px;
}
}
}
.center {
text-align: center;
}
#logo {
border: none;
outline: none;
min-height: 88px;
width: 134px;
}
#content {
h2 {
color: $header-color;
font-size: 30px;
font-weight: 400;
line-height: 34px;
margin-top: 0;
}
p {
color: $body-color;
font-size: 17px;
line-height: 24px;
margin-bottom: 0;
}
}
#cta {
border: 1px solid $cta-color;
border-radius: 3px;
display: inline-block;
margin: 20px 0;
padding: 12px 24px;
a {
background-color: $message-background-color;
color: $cta-color;
display: inline-block;
text-decoration: none;
}
}
#tanuki {
padding: 40px 0 0;
img {
border: none;
outline: none;
width: 37px;
min-height: 36px;
}
}
#tagline {
font-size: 22px;
font-weight: 100;
padding: 4px 0 40px;
}
#social {
padding: 0 10px 20px;
width: 600px;
word-spacing: 20px;
a {
color: $footer-link-color;
text-decoration: none;
}
}
...@@ -274,43 +274,6 @@ ...@@ -274,43 +274,6 @@
} }
.gl-responsive-table-row { .gl-responsive-table-row {
.environments-actions {
@media (min-width: $screen-md-min) {
text-align: right;
}
@media (max-width: $screen-sm-max) {
background-color: $gray-normal;
align-self: stretch;
border-top: 1px solid $border-color;
.environment-action-buttons {
padding: 10px 5px;
display: flex;
.btn {
border-radius: 3px;
}
> .btn-group,
> .external-url,
> .btn {
flex: 1;
flex-basis: 28px;
margin: 0 5px;
}
.dropdown-new {
width: 100%;
}
.dropdown-menu {
min-width: initial;
}
}
}
}
.branch-commit { .branch-commit {
max-width: 100%; max-width: 100%;
} }
......
...@@ -111,8 +111,8 @@ ...@@ -111,8 +111,8 @@
} }
} }
.issues-sortable-list, .milestone-issues-list,
.merge_requests-sortable-list { .milestone-merge_requests-list {
.issuable-detail { .issuable-detail {
display: block; display: block;
margin-top: 7px; margin-top: 7px;
...@@ -197,8 +197,6 @@ ...@@ -197,8 +197,6 @@
.issuable-row { .issuable-row {
background-color: $white-light; background-color: $white-light;
cursor: -webkit-grab;
cursor: grab;
} }
// EE-only // EE-only
......
...@@ -509,11 +509,6 @@ ul.notes { ...@@ -509,11 +509,6 @@ ul.notes {
display: inline; display: inline;
line-height: 20px; line-height: 20px;
@include notes-media('min', $screen-sm-min) {
margin-left: 10px;
line-height: 24px;
}
.fa { .fa {
color: $gray-darkest; color: $gray-darkest;
position: relative; position: relative;
......
...@@ -37,17 +37,13 @@ ...@@ -37,17 +37,13 @@
.table-holder { .table-holder {
width: 100%; width: 100%;
@media (max-width: $screen-sm-max) {
overflow: auto;
}
} }
.commit-title { .commit-title {
margin: 0; margin: 0;
} }
.table.ci-table { .ci-table {
.label { .label {
margin-bottom: 3px; margin-bottom: 3px;
...@@ -57,11 +53,6 @@ ...@@ -57,11 +53,6 @@
color: $black; color: $black;
} }
.stage-cell {
min-width: 130px; // Guarantees we show at least 4 stages in line
width: 20%;
}
.pipelines-time-ago { .pipelines-time-ago {
text-align: right; text-align: right;
} }
...@@ -135,43 +126,7 @@ ...@@ -135,43 +126,7 @@
} }
} }
.table.ci-table { .ci-table {
&.builds-page tbody tr {
height: 71px;
}
tr {
th {
padding: 16px 8px;
border: none;
}
td {
padding: 10px 8px;
}
td.environments-actions {
padding-right: 0;
}
td.stage-cell {
padding: 10px 0;
}
td.deploy-board-container {
padding: 0;
}
.commit-link {
padding: 9px 8px 10px 2px;
}
}
tbody {
border-top-width: 1px;
}
.build.retried { .build.retried {
background-color: $gray-lightest; background-color: $gray-lightest;
} }
...@@ -225,13 +180,6 @@ ...@@ -225,13 +180,6 @@
color: $gl-link-color; color: $gl-link-color;
} }
.commit-title {
max-width: 225px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.label { .label {
margin-right: 4px; margin-right: 4px;
} }
...@@ -284,11 +232,7 @@ ...@@ -284,11 +232,7 @@
} }
.stage-cell { .stage-cell {
font-size: 0; .mini-pipeline-graph-dropdown-toggle svg {
padding: 0 4px;
> .stage-container > div > button > span > svg,
> .stage-container > button > svg {
height: 22px; height: 22px;
width: 22px; width: 22px;
position: absolute; position: absolute;
...@@ -656,6 +600,23 @@ ...@@ -656,6 +600,23 @@
font-weight: normal; font-weight: normal;
} }
@mixin mini-pipeline-graph-color($color-light, $color-main, $color-dark) {
border-color: $color-main;
color: $color-main;
&:hover,
&:focus,
&:active {
background-color: $color-light;
border-color: $color-dark;
color: $color-dark;
svg {
fill: $color-dark;
}
}
}
// Dropdown button in mini pipeline graph // Dropdown button in mini pipeline graph
.mini-pipeline-graph-dropdown-toggle, .mini-pipeline-graph-dropdown-toggle,
.linked-pipeline-mini-item { .linked-pipeline-mini-item {
...@@ -696,100 +657,32 @@ ...@@ -696,100 +657,32 @@
// Dropdown button animation in mini pipeline graph // Dropdown button animation in mini pipeline graph
&.ci-status-icon-success { &.ci-status-icon-success {
border-color: $green-500; @include mini-pipeline-graph-color($green-50, $green-500, $green-600);
color: $green-500;
&:hover,
&:focus,
&:active {
background-color: $green-50;
border-color: $green-600;
color: $green-600;
svg {
fill: $green-600;
}
}
} }
&.ci-status-icon-failed { &.ci-status-icon-failed {
border-color: $red-500; @include mini-pipeline-graph-color($red-50, $red-500, $red-600);
color: $red-500;
&:hover,
&:focus,
&:active {
background-color: $red-50;
border-color: $red-600;
color: $red-600;
svg {
fill: $red-600;
}
}
} }
&.ci-status-icon-pending, &.ci-status-icon-pending,
&.ci-status-icon-success_with_warnings { &.ci-status-icon-success_with_warnings {
border-color: $orange-500; @include mini-pipeline-graph-color($orange-50, $orange-500, $orange-600);
color: $orange-500;
&:hover,
&:focus,
&:active {
background-color: $orange-50;
border-color: $orange-600;
color: $orange-600;
svg {
fill: $orange-600;
}
}
} }
&.ci-status-icon-running { &.ci-status-icon-running {
border-color: $blue-400; @include mini-pipeline-graph-color($blue-50, $blue-400, $blue-600);
color: $blue-400;
&:hover,
&:focus,
&:active {
background-color: $blue-50;
border-color: $blue-600;
color: $blue-600;
svg {
fill: $blue-600;
}
}
} }
&.ci-status-icon-canceled, &.ci-status-icon-canceled,
&.ci-status-icon-disabled, &.ci-status-icon-disabled,
&.ci-status-icon-not-found, &.ci-status-icon-not-found,
&.ci-status-icon-manual { &.ci-status-icon-manual {
border-color: $gl-text-color; @include mini-pipeline-graph-color(rgba($gl-text-color, 0.1), $gl-text-color, $gl-text-color);
color: $gl-text-color;
&:hover,
&:focus,
&:active {
background-color: rgba($gl-text-color, 0.1);
border-color: $gl-text-color;
}
} }
&.ci-status-icon-created, &.ci-status-icon-created,
&.ci-status-icon-skipped { &.ci-status-icon-skipped {
border-color: $gray-darkest; @include mini-pipeline-graph-color(rgba($gray-darkest, 0.1), $gray-darkest, $gray-darkest);
color: $gray-darkest;
&:hover,
&:focus,
&:active {
background-color: rgba($gray-darkest, 0.1);
border-color: $gray-darkest;
}
} }
} }
...@@ -868,6 +761,10 @@ ...@@ -868,6 +761,10 @@
top: 1px; top: 1px;
vertical-align: text-bottom; vertical-align: text-bottom;
position: relative; position: relative;
@media (max-width: $screen-xs-max) {
max-width: 60%;
}
} }
// status icon on the left // status icon on the left
...@@ -958,6 +855,11 @@ ...@@ -958,6 +855,11 @@
left: 50%; left: 50%;
transform: translate(-50%, 0); transform: translate(-50%, 0);
border-width: 0 5px 6px; border-width: 0 5px 6px;
@media (max-width: $screen-sm-max) {
left: 100%;
margin-left: -12px;
}
} }
&::before { &::before {
...@@ -975,9 +877,15 @@ ...@@ -975,9 +877,15 @@
* Center dropdown menu in mini graph * Center dropdown menu in mini graph
*/ */
.mini-pipeline-graph-dropdown-menu.dropdown-menu { .mini-pipeline-graph-dropdown-menu.dropdown-menu {
right: auto; transform: translate(-80%, 0);
left: 50%; min-width: 150px;
transform: translate(-50%, 0);
@media(min-width: $screen-md-min) {
transform: translate(-50%, 0);
right: auto;
left: 50%;
min-width: 240px;
}
} }
/** /**
* Terminal * Terminal
......
.container-fluid { @mixin status-color($color-light, $color-main, $color-dark) {
.ci-status { color: $color-main;
padding: 2px 7px 4px; border-color: $color-main;
margin-right: 10px;
border: 1px solid $gray-darker;
white-space: nowrap;
border-radius: 4px;
&:hover,
&:focus {
text-decoration: none;
}
svg {
height: 13px;
width: 13px;
position: relative;
top: 2px;
overflow: visible;
}
&.ci-failed { &:not(span):hover {
color: $red-500; background-color: $color-light;
border-color: $red-500; color: $color-dark;
border-color: $color-dark;
&:not(span):hover { svg {
background-color: $red-50; fill: $color-dark;
color: $red-600;
border-color: $red-600;
svg {
fill: $red-600;
}
}
svg {
fill: $red-500;
}
} }
}
&.ci-success { svg {
color: $green-600; fill: $color-main;
border-color: $green-500; }
}
&:not(span):hover { .ci-status {
background-color: $green-50; padding: 2px 7px 4px;
color: $green-700; border: 1px solid $gray-darker;
border-color: $green-600; white-space: nowrap;
border-radius: 4px;
svg { &:hover,
fill: $green-600; &:focus {
} text-decoration: none;
} }
svg { svg {
fill: $green-500; height: 13px;
} width: 13px;
} position: relative;
top: 2px;
overflow: visible;
}
&.ci-canceled, &.ci-failed {
&.ci-disabled { @include status-color($red-50, $red-500, $red-600);
color: $gl-text-color; }
border-color: $gl-text-color;
&:not(span):hover { &.ci-success {
background-color: rgba($gl-text-color, .07); @include status-color($green-50, $green-500, $green-700);
} }
svg { &.ci-canceled,
fill: $gl-text-color; &.ci-disabled,
} &.ci-manual {
} color: $gl-text-color;
border-color: $gl-text-color;
&.ci-pending, &:not(span):hover {
&.ci-success_with_warnings, background-color: rgba($gl-text-color, .07);
&.ci-failed_with_warnings {
color: $orange-600;
border-color: $orange-500;
&:not(span):hover {
background-color: $orange-50;
color: $orange-700;
border-color: $orange-600;
svg {
fill: $orange-600;
}
}
svg {
fill: $orange-500;
}
} }
}
&.ci-info, &.ci-pending,
&.ci-running { &.ci-failed_with_warnings,
color: $blue-500; &.ci-success_with_warnings {
border-color: $blue-500; @include status-color($orange-50, $orange-500, $orange-700);
}
&:not(span):hover {
background-color: $blue-50;
color: $blue-600;
border-color: $blue-600;
svg {
fill: $blue-600;
}
}
svg {
fill: $blue-500;
}
}
&.ci-created, &.ci-info,
&.ci-skipped { &.ci-running {
color: $gl-text-color-secondary; @include status-color($blue-50, $blue-500, $blue-600);
border-color: $gl-text-color-secondary; }
&:not(span):hover { &.ci-created,
background-color: rgba($gl-text-color-secondary, .07); &.ci-skipped {
} color: $gl-text-color-secondary;
border-color: $gl-text-color-secondary;
svg { &:not(span):hover {
fill: $gl-text-color-secondary; background-color: rgba($gl-text-color-secondary, .07);
}
} }
&.ci-manual { svg {
color: $gl-text-color; fill: $gl-text-color-secondary;
border-color: $gl-text-color;
&:not(span):hover {
background-color: rgba($gl-text-color, .07);
}
svg {
fill: $gl-text-color;
}
} }
} }
} }
......
...@@ -6,7 +6,7 @@ module MilestoneActions ...@@ -6,7 +6,7 @@ module MilestoneActions
format.html { redirect_to milestone_redirect_path } format.html { redirect_to milestone_redirect_path }
format.json do format.json do
render json: tabs_json("shared/milestones/_merge_requests_tab", { render json: tabs_json("shared/milestones/_merge_requests_tab", {
merge_requests: @milestone.merge_requests, merge_requests: @milestone.sorted_merge_requests,
show_project_name: true show_project_name: true
}) })
end end
......
...@@ -2,7 +2,7 @@ class Projects::MilestonesController < Projects::ApplicationController ...@@ -2,7 +2,7 @@ class Projects::MilestonesController < Projects::ApplicationController
include MilestoneActions include MilestoneActions
before_action :module_enabled before_action :module_enabled
before_action :milestone, only: [:edit, :update, :destroy, :show, :sort_issues, :sort_merge_requests, :merge_requests, :participants, :labels] before_action :milestone, only: [:edit, :update, :destroy, :show, :merge_requests, :participants, :labels]
# Allow read any milestone # Allow read any milestone
before_action :authorize_read_milestone! before_action :authorize_read_milestone!
...@@ -86,22 +86,6 @@ class Projects::MilestonesController < Projects::ApplicationController ...@@ -86,22 +86,6 @@ class Projects::MilestonesController < Projects::ApplicationController
end end
end end
def sort_issues
@milestone.sort_issues(params['sortable_issue'].map(&:to_i))
render json: { saved: true }
end
def sort_merge_requests
@merge_requests = @milestone.merge_requests.where(id: params['sortable_merge_request'])
@merge_requests.each do |merge_request|
merge_request.position = params['sortable_merge_request'].index(merge_request.id.to_s) + 1
merge_request.save
end
render json: { saved: true }
end
protected protected
def milestone def milestone
......
...@@ -5,8 +5,10 @@ class GroupsFinder < UnionFinder ...@@ -5,8 +5,10 @@ class GroupsFinder < UnionFinder
end end
def execute def execute
groups = find_union(all_groups, Group).with_route.order_id_desc items = all_groups.map do |item|
by_parent(groups) by_parent(item)
end
find_union(items, Group).with_route.order_id_desc
end end
private private
...@@ -16,12 +18,22 @@ class GroupsFinder < UnionFinder ...@@ -16,12 +18,22 @@ class GroupsFinder < UnionFinder
def all_groups def all_groups
groups = [] groups = []
groups << current_user.authorized_groups if current_user if current_user
groups << Gitlab::GroupHierarchy.new(groups_for_ancestors, groups_for_descendants).all_groups
end
groups << Group.unscoped.public_to_user(current_user) groups << Group.unscoped.public_to_user(current_user)
groups groups
end end
def groups_for_ancestors
current_user.authorized_groups
end
def groups_for_descendants
current_user.groups
end
def by_parent(groups) def by_parent(groups)
return groups unless params[:parent] return groups unless params[:parent]
......
...@@ -46,6 +46,7 @@ class IssuableFinder ...@@ -46,6 +46,7 @@ class IssuableFinder
items = by_iids(items) items = by_iids(items)
items = by_milestone(items) items = by_milestone(items)
items = by_label(items) items = by_label(items)
items = by_created_at(items)
# Filtering by project HAS TO be the last because we use the project IDs yielded by the issuable query thus far # Filtering by project HAS TO be the last because we use the project IDs yielded by the issuable query thus far
items = by_project(items) items = by_project(items)
...@@ -432,6 +433,18 @@ class IssuableFinder ...@@ -432,6 +433,18 @@ class IssuableFinder
params[:non_archived].present? ? items.non_archived : items params[:non_archived].present? ? items.non_archived : items
end end
def by_created_at(items)
if params[:created_after].present?
items = items.where(items.klass.arel_table[:created_at].gteq(params[:created_after]))
end
if params[:created_before].present?
items = items.where(items.klass.arel_table[:created_at].lteq(params[:created_before]))
end
items
end
def current_user_related? def current_user_related?
params[:scope] == 'created-by-me' || params[:scope] == 'authored' || params[:scope] == 'assigned-to-me' params[:scope] == 'created-by-me' || params[:scope] == 'authored' || params[:scope] == 'assigned-to-me'
end end
......
...@@ -170,9 +170,9 @@ module ApplicationHelper ...@@ -170,9 +170,9 @@ module ApplicationHelper
css_classes = short_format ? 'js-short-timeago' : 'js-timeago' css_classes = short_format ? 'js-short-timeago' : 'js-timeago'
css_classes << " #{html_class}" unless html_class.blank? css_classes << " #{html_class}" unless html_class.blank?
element = content_tag :time, time.strftime("%b %d, %Y"), element = content_tag :time, l(time, format: "%b %d, %Y"),
class: css_classes, class: css_classes,
title: time.to_time.in_time_zone.to_s(:medium), title: l(time.to_time.in_time_zone, format: :timeago_tooltip),
datetime: time.to_time.getutc.iso8601, datetime: time.to_time.getutc.iso8601,
data: { data: {
toggle: 'tooltip', toggle: 'tooltip',
......
...@@ -66,12 +66,12 @@ module DiffHelper ...@@ -66,12 +66,12 @@ module DiffHelper
discussions_left = discussions_right = nil discussions_left = discussions_right = nil
if left && (left.unchanged? || left.discussable?) if left && left.discussable? && (left.unchanged? || left.removed?)
line_code = diff_file.line_code(left) line_code = diff_file.line_code(left)
discussions_left = @grouped_diff_discussions[line_code] discussions_left = @grouped_diff_discussions[line_code]
end end
if right&.discussable? if right && right.discussable? && right.added?
line_code = diff_file.line_code(right) line_code = diff_file.line_code(right)
discussions_right = @grouped_diff_discussions[line_code] discussions_right = @grouped_diff_discussions[line_code]
end end
......
...@@ -66,4 +66,17 @@ module EmailsHelper ...@@ -66,4 +66,17 @@ module EmailsHelper
) )
end end
end end
def email_default_heading(text)
content_tag :h1, text, style: [
"font-family:'Helvetica Neue',Helvetica,Arial,sans-serif",
'color:#333333',
'font-size:18px',
'font-weight:400',
'line-height:1.4',
'padding:0',
'margin:0',
'text-align:center'
].join(';')
end
end end
...@@ -80,7 +80,7 @@ module ProjectsHelper ...@@ -80,7 +80,7 @@ module ProjectsHelper
end end
def remove_fork_project_message(project) def remove_fork_project_message(project)
_("You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?") % _("You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?") %
{ forked_from_project: @project.forked_from_project.name_with_namespace } { forked_from_project: @project.forked_from_project.name_with_namespace }
end end
...@@ -151,14 +151,21 @@ module ProjectsHelper ...@@ -151,14 +151,21 @@ module ProjectsHelper
disabled: disabled_option disabled: disabled_option
) )
content_tag( content_tag :div, class: "select-wrapper" do
:select, concat(
options, content_tag(
name: "project[project_feature_attributes][#{field}]", :select,
id: "project_project_feature_attributes_#{field}", options,
class: "pull-right form-control #{repo_children_classes(field)}", name: "project[project_feature_attributes][#{field}]",
data: { field: field } id: "project_project_feature_attributes_#{field}",
).html_safe class: "pull-right form-control select-control #{repo_children_classes(field)} ",
data: { field: field }
)
)
concat(
icon('chevron-down')
)
end.html_safe
end end
def link_to_autodeploy_doc def link_to_autodeploy_doc
......
...@@ -2,7 +2,9 @@ class DeviseMailer < Devise::Mailer ...@@ -2,7 +2,9 @@ class DeviseMailer < Devise::Mailer
default from: "#{Gitlab.config.gitlab.email_display_name} <#{Gitlab.config.gitlab.email_from}>" default from: "#{Gitlab.config.gitlab.email_display_name} <#{Gitlab.config.gitlab.email_from}>"
default reply_to: Gitlab.config.gitlab.email_reply_to default reply_to: Gitlab.config.gitlab.email_reply_to
layout 'devise_mailer' layout 'mailer/devise'
helper EmailsHelper
protected protected
......
...@@ -67,7 +67,6 @@ module Issuable ...@@ -67,7 +67,6 @@ module Issuable
scope :authored, ->(user) { where(author_id: user) } scope :authored, ->(user) { where(author_id: user) }
scope :recent, -> { reorder(id: :desc) } scope :recent, -> { reorder(id: :desc) }
scope :order_position_asc, -> { reorder(position: :asc) }
scope :of_projects, ->(ids) { where(project_id: ids) } scope :of_projects, ->(ids) { where(project_id: ids) }
scope :of_milestones, ->(ids) { where(milestone_id: ids) } scope :of_milestones, ->(ids) { where(milestone_id: ids) }
scope :with_milestone, ->(title) { left_joins_milestones.where(milestones: { title: title }) } scope :with_milestone, ->(title) { left_joins_milestones.where(milestones: { title: title }) }
...@@ -142,7 +141,6 @@ module Issuable ...@@ -142,7 +141,6 @@ module Issuable
when 'upvotes_desc' then order_upvotes_desc when 'upvotes_desc' then order_upvotes_desc
when 'label_priority' then order_labels_priority(excluded_labels: excluded_labels) when 'label_priority' then order_labels_priority(excluded_labels: excluded_labels)
when 'priority' then order_due_date_and_labels_priority(excluded_labels: excluded_labels) when 'priority' then order_due_date_and_labels_priority(excluded_labels: excluded_labels)
when 'position_asc' then order_position_asc
else else
order_by(method) order_by(method)
end end
......
...@@ -40,10 +40,18 @@ module Milestoneish ...@@ -40,10 +40,18 @@ module Milestoneish
def issues_visible_to_user(user) def issues_visible_to_user(user)
memoize_per_user(user, :issues_visible_to_user) do memoize_per_user(user, :issues_visible_to_user) do
IssuesFinder.new(user, issues_finder_params) IssuesFinder.new(user, issues_finder_params)
.execute.includes(:assignees).where(milestone_id: milestoneish_ids) .execute.preload(:assignees).where(milestone_id: milestoneish_ids)
end end
end end
def sorted_issues(user)
issues_visible_to_user(user).preload_associations.sort('label_priority')
end
def sorted_merge_requests
merge_requests.sort('label_priority')
end
def upcoming? def upcoming?
start_date && start_date.future? start_date && start_date.future?
end end
......
...@@ -12,6 +12,9 @@ class Issue < ActiveRecord::Base ...@@ -12,6 +12,9 @@ class Issue < ActiveRecord::Base
include Elastic::IssuesSearch include Elastic::IssuesSearch
include FasterCacheKeys include FasterCacheKeys
include RelativePositioning include RelativePositioning
include IgnorableColumn
ignore_column :position
WEIGHT_RANGE = 1..9 WEIGHT_RANGE = 1..9
WEIGHT_ALL = 'Everything'.freeze WEIGHT_ALL = 'Everything'.freeze
...@@ -54,7 +57,7 @@ class Issue < ActiveRecord::Base ...@@ -54,7 +57,7 @@ class Issue < ActiveRecord::Base
scope :created_after, -> (datetime) { where("created_at >= ?", datetime) } scope :created_after, -> (datetime) { where("created_at >= ?", datetime) }
scope :include_associations, -> { includes(:labels, project: :namespace) } scope :preload_associations, -> { preload(:labels, project: :namespace) }
after_save :expire_etag_cache after_save :expire_etag_cache
......
...@@ -47,7 +47,7 @@ class LegacyDiffNote < Note ...@@ -47,7 +47,7 @@ class LegacyDiffNote < Note
end end
def for_line?(line) def for_line?(line)
!line.meta? && diff_file.line_code(line) == self.line_code line.discussable? && diff_file.line_code(line) == self.line_code
end end
def original_line_code def original_line_code
......
...@@ -6,6 +6,9 @@ class MergeRequest < ActiveRecord::Base ...@@ -6,6 +6,9 @@ class MergeRequest < ActiveRecord::Base
include Sortable include Sortable
include Elastic::MergeRequestsSearch include Elastic::MergeRequestsSearch
include Approvable include Approvable
include IgnorableColumn
ignore_column :position
belongs_to :target_project, class_name: "Project" belongs_to :target_project, class_name: "Project"
belongs_to :source_project, class_name: "Project" belongs_to :source_project, class_name: "Project"
......
...@@ -10,6 +10,7 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -10,6 +10,7 @@ class MergeRequestDiff < ActiveRecord::Base
VALID_CLASSES = [Hash, Rugged::Patch, Rugged::Diff::Delta].freeze VALID_CLASSES = [Hash, Rugged::Patch, Rugged::Diff::Delta].freeze
belongs_to :merge_request belongs_to :merge_request
has_many :merge_request_diff_files, -> { order(:merge_request_diff_id, :relative_order) }
serialize :st_commits # rubocop:disable Cop/ActiverecordSerialize serialize :st_commits # rubocop:disable Cop/ActiverecordSerialize
serialize :st_diffs # rubocop:disable Cop/ActiverecordSerialize serialize :st_diffs # rubocop:disable Cop/ActiverecordSerialize
...@@ -91,7 +92,7 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -91,7 +92,7 @@ class MergeRequestDiff < ActiveRecord::Base
head_commit_sha).diffs(options) head_commit_sha).diffs(options)
else else
@raw_diffs ||= {} @raw_diffs ||= {}
@raw_diffs[options] ||= load_diffs(st_diffs, options) @raw_diffs[options] ||= load_diffs(options)
end end
end end
...@@ -253,24 +254,44 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -253,24 +254,44 @@ class MergeRequestDiff < ActiveRecord::Base
update_columns_serialized(new_attributes) update_columns_serialized(new_attributes)
end end
def dump_diffs(diffs) def create_merge_request_diff_files(diffs)
if diffs.respond_to?(:map) rows = diffs.map.with_index do |diff, index|
diffs.map(&:to_hash) diff.to_hash.merge(
merge_request_diff_id: self.id,
relative_order: index
)
end end
Gitlab::Database.bulk_insert('merge_request_diff_files', rows)
end end
def load_diffs(raw, options) def load_diffs(options)
if valid_raw_diff?(raw) return Gitlab::Git::DiffCollection.new([]) unless diffs_from_database
if paths = options[:paths]
raw = raw.select do |diff|
paths.include?(diff[:old_path]) || paths.include?(diff[:new_path])
end
end
Gitlab::Git::DiffCollection.new(raw, options) raw = diffs_from_database
else
Gitlab::Git::DiffCollection.new([]) if paths = options[:paths]
raw = raw.select do |diff|
paths.include?(diff[:old_path]) || paths.include?(diff[:new_path])
end
end end
Gitlab::Git::DiffCollection.new(raw, options)
end
def diffs_from_database
return @diffs_from_database if defined?(@diffs_from_database)
@diffs_from_database =
if st_diffs.present?
if valid_raw_diff?(st_diffs)
st_diffs
end
elsif merge_request_diff_files.present?
merge_request_diff_files
.as_json(only: Gitlab::Git::Diff::SERIALIZE_KEYS)
.map(&:with_indifferent_access)
end
end end
# Load diffs between branches related to current merge request diff from repo # Load diffs between branches related to current merge request diff from repo
...@@ -285,11 +306,10 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -285,11 +306,10 @@ class MergeRequestDiff < ActiveRecord::Base
new_attributes[:real_size] = diff_collection.real_size new_attributes[:real_size] = diff_collection.real_size
if diff_collection.any? if diff_collection.any?
new_diffs = dump_diffs(diff_collection)
new_attributes[:state] = :collected new_attributes[:state] = :collected
end
new_attributes[:st_diffs] = new_diffs || [] create_merge_request_diff_files(diff_collection)
end
# Set our state to 'overflow' to make the #empty? and #collected? # Set our state to 'overflow' to make the #empty? and #collected?
# methods (generated by StateMachine) return false. # methods (generated by StateMachine) return false.
......
class MergeRequestDiffFile < ActiveRecord::Base
include Gitlab::EncodingHelper
belongs_to :merge_request_diff
def utf8_diff
return '' if diff.blank?
encode_utf8(diff) if diff.respond_to?(:encoding)
end
end
...@@ -166,38 +166,6 @@ class Milestone < ActiveRecord::Base ...@@ -166,38 +166,6 @@ class Milestone < ActiveRecord::Base
write_attribute(:title, sanitize_title(value)) if value.present? write_attribute(:title, sanitize_title(value)) if value.present?
end end
# Sorts the issues for the given IDs.
#
# This method runs a single SQL query using a CASE statement to update the
# position of all issues in the current milestone (scoped to the list of IDs).
#
# Given the ids [10, 20, 30] this method produces a SQL query something like
# the following:
#
# UPDATE issues
# SET position = CASE
# WHEN id = 10 THEN 1
# WHEN id = 20 THEN 2
# WHEN id = 30 THEN 3
# ELSE position
# END
# WHERE id IN (10, 20, 30);
#
# This method expects that the IDs given in `ids` are already Fixnums.
def sort_issues(ids)
pairs = []
ids.each_with_index do |id, index|
pairs << id
pairs << index + 1
end
conditions = 'WHEN id = ? THEN ? ' * ids.length
issues.where(id: ids)
.update_all(["position = CASE #{conditions} ELSE position END", *pairs])
end
private private
def milestone_format_reference(format = :iid) def milestone_format_reference(format = :iid)
......
...@@ -41,10 +41,8 @@ class NotificationSetting < ActiveRecord::Base ...@@ -41,10 +41,8 @@ class NotificationSetting < ActiveRecord::Base
:success_pipeline :success_pipeline
].freeze ].freeze
store :events, accessors: EMAIL_EVENTS, coder: JSON store :events, coder: JSON
before_save :convert_events
before_create :set_events
before_save :events_to_boolean
def self.find_or_create_for(source) def self.find_or_create_for(source)
setting = find_or_initialize_by(source: source) setting = find_or_initialize_by(source: source)
...@@ -56,21 +54,18 @@ class NotificationSetting < ActiveRecord::Base ...@@ -56,21 +54,18 @@ class NotificationSetting < ActiveRecord::Base
setting setting
end end
# Set all event attributes to false when level is not custom or being initialized for UX reasons # 1. Check if this event has a value stored in its database column.
def set_events # 2. If it does, return that value.
return if custom? # 3. If it doesn't (the value is nil), return the value from the serialized
# JSON hash in `events`.
self.events = {} (EMAIL_EVENTS - [:failed_pipeline]).each do |event|
end define_method(event) do
bool = super()
# Validates store accessors values as boolean bool.nil? ? !!events[event] : bool
# It is a text field so it does not cast correct boolean values in JSON
def events_to_boolean
EMAIL_EVENTS.each do |event|
bool = ActiveRecord::ConnectionAdapters::Column::TRUE_VALUES.include?(public_send(event))
events[event] = bool
end end
alias_method :"#{event}?", event
end end
# Allow people to receive failed pipeline notifications if they already have # Allow people to receive failed pipeline notifications if they already have
...@@ -78,7 +73,23 @@ class NotificationSetting < ActiveRecord::Base ...@@ -78,7 +73,23 @@ class NotificationSetting < ActiveRecord::Base
# custom settings. # custom settings.
def failed_pipeline def failed_pipeline
bool = super bool = super
bool = events[:failed_pipeline] if bool.nil?
bool.nil? || bool bool.nil? || bool
end end
alias_method :failed_pipeline?, :failed_pipeline
def event_enabled?(event)
respond_to?(event) && public_send(event)
end
def convert_events
return if events_before_type_cast.nil?
EMAIL_EVENTS.each do |event|
write_attribute(event, public_send(event))
end
write_attribute(:events, nil)
end
end end
...@@ -150,21 +150,21 @@ class User < ActiveRecord::Base ...@@ -150,21 +150,21 @@ class User < ActiveRecord::Base
presence: true, presence: true,
uniqueness: { case_sensitive: false } uniqueness: { case_sensitive: false }
validate :namespace_uniq, if: ->(user) { user.username_changed? } validate :namespace_uniq, if: :username_changed?
validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? } validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? }
validate :unique_email, if: ->(user) { user.email_changed? } validate :unique_email, if: :email_changed?
validate :owns_notification_email, if: ->(user) { user.notification_email_changed? } validate :owns_notification_email, if: :notification_email_changed?
validate :owns_public_email, if: ->(user) { user.public_email_changed? } validate :owns_public_email, if: :public_email_changed?
validate :signup_domain_valid?, on: :create, if: ->(user) { !user.created_by_id } validate :signup_domain_valid?, on: :create, if: ->(user) { !user.created_by_id }
validates :avatar, file_size: { maximum: 200.kilobytes.to_i } validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
before_validation :sanitize_attrs before_validation :sanitize_attrs
before_validation :set_notification_email, if: ->(user) { user.email_changed? } before_validation :set_notification_email, if: :email_changed?
before_validation :set_public_email, if: ->(user) { user.public_email_changed? } before_validation :set_public_email, if: :public_email_changed?
after_update :update_emails_with_primary_email, if: ->(user) { user.email_changed? } after_update :update_emails_with_primary_email, if: :email_changed?
before_save :ensure_authentication_token, :ensure_incoming_email_token before_save :ensure_authentication_token, :ensure_incoming_email_token
before_save :ensure_external_user_rights before_save :ensure_user_rights_and_limits, if: :external_changed?
after_save :ensure_namespace_correct after_save :ensure_namespace_correct
after_initialize :set_projects_limit after_initialize :set_projects_limit
after_destroy :post_destroy_hook after_destroy :post_destroy_hook
...@@ -1069,11 +1069,14 @@ class User < ActiveRecord::Base ...@@ -1069,11 +1069,14 @@ class User < ActiveRecord::Base
super super
end end
def ensure_external_user_rights def ensure_user_rights_and_limits
return unless external? if external?
self.can_create_group = false
self.can_create_group = false self.projects_limit = 0
self.projects_limit = 0 else
self.can_create_group = gitlab_config.default_can_create_group
self.projects_limit = current_application_settings.default_projects_limit
end
end end
def signup_domain_valid? def signup_domain_valid?
......
...@@ -6,10 +6,11 @@ class GroupEntity < Grape::Entity ...@@ -6,10 +6,11 @@ class GroupEntity < Grape::Entity
expose :id, :name, :path, :description, :visibility expose :id, :name, :path, :description, :visibility
expose :full_name, :full_path expose :full_name, :full_path
expose :web_url
expose :parent_id expose :parent_id
expose :created_at, :updated_at expose :created_at, :updated_at
expose :web_url do |group| expose :group_path do |group|
group_path(group) group_path(group)
end end
......
...@@ -5,7 +5,6 @@ class IssuableEntity < Grape::Entity ...@@ -5,7 +5,6 @@ class IssuableEntity < Grape::Entity
expose :description expose :description
expose :lock_version expose :lock_version
expose :milestone_id expose :milestone_id
expose :position
expose :state expose :state
expose :title expose :title
expose :updated_by_id expose :updated_by_id
......
...@@ -8,7 +8,7 @@ class NotificationRecipientService ...@@ -8,7 +8,7 @@ class NotificationRecipientService
@project = project @project = project
end end
def build_recipients(target, current_user, action: nil, previous_assignee: nil, skip_current_user: true) def build_recipients(target, current_user, action:, previous_assignee: nil, skip_current_user: true)
custom_action = build_custom_key(action, target) custom_action = build_custom_key(action, target)
recipients = target.participants(current_user) recipients = target.participants(current_user)
...@@ -59,7 +59,7 @@ class NotificationRecipientService ...@@ -59,7 +59,7 @@ class NotificationRecipientService
return [] if notification_setting.mention? || notification_setting.disabled? return [] if notification_setting.mention? || notification_setting.disabled?
return [] if notification_setting.custom? && !notification_setting.public_send(custom_action) return [] if notification_setting.custom? && !notification_setting.event_enabled?(custom_action)
return [] if (notification_setting.watch? || notification_setting.participating?) && NotificationSetting::EXCLUDED_WATCHER_EVENTS.include?(custom_action) return [] if (notification_setting.watch? || notification_setting.participating?) && NotificationSetting::EXCLUDED_WATCHER_EVENTS.include?(custom_action)
...@@ -176,7 +176,7 @@ class NotificationRecipientService ...@@ -176,7 +176,7 @@ class NotificationRecipientService
if notification_level if notification_level
settings = resource.notification_settings.where(level: NotificationSetting.levels[notification_level]) settings = resource.notification_settings.where(level: NotificationSetting.levels[notification_level])
settings = settings.select { |setting| setting.events[action] } if action.present? settings = settings.select { |setting| setting.event_enabled?(action) } if action.present?
settings.map(&:user_id) settings.map(&:user_id)
else else
resource.notification_settings.pluck(:user_id) resource.notification_settings.pluck(:user_id)
...@@ -225,7 +225,7 @@ class NotificationRecipientService ...@@ -225,7 +225,7 @@ class NotificationRecipientService
def user_ids_with_global_level_custom(ids, action) def user_ids_with_global_level_custom(ids, action)
settings = settings_with_global_level_of(:custom, ids) settings = settings_with_global_level_of(:custom, ids)
settings = settings.select { |setting| setting.events[action] } settings = settings.select { |setting| setting.event_enabled?(action) }
settings.map(&:user_id) settings.map(&:user_id)
end end
......
...@@ -296,7 +296,7 @@ class NotificationService ...@@ -296,7 +296,7 @@ class NotificationService
end end
def issue_moved(issue, new_issue, current_user) def issue_moved(issue, new_issue, current_user)
recipients = NotificationRecipientService.new(issue.project).build_recipients(issue, current_user) recipients = NotificationRecipientService.new(issue.project).build_recipients(issue, current_user, action: 'moved')
recipients.map do |recipient| recipients.map do |recipient|
email = mailer.issue_moved_email(recipient, issue, new_issue, current_user) email = mailer.issue_moved_email(recipient, issue, new_issue, current_user)
...@@ -403,7 +403,7 @@ class NotificationService ...@@ -403,7 +403,7 @@ class NotificationService
end end
def approve_mr_email(merge_request, project, current_user) def approve_mr_email(merge_request, project, current_user)
recipients = NotificationRecipientService.new(project).build_recipients(merge_request, current_user) recipients = NotificationRecipientService.new(project).build_recipients(merge_request, current_user, action: 'approve')
recipients.each do |recipient| recipients.each do |recipient|
mailer.approved_merge_request_email(recipient.id, merge_request.id, current_user.id).deliver_later mailer.approved_merge_request_email(recipient.id, merge_request.id, current_user.id).deliver_later
...@@ -411,7 +411,7 @@ class NotificationService ...@@ -411,7 +411,7 @@ class NotificationService
end end
def unapprove_mr_email(merge_request, project, current_user) def unapprove_mr_email(merge_request, project, current_user)
recipients = NotificationRecipientService.new(project).build_recipients(merge_request, current_user) recipients = NotificationRecipientService.new(project).build_recipients(merge_request, current_user, action: 'unapprove')
recipients.each do |recipient| recipients.each do |recipient|
mailer.unapproved_merge_request_email(recipient.id, merge_request.id, current_user.id).deliver_later mailer.unapproved_merge_request_email(recipient.id, merge_request.id, current_user.id).deliver_later
......
...@@ -34,7 +34,7 @@ module Users ...@@ -34,7 +34,7 @@ module Users
# Keep trying until we obtain the lease. If we don't do so we may end up # Keep trying until we obtain the lease. If we don't do so we may end up
# not updating the list of authorized projects properly. To prevent # not updating the list of authorized projects properly. To prevent
# hammering Redis too much we'll wait for a bit between retries. # hammering Redis too much we'll wait for a bit between retries.
sleep(1) sleep(0.1)
end end
begin begin
......
...@@ -341,8 +341,9 @@ ...@@ -341,8 +341,9 @@
%fieldset %fieldset
%legend Metrics - Prometheus %legend Metrics - Prometheus
%p %p
Setup Prometheus to measure a variety of statistics that partially overlap and complement Influx based metrics. Enable a Prometheus metrics endpoint at `#{metrics_path}` to expose a variety of statistics on the health and performance of GitLab. Additional information on authenticating and connecting to the metrics endpoint is available
This setting requires a = link_to 'here', admin_health_check_path
\. This setting requires a
= link_to 'restart', help_page_path('administration/restart_gitlab') = link_to 'restart', help_page_path('administration/restart_gitlab')
to take effect. to take effect.
= link_to icon('question-circle'), help_page_path('administration/monitoring/performance/introduction') = link_to icon('question-circle'), help_page_path('administration/monitoring/performance/introduction')
......
...@@ -21,11 +21,11 @@ ...@@ -21,11 +21,11 @@
.form-group.js-toggle-colors-container.hide .form-group.js-toggle-colors-container.hide
= f.label :color, "Background Color", class: 'control-label' = f.label :color, "Background Color", class: 'control-label'
.col-sm-10 .col-sm-10
= f.text_field :color, class: "form-control" = f.color_field :color, class: "form-control"
.form-group.js-toggle-colors-container.hide .form-group.js-toggle-colors-container.hide
= f.label :font, "Font Color", class: 'control-label' = f.label :font, "Font Color", class: 'control-label'
.col-sm-10 .col-sm-10
= f.text_field :font, class: "form-control" = f.color_field :font, class: "form-control"
.form-group .form-group
= f.label :starts_at, class: 'control-label' = f.label :starts_at, class: 'control-label'
.col-sm-10.datetime-controls .col-sm-10.datetime-controls
......
.center - if @resource.unconfirmed_email.present?
- if @resource.unconfirmed_email.present? #content
#content = email_default_heading(@resource.unconfirmed_email)
%h2= @resource.unconfirmed_email %p Click the link below to confirm your email address.
%p Click the link below to confirm your email address. #cta
#cta = link_to 'Confirm your email address', confirmation_url(@resource, confirmation_token: @token)
= link_to 'Confirm your email address', confirmation_url(@resource, confirmation_token: @token) - else
- else #content
#content - if Gitlab.com?
- if Gitlab.com? = email_default_heading('Thanks for signing up to GitLab!')
%h2 Thanks for signing up to GitLab! - else
- else = email_default_heading("Welcome, #{@resource.name}!")
%h2 Welcome, #{@resource.name}! %p To get started, click the link below to confirm your account.
%p To get started, click the link below to confirm your account. #cta
#cta = link_to 'Confirm your account', confirmation_url(@resource, confirmation_token: @token)
= link_to 'Confirm your account', confirmation_url(@resource, confirmation_token: @token)
.center = email_default_heading("Hello, #{@resource.name}!")
#content %p
%h2 Hello, #{@resource.name}! The password for your GitLab account on
%p #{link_to(Gitlab.config.gitlab.url, Gitlab.config.gitlab.url)}
The password for your GitLab account on has successfully been changed.
#{link_to(Gitlab.config.gitlab.url, Gitlab.config.gitlab.url)} %p
has successfully been changed. If you did not initiate this change, please contact your administrator
%p immediately.
If you did not initiate this change, please contact your administrator
immediately.
.center = email_default_heading("Hello, #{@resource.name}!")
#content %p
%h2 Hello, #{@resource.name}! Someone, hopefully you, has requested to reset the password for your
%p GitLab account on #{link_to(Gitlab.config.gitlab.url, Gitlab.config.gitlab.url)}.
Someone, hopefully you, has requested to reset the password for your %p
GitLab account on #{link_to(Gitlab.config.gitlab.url, Gitlab.config.gitlab.url)}. If you did not perform this request, you can safely ignore this email.
%p %p
If you did not perform this request, you can safely ignore this email. Otherwise, click the link below to complete the process.
%p #cta
Otherwise, click the link below to complete the process. = link_to('Reset password', edit_password_url(@resource, reset_password_token: @token))
#cta
= link_to('Reset password', edit_password_url(@resource, reset_password_token: @token))
.center #content
#content = email_default_heading("Hello, #{@resource.name}!")
%h2 Hello, #{@resource.name}! %p
%p Your GitLab account has been locked due to an excessive amount of unsuccessful
Your GitLab account has been locked due to an excessive amount of unsuccessful sign in attempts. Your account will automatically unlock in #{time_ago_in_words(Devise.unlock_in.from_now)}
sign in attempts. Your account will automatically unlock in #{time_ago_in_words(Devise.unlock_in.from_now)} or you may click the link below to unlock now.
or you may click the link below to unlock now. #cta
#cta = link_to('Unlock account', unlock_url(@resource, unlock_token: @token))
= link_to('Unlock account', unlock_url(@resource, unlock_token: @token))
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional //EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
%html{ lang: "en" }
%head
%meta{ content: "text/html; charset=UTF-8", "http-equiv" => "Content-Type" }/
%meta{ content: "width=device-width, initial-scale=1", name: "viewport" }/
%meta{ content: "IE=edge", "http-equiv" => "X-UA-Compatible" }/
%title= message.subject
:css
/* CLIENT-SPECIFIC STYLES */
body, table, td, a { -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; }
table, td { mso-table-lspace: 0pt; mso-table-rspace: 0pt; }
img { -ms-interpolation-mode: bicubic; }
/* iOS BLUE LINKS */
a[x-apple-data-detectors] {
color: inherit !important;
text-decoration: none !important;
font-size: inherit !important;
font-family: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
}
/* ANDROID MARGIN HACK */
body { margin:0 !important; }
div[style*="margin: 16px 0"] { margin:0 !important; }
@media only screen and (max-width: 639px) {
body, #body {
min-width: 320px !important;
}
table.wrapper {
width: 100% !important;
min-width: 320px !important;
}
table.wrapper > tbody > tr > td {
border-left: 0 !important;
border-right: 0 !important;
border-radius: 0 !important;
padding-left: 10px !important;
padding-right: 10px !important;
}
}
%body{ style: "background-color:#fafafa;margin:0;padding:0;text-align:center;min-width:640px;width:100%;height:100%;font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;" }
%table#body{ border: "0", cellpadding: "0", cellspacing: "0", style: "background-color:#fafafa;margin:0;padding:0;text-align:center;min-width:640px;width:100%;" }
%tbody
%tr.line
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#6b4fbb;height:4px;font-size:4px;line-height:4px;" }  
%tr.header
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;" }
= header_logo
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;" }
%table.wrapper{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:640px;margin:0 auto;border-collapse:separate;border-spacing:0;" }
%tbody
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#ffffff;text-align:left;padding:18px 25px;border:1px solid #ededed;border-radius:3px;overflow:hidden;" }
%table.content{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;border-collapse:separate;border-spacing:0;" }
%tbody
= yield
%tr.footer
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;" }
%img{ alt: "GitLab", height: "33", src: image_url('mailers/gitlab_footer_logo.gif'), style: "display:block;margin:0 auto 1em;", width: "90" }/
%div
%a{ href: profile_notifications_url, style: "color:#3777b0;text-decoration:none;" } Manage all notifications
&middot;
%a{ href: help_url, style: "color:#3777b0;text-decoration:none;" } Help
%div
You're receiving this email because of your account on
= succeed "." do
%a{ href: root_url, style: "color:#3777b0;text-decoration:none;" }= Gitlab.config.gitlab.host
= yield :additional_footer
!!! 5
%html
%head
%meta{ content: 'text/html; charset=UTF-8', 'http-equiv'=> 'Content-Type' }
= stylesheet_link_tag 'mailers/devise'
%body
%table#wrapper
%tr
%td
%table#header
%td{ valign: "top" }
= image_tag('mailers/gitlab_header_logo.png', id: 'logo', alt: 'GitLab Wordmark')
%table#body
%tr
%td#body-container
= yield
- if Gitlab.com?
%table#footer
%tr
%td#tanuki
= image_tag('mailers/gitlab_tanuki_2x.png', alt: 'GitLab Logo')
%tr
%td#tagline
Everyone can contribute
%tr
%td#social
= link_to 'Blog', 'https://about.gitlab.com/blog/'
= link_to 'Twitter', 'https://twitter.com/gitlab'
= link_to 'Facebook', 'https://www.facebook.com/gitlab/'
= link_to 'YouTube', 'https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg'
= link_to 'LinkedIn', 'https://www.linkedin.com/company/gitlab-com'
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional //EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> = render 'layouts/mailer'
%html{ lang: "en" }
%head
%meta{ content: "text/html; charset=UTF-8", "http-equiv" => "Content-Type" }/
%meta{ content: "width=device-width, initial-scale=1", name: "viewport" }/
%meta{ content: "IE=edge", "http-equiv" => "X-UA-Compatible" }/
%title= message.subject
:css
/* CLIENT-SPECIFIC STYLES */
body, table, td, a { -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; }
table, td { mso-table-lspace: 0pt; mso-table-rspace: 0pt; }
img { -ms-interpolation-mode: bicubic; }
/* iOS BLUE LINKS */
a[x-apple-data-detectors] {
color: inherit !important;
text-decoration: none !important;
font-size: inherit !important;
font-family: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
}
/* ANDROID MARGIN HACK */
body { margin:0 !important; }
div[style*="margin: 16px 0"] { margin:0 !important; }
@media only screen and (max-width: 639px) {
body, #body {
min-width: 320px !important;
}
table.wrapper {
width: 100% !important;
min-width: 320px !important;
}
table.wrapper > tbody > tr > td {
border-left: 0 !important;
border-right: 0 !important;
border-radius: 0 !important;
padding-left: 10px !important;
padding-right: 10px !important;
}
}
%body{ style: "background-color:#fafafa;margin:0;padding:0;text-align:center;min-width:640px;width:100%;height:100%;font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;" }
%table#body{ border: "0", cellpadding: "0", cellspacing: "0", style: "background-color:#fafafa;margin:0;padding:0;text-align:center;min-width:640px;width:100%;" }
%tbody
%tr.line
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#6b4fbb;height:4px;font-size:4px;line-height:4px;" }  
%tr.header
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;" }
= header_logo
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;" }
%table.wrapper{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:640px;margin:0 auto;border-collapse:separate;border-spacing:0;" }
%tbody
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#ffffff;text-align:left;padding:18px 25px;border:1px solid #ededed;border-radius:3px;overflow:hidden;" }
%table.content{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;border-collapse:separate;border-spacing:0;" }
%tbody
= yield
%tr.footer
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;" }
%img{ alt: "GitLab", height: "33", src: image_url('mailers/gitlab_footer_logo.gif'), style: "display:block;margin:0 auto 1em;", width: "90" }/
%div
%a{ href: profile_notifications_url, style: "color:#3777b0;text-decoration:none;" } Manage all notifications
&middot;
%a{ href: help_url, style: "color:#3777b0;text-decoration:none;" } Help
%div
You're receiving this email because of your account on
= succeed "." do
%a{ href: root_url, style: "color:#3777b0;text-decoration:none;" }= Gitlab.config.gitlab.host
- if Gitlab.com?
- content_for :additional_footer do
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:13px;line-height:1.6;color:#5c5c5c;" }
%div
Everyone can contribute
%div
= link_to 'Blog', 'https://about.gitlab.com/blog/', style: "color:#3777b0;text-decoration:none;"
&middot;
= link_to 'Twitter', 'https://twitter.com/gitlab', style: "color:#3777b0;text-decoration:none;"
&middot;
= link_to 'Facebook', 'https://www.facebook.com/gitlab/', style: "color:#3777b0;text-decoration:none;"
&middot;
= link_to 'YouTube', 'https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg', style: "color:#3777b0;text-decoration:none;"
&middot;
= link_to 'LinkedIn', 'https://www.linkedin.com/company/gitlab-com', style: "color:#3777b0;text-decoration:none;"
= render layout: 'layouts/mailer' do
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#333333;font-size:15px;font-weight:400;line-height:1.4;padding:15px 5px;text-align:center;" }
= yield
...@@ -42,10 +42,17 @@ ...@@ -42,10 +42,17 @@
- if current_user.ldap_user? - if current_user.ldap_user?
Some options are unavailable for LDAP accounts Some options are unavailable for LDAP accounts
.col-lg-9 .col-lg-9
.form-group .row
= f.label :name, class: "label-light" .form-group.col-md-9
= f.text_field :name, class: "form-control", required: true = f.label :name, class: "label-light"
%span.help-block Enter your name, so people you know can recognize you. = f.text_field :name, class: "form-control", required: true
%span.help-block Enter your name, so people you know can recognize you.
.form-group.col-md-3
= f.label :id, class: 'label-light' do
User ID
= f.text_field :id, class: 'form-control', readonly: true
.form-group .form-group
= f.label :email, class: "label-light" = f.label :email, class: "label-light"
......
- if can_change_visibility_level?(@project, current_user) - if can_change_visibility_level?(@project, current_user)
= form.select(model_method, visibility_select_options(@project, selected_level), {}, class: 'form-control visibility-select') .select-wrapper
= form.select(model_method, visibility_select_options(@project, selected_level), {}, class: 'form-control visibility-select select-control')
= icon('chevron-down')
- else - else
.info.js-locked{ data: { help_block: visibility_level_description(@project.visibility_level, @project) } } .info.js-locked{ data: { help_block: visibility_level_description(@project.visibility_level, @project) } }
= visibility_level_icon(@project.visibility_level) = visibility_level_icon(@project.visibility_level)
......
...@@ -9,8 +9,10 @@ ...@@ -9,8 +9,10 @@
.dropzone .dropzone
.dropzone-previews.blob-upload-dropzone-previews .dropzone-previews.blob-upload-dropzone-previews
%p.dz-message.light %p.dz-message.light
Attach a file by drag &amp; drop or - upload_link = link_to n_('UploadLink|click to upload'), '#', class: "markdown-selector"
= link_to 'click to upload', '#', class: "markdown-selector" - dropzone_text = _('Attach a file by drag &amp; drop or %{upload_link}') % { upload_link: upload_link }
#{ dropzone_text.html_safe }
%br %br
.dropzone-alerts.alert.alert-danger.data{ style: "display:none" } .dropzone-alerts.alert.alert-danger.data{ style: "display:none" }
...@@ -18,7 +20,7 @@ ...@@ -18,7 +20,7 @@
.form-actions .form-actions
= button_tag button_title, class: 'btn btn-small btn-create btn-upload-file', id: 'submit-all' = button_tag button_title, class: 'btn btn-small btn-create btn-upload-file', id: 'submit-all'
= link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal" = link_to _("Cancel"), '#', class: "btn btn-cancel", "data-dismiss" => "modal"
- unless can?(current_user, :push_code, @project) - unless can?(current_user, :push_code, @project)
.inline.prepend-left-10 .inline.prepend-left-10
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
- if !project.empty_repo? && can?(current_user, :download_code, project) - if !project.empty_repo? && can?(current_user, :download_code, project)
.project-action-button.dropdown.inline> .project-action-button.dropdown.inline>
%button.btn.has-tooltip{ title: 'Download', 'data-toggle' => 'dropdown', 'aria-label' => s_('DownloadSource|Download') } %button.btn.has-tooltip{ title: s_('DownloadSource|Download'), 'data-toggle' => 'dropdown', 'aria-label' => s_('DownloadSource|Download') }
= icon('download') = icon('download')
= icon("caret-down") = icon("caret-down")
%span.sr-only= _('Select Archive Format') %span.sr-only= _('Select Archive Format')
......
- if current_user - if current_user
.project-action-button.dropdown.inline .project-action-button.dropdown.inline
%a.btn.dropdown-toggle.has-tooltip{ href: '#', title: 'Create new...', 'data-toggle' => 'dropdown', 'data-container' => 'body', 'aria-label' => 'Create new...' } %a.btn.dropdown-toggle.has-tooltip{ href: '#', title: _('Create new...'), 'data-toggle' => 'dropdown', 'data-container' => 'body', 'aria-label' => _('Create new...') }
= icon('plus') = icon('plus')
= icon("caret-down") = icon("caret-down")
%ul.dropdown-menu.dropdown-menu-align-right.project-home-dropdown %ul.dropdown-menu.dropdown-menu-align-right.project-home-dropdown
......
...@@ -4,11 +4,15 @@ ...@@ -4,11 +4,15 @@
= link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: _('Go to your fork'), class: 'btn has-tooltip' do = link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: _('Go to your fork'), class: 'btn has-tooltip' do
= custom_icon('icon_fork') = custom_icon('icon_fork')
%span= s_('GoToYourFork|Fork') %span= s_('GoToYourFork|Fork')
- elsif !current_user.can_create_project?
= link_to new_namespace_project_fork_path(@project.namespace, @project), title: _('You have reached your project limit'), class: 'btn has-tooltip disabled' do
= custom_icon('icon_fork')
%span= s_('CreateNewFork|Fork')
- else - else
= link_to new_namespace_project_fork_path(@project.namespace, @project), class: 'btn' do = link_to new_namespace_project_fork_path(@project.namespace, @project), class: 'btn' do
= custom_icon('icon_fork') = custom_icon('icon_fork')
%span= s_('CreateNewFork|Fork') %span= s_('CreateNewFork|Fork')
.count-with-arrow .count-with-arrow
%span.arrow %span.arrow
= link_to namespace_project_forks_path(@project.namespace, @project), title: n_('Forks', @project.forks_count), class: 'count' do = link_to namespace_project_forks_path(@project.namespace, @project), title: n_('Fork', 'Forks', @project.forks_count), class: 'count' do
= @project.forks_count = @project.forks_count
- case type.to_s - case type.to_s
- when 'revert' - when 'revert'
- label = 'Revert' - label = s_('ChangeTypeAction|Revert')
- branch_label = 'Revert in branch' - branch_label = s_('ChangeTypeActionLabel|Revert in branch')
- revert_merge_request = _('Revert this merge request')
- revert_commit = _('Revert this commit')
- title = commit.merged_merge_request(current_user) ? revert_merge_request : revert_commit
- when 'cherry-pick' - when 'cherry-pick'
- label = 'Cherry-pick' - label = s_('ChangeTypeAction|Cherry-pick')
- branch_label = 'Pick into branch' - branch_label = s_('ChangeTypeActionLabel|Pick into branch')
- title = commit.merged_merge_request(current_user) ? _('Cherry-pick this merge request') : _('Cherry-pick this commit')
.modal{ id: "modal-#{type}-commit" } .modal{ id: "modal-#{type}-commit" }
.modal-dialog .modal-dialog
.modal-content .modal-content
.modal-header .modal-header
%a.close{ href: "#", "data-dismiss" => "modal" } × %a.close{ href: "#", "data-dismiss" => "modal" } ×
%h3.page-title== #{label} this #{commit.change_type_title(current_user)} %h3.page-title= title
.modal-body .modal-body
= form_tag [type.underscore, @project.namespace.becomes(Namespace), @project, commit], method: :post, remote: false, class: "form-horizontal js-#{type}-form js-requires-input" do = form_tag [type.underscore, @project.namespace.becomes(Namespace), @project, commit], method: :post, remote: false, class: "form-horizontal js-#{type}-form js-requires-input" do
.form-group.branch .form-group.branch
= label_tag 'start_branch', branch_label, class: 'control-label' = label_tag 'start_branch', branch_label, class: 'control-label'
.col-sm-10 .col-sm-10
= hidden_field_tag :start_branch, @project.default_branch, id: 'start_branch' = hidden_field_tag :start_branch, @project.default_branch, id: 'start_branch'
= dropdown_tag(@project.default_branch, options: { title: "Switch branch", filter: true, placeholder: "Search branches", toggle_class: 'js-project-refs-dropdown dynamic', dropdown_class: 'dropdown-menu-selectable', data: { field_name: "start_branch", selected: @project.default_branch, start_branch: @project.default_branch, refs_url: namespace_project_branches_path(@project.namespace, @project), submit_form_on_click: false } }) = dropdown_tag(@project.default_branch, options: { title: n_("BranchSwitcherTitle|Switch branch"), filter: true, placeholder: n_("BranchSwitcherPlaceholder|Search branches"), toggle_class: 'js-project-refs-dropdown dynamic', dropdown_class: 'dropdown-menu-selectable', data: { field_name: "start_branch", selected: @project.default_branch, start_branch: @project.default_branch, refs_url: namespace_project_branches_path(@project.namespace, @project), submit_form_on_click: false } })
- if can?(current_user, :push_code, @project) - if can?(current_user, :push_code, @project)
.checkbox = render 'shared/new_merge_request_checkbox'
= label_tag do
= check_box_tag 'create_merge_request', 1, true, class: 'js-create-merge-request', id: nil
Start a <strong>new merge request</strong> with these changes
- else - else
= hidden_field_tag 'create_merge_request', 1, id: nil = hidden_field_tag 'create_merge_request', 1, id: nil
.form-actions .form-actions
= submit_tag label, class: 'btn btn-create' = submit_tag label, class: 'btn btn-create'
= link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal" = link_to _("Cancel"), '#', class: "btn btn-cancel", "data-dismiss" => "modal"
- unless can?(current_user, :push_code, @project) - unless can?(current_user, :push_code, @project)
.inline.prepend-left-10 .inline.prepend-left-10
......
.page-content-header .page-content-header
.header-main-content .header-main-content
%strong %strong
Commit #{ s_('CommitBoxTitle|Commit') }
%span.commit-sha= @commit.short_id %span.commit-sha= @commit.short_id
= clipboard_button(text: @commit.id, title: "Copy commit SHA to clipboard") = clipboard_button(text: @commit.id, title: _("Copy commit SHA to clipboard"))
%span.hidden-xs authored %span.hidden-xs authored
#{time_ago_with_tooltip(@commit.authored_date)} #{time_ago_with_tooltip(@commit.authored_date)}
%span by %span= s_('ByAuthor|by')
= author_avatar(@commit, size: 24) = author_avatar(@commit, size: 24)
%strong %strong
= commit_author_link(@commit, avatar: true, size: 24) = commit_author_link(@commit, avatar: true, size: 24)
- if @commit.different_committer? - if @commit.different_committer?
%span.light Committed by %span.light= _('Committed by')
%strong %strong
= commit_committer_link(@commit, avatar: true, size: 24) = commit_committer_link(@commit, avatar: true, size: 24)
#{time_ago_with_tooltip(@commit.committed_date)} #{time_ago_with_tooltip(@commit.committed_date)}
...@@ -22,15 +22,15 @@ ...@@ -22,15 +22,15 @@
= icon('comment') = icon('comment')
= @notes_count = @notes_count
= link_to namespace_project_tree_path(@project.namespace, @project, @commit), class: "btn btn-default append-right-10 hidden-xs hidden-sm" do = link_to namespace_project_tree_path(@project.namespace, @project, @commit), class: "btn btn-default append-right-10 hidden-xs hidden-sm" do
Browse files #{ _('Browse files') }
.dropdown.inline .dropdown.inline
%a.btn.btn-default.dropdown-toggle{ data: { toggle: "dropdown" } } %a.btn.btn-default.dropdown-toggle{ data: { toggle: "dropdown" } }
%span Options %span= _('Options')
= icon('caret-down') = icon('caret-down')
%ul.dropdown-menu.dropdown-menu-align-right %ul.dropdown-menu.dropdown-menu-align-right
%li.visible-xs-block.visible-sm-block %li.visible-xs-block.visible-sm-block
= link_to namespace_project_tree_path(@project.namespace, @project, @commit) do = link_to namespace_project_tree_path(@project.namespace, @project, @commit) do
Browse Files _('Browse Files')
- unless @commit.has_been_reverted?(current_user) - unless @commit.has_been_reverted?(current_user)
%li.clearfix %li.clearfix
= revert_commit_link(@commit, namespace_project_commit_path(@project.namespace, @project, @commit.id), has_tooltip: false) = revert_commit_link(@commit, namespace_project_commit_path(@project.namespace, @project, @commit.id), has_tooltip: false)
...@@ -38,13 +38,13 @@ ...@@ -38,13 +38,13 @@
= cherry_pick_commit_link(@commit, namespace_project_commit_path(@project.namespace, @project, @commit.id), has_tooltip: false) = cherry_pick_commit_link(@commit, namespace_project_commit_path(@project.namespace, @project, @commit.id), has_tooltip: false)
- if can_collaborate_with_project? - if can_collaborate_with_project?
%li.clearfix %li.clearfix
= link_to "Tag", new_namespace_project_tag_path(@project.namespace, @project, ref: @commit) = link_to s_("CreateTag|Tag"), new_namespace_project_tag_path(@project.namespace, @project, ref: @commit)
%li.divider %li.divider
%li.dropdown-header %li.dropdown-header
Download #{ _('Download') }
- unless @commit.parents.length > 1 - unless @commit.parents.length > 1
%li= link_to "Email Patches", namespace_project_commit_path(@project.namespace, @project, @commit, format: :patch) %li= link_to s_("DownloadCommit|Email Patches"), namespace_project_commit_path(@project.namespace, @project, @commit, format: :patch)
%li= link_to "Plain Diff", namespace_project_commit_path(@project.namespace, @project, @commit, format: :diff) %li= link_to s_("DownloadCommit|Plain Diff"), namespace_project_commit_path(@project.namespace, @project, @commit, format: :diff)
.commit-box .commit-box
%h3.commit-title %h3.commit-title
...@@ -57,7 +57,7 @@ ...@@ -57,7 +57,7 @@
.well-segment.branch-info .well-segment.branch-info
.icon-container.commit-icon .icon-container.commit-icon
= custom_icon("icon_commit") = custom_icon("icon_commit")
%span.cgray= pluralize(@commit.parents.count, "parent") %span.cgray= n_('parent', 'parents', @commit.parents.count)
- @commit.parents.each do |parent| - @commit.parents.each do |parent|
= link_to parent.short_id, namespace_project_commit_path(@project.namespace, @project, parent), class: "commit-sha" = link_to parent.short_id, namespace_project_commit_path(@project.namespace, @project, parent), class: "commit-sha"
%span.commit-info.branches %span.commit-info.branches
...@@ -69,11 +69,11 @@ ...@@ -69,11 +69,11 @@
.status-icon-container{ class: "ci-status-icon-#{@commit.status}" } .status-icon-container{ class: "ci-status-icon-#{@commit.status}" }
= link_to namespace_project_pipeline_path(@project.namespace, @project, last_pipeline.id) do = link_to namespace_project_pipeline_path(@project.namespace, @project, last_pipeline.id) do
= ci_icon_for_status(last_pipeline.status) = ci_icon_for_status(last_pipeline.status)
Pipeline #{ _('Pipeline') }
= link_to "##{last_pipeline.id}", namespace_project_pipeline_path(@project.namespace, @project, last_pipeline.id) = link_to "##{last_pipeline.id}", namespace_project_pipeline_path(@project.namespace, @project, last_pipeline.id)
= ci_label_for_status(last_pipeline.status) = ci_label_for_status(last_pipeline.status)
- if last_pipeline.stages_count.nonzero? - if last_pipeline.stages_count.nonzero?
with #{"stage".pluralize(last_pipeline.stages_count)} #{ n_(s_('Pipeline|with stage'), s_('Pipeline|with stages'), last_pipeline.stages_count) }
.mr-widget-pipeline-graph .mr-widget-pipeline-graph
= render 'shared/mini_pipeline_graph', pipeline: last_pipeline, klass: 'js-commit-pipeline-graph' = render 'shared/mini_pipeline_graph', pipeline: last_pipeline, klass: 'js-commit-pipeline-graph'
in in
......
...@@ -30,9 +30,11 @@ ...@@ -30,9 +30,11 @@
%pre.commit-row-description.js-toggle-content %pre.commit-row-description.js-toggle-content
= preserve(markdown(commit.description, pipeline: :single_line, author: commit.author)) = preserve(markdown(commit.description, pipeline: :single_line, author: commit.author))
.commiter .commiter
= commit_author_link(commit, avatar: false, size: 24) - commit_author_link = commit_author_link(commit, avatar: false, size: 24)
#{ _('committed') } - commit_timeago = time_ago_with_tooltip(commit.committed_date)
#{time_ago_with_tooltip(commit.committed_date)} - commit_text = _('%{commit_author_link} committed %{commit_timeago}') % { commit_author_link: commit_author_link, commit_timeago: commit_timeago }
#{ commit_text.html_safe }
.commit-actions.flex-row.hidden-xs .commit-actions.flex-row.hidden-xs
- if commit.status(ref) - if commit.status(ref)
......
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
.table-mobile-header{ role: 'rowheader' } Created .table-mobile-header{ role: 'rowheader' } Created
%span.table-mobile-content= time_ago_with_tooltip(deployment.created_at) %span.table-mobile-content= time_ago_with_tooltip(deployment.created_at)
.table-section.section-20.environments-actions.table-button-footer{ role: 'gridcell' } .table-section.section-20.table-button-footer{ role: 'gridcell' }
.btn-group.environment-action-buttons .btn-group.table-action-button
= render 'projects/deployments/actions', deployment: deployment = render 'projects/deployments/actions', deployment: deployment
= render 'projects/deployments/rollback', deployment: deployment = render 'projects/deployments/rollback', deployment: deployment
...@@ -113,9 +113,9 @@ ...@@ -113,9 +113,9 @@
Git Large File Storage Git Large File Storage
= link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs') = link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs')
.col-md-3 .col-md-3
= f.select :lfs_enabled, [%w(Enabled true), %w(Disabled false)], {}, selected: @project.lfs_enabled?, class: 'pull-right form-control project-repo-select', data: { field: 'lfs_enabled' } .select-wrapper
= f.select :lfs_enabled, [%w(Enabled true), %w(Disabled false)], {}, selected: @project.lfs_enabled?, class: 'pull-right form-control project-repo-select select-control', data: { field: 'lfs_enabled' }
= icon('chevron-down')
- if Gitlab.config.registry.enabled - if Gitlab.config.registry.enabled
.form-group.js-container-registry{ style: ("display: none;" if @project.project_feature.send(:repository_access_level) == 0) } .form-group.js-container-registry{ style: ("display: none;" if @project.project_feature.send(:repository_access_level) == 0) }
.checkbox .checkbox
......
.form-horizontal.resolve-conflicts-form .form-horizontal.resolve-conflicts-form
.form-group .form-group
%label.col-sm-2.control-label{ "for" => "commit-message" } %label.col-sm-2.control-label{ "for" => "commit-message" }
Commit message #{ _('Commit message') }
.col-sm-10 .col-sm-10
.commit-message-container .commit-message-container
.max-width-marker .max-width-marker
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
.form-group .form-group
.col-md-9 .col-md-9
= f.label :description, _('Description'), class: 'label-light' = f.label :description, _('Description'), class: 'label-light'
= f.text_field :description, class: 'form-control', required: true, autofocus: true, placeholder: _('PipelineSchedules|Provide a short description for this pipeline') = f.text_field :description, class: 'form-control', required: true, autofocus: true, placeholder: s_('PipelineSchedules|Provide a short description for this pipeline')
.form-group .form-group
.col-md-9 .col-md-9
= f.label :cron, _('Interval Pattern'), class: 'label-light' = f.label :cron, _('Interval Pattern'), class: 'label-light'
...@@ -15,19 +15,19 @@ ...@@ -15,19 +15,19 @@
.form-group .form-group
.col-md-9 .col-md-9
= f.label :cron_timezone, _('Cron Timezone'), class: 'label-light' = f.label :cron_timezone, _('Cron Timezone'), class: 'label-light'
= dropdown_tag(_("Select a timezone"), options: { toggle_class: 'btn js-timezone-dropdown', title: _("Select a timezone"), filter: true, placeholder: _("Filter"), data: { data: timezone_data } } ) = dropdown_tag(_("Select a timezone"), options: { toggle_class: 'btn js-timezone-dropdown', title: _("Select a timezone"), filter: true, placeholder: _("OfSearchInADropdown|Filter"), data: { data: timezone_data } } )
= f.text_field :cron_timezone, value: @schedule.cron_timezone, id: 'schedule_cron_timezone', class: 'hidden', name: 'schedule[cron_timezone]', required: true = f.text_field :cron_timezone, value: @schedule.cron_timezone, id: 'schedule_cron_timezone', class: 'hidden', name: 'schedule[cron_timezone]', required: true
.form-group .form-group
.col-md-9 .col-md-9
= f.label :ref, _('Target Branch'), class: 'label-light' = f.label :ref, _('Target Branch'), class: 'label-light'
= dropdown_tag(_("Select target branch"), options: { toggle_class: 'btn js-target-branch-dropdown', dropdown_class: 'git-revision-dropdown', title: _("Select target branch"), filter: true, placeholder: _("Filter"), data: { data: @project.repository.branch_names, default_branch: @project.default_branch } } ) = dropdown_tag(_("Select target branch"), options: { toggle_class: 'btn js-target-branch-dropdown', dropdown_class: 'git-revision-dropdown', title: _("Select target branch"), filter: true, placeholder: _("OfSearchInADropdown|Filter"), data: { data: @project.repository.branch_names, default_branch: @project.default_branch } } )
= f.text_field :ref, value: @schedule.ref, id: 'schedule_ref', class: 'hidden', name: 'schedule[ref]', required: true = f.text_field :ref, value: @schedule.ref, id: 'schedule_ref', class: 'hidden', name: 'schedule[ref]', required: true
.form-group .form-group
.col-md-9 .col-md-9
= f.label :active, _('PipelineSchedules|Activated'), class: 'label-light' = f.label :active, s_('PipelineSchedules|Activated'), class: 'label-light'
%div %div
= f.check_box :active, required: false, value: @schedule.active? = f.check_box :active, required: false, value: @schedule.active?
Active = _('Active')
.footer-block.row-content-block .footer-block.row-content-block
= f.submit _('Save pipeline schedule'), class: 'btn btn-create', tabindex: 3 = f.submit _('Save pipeline schedule'), class: 'btn btn-create', tabindex: 3
= link_to _('Cancel'), pipeline_schedules_path(@project), class: 'btn btn-cancel' = link_to _('Cancel'), pipeline_schedules_path(@project), class: 'btn btn-cancel'
...@@ -13,12 +13,12 @@ ...@@ -13,12 +13,12 @@
= ci_icon_for_status(pipeline_schedule.last_pipeline.status) = ci_icon_for_status(pipeline_schedule.last_pipeline.status)
%span ##{pipeline_schedule.last_pipeline.id} %span ##{pipeline_schedule.last_pipeline.id}
- else - else
= _("PipelineSchedules|None") = s_("PipelineSchedules|None")
%td.next-run-cell %td.next-run-cell
- if pipeline_schedule.active? - if pipeline_schedule.active?
= time_ago_with_tooltip(pipeline_schedule.real_next_run) = time_ago_with_tooltip(pipeline_schedule.real_next_run)
- else - else
= _("PipelineSchedules|Inactive") = s_("PipelineSchedules|Inactive")
%td %td
- if pipeline_schedule.owner - if pipeline_schedule.owner
= image_tag avatar_icon(pipeline_schedule.owner, 20), class: "avatar s20" = image_tag avatar_icon(pipeline_schedule.owner, 20), class: "avatar s20"
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
.nav-controls .nav-controls
= link_to new_namespace_project_pipeline_schedule_path(@project.namespace, @project), class: 'btn btn-create' do = link_to new_namespace_project_pipeline_schedule_path(@project.namespace, @project), class: 'btn btn-create' do
%span New schedule %span= _('New schedule')
- if @schedules.present? - if @schedules.present?
%ul.content-list %ul.content-list
......
...@@ -6,7 +6,9 @@ ...@@ -6,7 +6,9 @@
= users_select_tag(:user_ids, multiple: true, class: "input-clamp", scope: :all, email_user: true, placeholder: "Search for members to update or invite") = users_select_tag(:user_ids, multiple: true, class: "input-clamp", scope: :all, email_user: true, placeholder: "Search for members to update or invite")
.form-group .form-group
= label_tag :access_level, "Choose a role permission", class: "label-light" = label_tag :access_level, "Choose a role permission", class: "label-light"
= select_tag :access_level, options_for_select(ProjectMember.access_level_roles, @project_member.access_level), class: "form-control project-access-select" .select-wrapper
= select_tag :access_level, options_for_select(ProjectMember.access_level_roles, @project_member.access_level), class: "form-control project-access-select select-control"
= icon('chevron-down')
.help-block.append-bottom-10 .help-block.append-bottom-10
= link_to "Read more", help_page_path("user/permissions"), class: "vlink" = link_to "Read more", help_page_path("user/permissions"), class: "vlink"
about role permissions about role permissions
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
= label_tag :link_group_access, "Max access level", class: "label-light" = label_tag :link_group_access, "Max access level", class: "label-light"
.select-wrapper .select-wrapper
= select_tag :link_group_access, options_for_select(ProjectGroupLink.access_options, ProjectGroupLink.default_access), class: "form-control select-control" = select_tag :link_group_access, options_for_select(ProjectGroupLink.access_options, ProjectGroupLink.default_access), class: "form-control select-control"
= icon('caret-down') = icon('chevron-down')
.help-block.append-bottom-10 .help-block.append-bottom-10
= link_to "Read more", help_page_path("user/permissions"), class: "vlink" = link_to "Read more", help_page_path("user/permissions"), class: "vlink"
about role permissions about role permissions
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
- nonce = SecureRandom.hex - nonce = SecureRandom.hex
- descriptions = local_assigns.slice(:message_with_description, :message_without_description) - descriptions = local_assigns.slice(:message_with_description, :message_without_description)
= label_tag "commit_message-#{nonce}", class: 'control-label' do = label_tag "commit_message-#{nonce}", class: 'control-label' do
Commit message #{ _('Commit message') }
.col-sm-10 .col-sm-10
.commit-message-container .commit-message-container
.max-width-marker .max-width-marker
......
...@@ -5,16 +5,12 @@ ...@@ -5,16 +5,12 @@
- else - else
- if can?(current_user, :push_code, @project) - if can?(current_user, :push_code, @project)
.form-group.branch .form-group.branch
= label_tag 'branch_name', 'Target branch', class: 'control-label' = label_tag 'branch_name', _('Target Branch'), class: 'control-label'
.col-sm-10 .col-sm-10
= text_field_tag 'branch_name', @branch_name || tree_edit_branch, required: true, class: "form-control js-branch-name ref-name" = text_field_tag 'branch_name', @branch_name || tree_edit_branch, required: true, class: "form-control js-branch-name ref-name"
.js-create-merge-request-container .js-create-merge-request-container
.checkbox = render 'shared/new_merge_request_checkbox'
- nonce = SecureRandom.hex
= label_tag "create_merge_request-#{nonce}" do
= check_box_tag 'create_merge_request', 1, true, class: 'js-create-merge-request', id: "create_merge_request-#{nonce}"
Start a <strong>new merge request</strong> with these changes
- else - else
= hidden_field_tag 'branch_name', @branch_name || tree_edit_branch = hidden_field_tag 'branch_name', @branch_name || tree_edit_branch
= hidden_field_tag 'create_merge_request', 1 = hidden_field_tag 'create_merge_request', 1
......
.checkbox
- nonce = SecureRandom.hex
= label_tag "create_merge_request-#{nonce}" do
= check_box_tag 'create_merge_request', 1, true, class: 'js-create-merge-request', id: "create_merge_request-#{nonce}"
- translation_variables = { new_merge_request: "<strong>#{_('new merge request')}</strong>" }
- translation = _('Start a %{new_merge_request} with these changes') % translation_variables
#{ translation.html_safe }
...@@ -8,11 +8,11 @@ ...@@ -8,11 +8,11 @@
= title = title
- if show_counter - if show_counter
.counter .counter
= number_with_delimiter(issuables.size) = number_with_delimiter(issuables.length)
- class_prefix = dom_class(issuables).pluralize - class_prefix = dom_class(issuables).pluralize
%ul{ class: "well-list #{class_prefix}-sortable-list", id: "#{class_prefix}-list-#{id}", "data-state" => id } %ul{ class: "well-list milestone-#{class_prefix}-list", id: "#{class_prefix}-list-#{id}" }
= render partial: 'shared/milestones/issuable', = render partial: 'shared/milestones/issuable',
collection: issuables.order_position_asc, collection: issuables,
as: :issuable, as: :issuable,
locals: { show_project_name: show_project_name, show_full_project_name: show_full_project_name } locals: { show_project_name: show_project_name, show_full_project_name: show_full_project_name }
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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