Commit 19791bf7 authored by Stan Hu's avatar Stan Hu

Merge branch 'master' into sh-geo-add-project-deletion-events

parents 37d8a84b e76764ed
......@@ -427,19 +427,23 @@ gitlab:assets:compile:
- webpack-report/
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
<<: *use-pg
<<: *dedicated-runner
<<: *except-docs
variables:
BABEL_ENV: "coverage"
CHROME_LOG_FILE: "chrome_debug.log"
script:
- bundle exec rake karma
coverage: '/^Statements *: (\d+\.\d+%)/'
artifacts:
name: coverage-javascript
expire_in: 31d
when: always
paths:
- chrome_debug.log
- coverage-javascript/
codeclimate:
......
......@@ -88,7 +88,7 @@ gem 'kaminari', '~> 0.17.0'
gem 'hamlit', '~> 2.6.1'
# Files attachments
gem 'carrierwave', '~> 1.0'
gem 'carrierwave', '~> 1.1'
# Drag and Drop UI
gem 'dropzonejs-rails', '~> 0.7.1'
......@@ -167,7 +167,7 @@ gem 'rufus-scheduler', '~> 3.4'
gem 'httparty', '~> 0.13.3'
# Colored output to console
gem 'rainbow', '~> 2.1.0'
gem 'rainbow', '~> 2.2'
# GitLab settings
gem 'settingslogic', '~> 2.0.9'
......@@ -383,7 +383,7 @@ gem 'ruby-prof', '~> 0.16.2'
gem 'oauth2', '~> 1.4'
# Soft deletion
gem 'paranoia', '~> 2.2'
gem 'paranoia', '~> 2.3.1'
# Health check
gem 'health_check', '~> 2.6.0'
......
......@@ -116,7 +116,7 @@ GEM
capybara-screenshot (1.0.14)
capybara (>= 1.0, < 3)
launchy
carrierwave (1.0.0)
carrierwave (1.1.0)
activemodel (>= 4.0.0)
activesupport (>= 4.0.0)
mime-types (>= 1.16)
......@@ -574,8 +574,8 @@ GEM
rubypants (~> 0.2)
orm_adapter (0.5.0)
os (0.9.6)
paranoia (2.2.0)
activerecord (>= 4.0, < 5.1)
paranoia (2.3.1)
activerecord (>= 4.0, < 5.2)
parser (2.4.0.0)
ast (~> 2.2)
path_expander (1.0.1)
......@@ -679,7 +679,8 @@ GEM
activesupport (= 4.2.8)
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
rainbow (2.1.0)
rainbow (2.2.2)
rake
raindrops (0.17.0)
rake (10.5.0)
rblineprof (0.3.6)
......@@ -961,7 +962,7 @@ DEPENDENCIES
bundler-audit (~> 0.5.0)
capybara (~> 2.6.2)
capybara-screenshot (~> 1.0.0)
carrierwave (~> 1.0)
carrierwave (~> 1.1)
charlock_holmes (~> 0.7.3)
chronic (~> 0.10.2)
chronic_duration (~> 0.10.6)
......@@ -1067,7 +1068,7 @@ DEPENDENCIES
omniauth-twitter (~> 1.2.0)
omniauth_crowd (~> 2.2.0)
org-ruby (~> 0.9.12)
paranoia (~> 2.2)
paranoia (~> 2.3.1)
peek (~> 1.0.1)
peek-gc (~> 0.0.2)
peek-host (~> 1.0.0)
......@@ -1089,7 +1090,7 @@ DEPENDENCIES
rack-proxy (~> 0.6.0)
rails (= 4.2.8)
rails-deprecated_sanitizer (~> 1.0.3)
rainbow (~> 2.1.0)
rainbow (~> 2.2)
rblineprof (~> 0.3.6)
rdoc (~> 4.2)
recaptcha (~> 3.0)
......@@ -1155,4 +1156,4 @@ DEPENDENCIES
wikicloth (= 0.8.1)
BUNDLED WITH
1.15.0
1.15.1
......@@ -77,7 +77,7 @@ const Api = {
dataType: 'json',
})
.done(label => callback(label))
.error(message => callback(message.responseJSON));
.fail(message => callback(message.responseJSON));
},
// Return group projects list. Filtered by query
......@@ -134,7 +134,7 @@ const Api = {
dataType: 'json',
})
.done(file => callback(null, file))
.error(callback);
.fail(callback);
},
users(query, options) {
......
......@@ -287,6 +287,10 @@ window.DropzoneInput = (function() {
$uploadingErrorMessage.html(message);
};
closeAlertMessage = function() {
return form.find('.div-dropzone-alert').alert('close');
};
form.find('.markdown-selector').click(function(e) {
e.preventDefault();
$(this).closest('.gfm-form').find('.div-dropzone').click();
......
......@@ -551,10 +551,10 @@ export default {
</span>
</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
v-if="!model.isFolder"
class="btn-group environment-action-buttons"
class="btn-group table-action-buttons"
role="group">
<actions-component
......
......@@ -27,7 +27,7 @@ export default {
if (this.group.hasSubgroups) {
eventHub.$emit('toggleSubGroups', this.group);
} else {
window.location.href = this.group.webUrl;
window.location.href = this.group.groupPath;
}
}
},
......@@ -192,7 +192,7 @@ export default {
<div
class="avatar-container s40 hidden-xs">
<a
:href="group.webUrl">
:href="group.groupPath">
<img
class="avatar s40"
:src="group.avatarUrl"
......@@ -202,7 +202,7 @@ export default {
<div
class="title">
<a
:href="group.webUrl">{{fullPath}}</a>
:href="group.groupPath">{{fullPath}}</a>
<template v-if="group.permissions.humanGroupAccess">
as
<span class="access-type">{{group.permissions.humanGroupAccess}}</span>
......
......@@ -47,8 +47,8 @@ export default class GroupsStore {
// Map groups to an object
groups.map((group) => {
mappedGroups[group.id] = group;
mappedGroups[group.id].subGroups = {};
mappedGroups[`id${group.id}`] = group;
mappedGroups[`id${group.id}`].subGroups = {};
return group;
});
......@@ -56,26 +56,27 @@ export default class GroupsStore {
const currentGroup = mappedGroups[key];
if (currentGroup.parentId) {
// If the group is not at the root level, add it to its parent array of subGroups.
const findParentGroup = mappedGroups[currentGroup.parentId];
const findParentGroup = mappedGroups[`id${currentGroup.parentId}`];
if (findParentGroup) {
mappedGroups[currentGroup.parentId].subGroups[currentGroup.id] = currentGroup;
mappedGroups[currentGroup.parentId].isOpen = true; // Expand group if it has subgroups
mappedGroups[`id${currentGroup.parentId}`].subGroups[`id${currentGroup.id}`] = currentGroup;
mappedGroups[`id${currentGroup.parentId}`].isOpen = true; // Expand group if it has subgroups
} else if (parentGroup && parentGroup.id === currentGroup.parentId) {
tree[currentGroup.id] = currentGroup;
tree[`id${currentGroup.id}`] = currentGroup;
} else {
// Means the groups hast no direct parent.
// Save for later processing, we will add them to its corresponding base group
// No parent found. We save it for later processing
orphans.push(currentGroup);
// Add to tree to preserve original order
tree[`id${currentGroup.id}`] = currentGroup;
}
} else {
// If the group is at the root level, add it to first level elements array.
tree[currentGroup.id] = currentGroup;
// If the group is at the top level, add it to first level elements array.
tree[`id${currentGroup.id}`] = currentGroup;
}
return key;
});
// Hopefully this array will be empty for most cases
if (orphans.length) {
orphans.map((orphan) => {
let found = false;
......@@ -83,11 +84,23 @@ export default class GroupsStore {
Object.keys(tree).map((key) => {
const group = tree[key];
if (currentOrphan.fullPath.lastIndexOf(group.fullPath) === 0) {
if (
group &&
currentOrphan.fullPath.lastIndexOf(group.fullPath) === 0 &&
// Make sure the currently selected orphan is not the same as the group
// we are checking here otherwise it will end up in an infinite loop
currentOrphan.id !== group.id
) {
group.subGroups[currentOrphan.id] = currentOrphan;
group.isOpen = true;
currentOrphan.isOrphan = true;
found = true;
// Delete if group was put at the top level. If not the group will be displayed twice.
if (tree[`id${currentOrphan.id}`]) {
delete tree[`id${currentOrphan.id}`];
}
}
return key;
......@@ -95,7 +108,8 @@ export default class GroupsStore {
if (!found) {
currentOrphan.isOrphan = true;
tree[currentOrphan.id] = currentOrphan;
tree[`id${currentOrphan.id}`] = currentOrphan;
}
return orphan;
......@@ -122,6 +136,7 @@ export default class GroupsStore {
canEdit: rawGroup.can_edit,
description: rawGroup.description,
webUrl: rawGroup.web_url,
groupPath: rawGroup.group_path,
parentId: rawGroup.parent_id,
visibility: rawGroup.visibility,
leavePath: rawGroup.leave_path,
......@@ -139,7 +154,7 @@ export default class GroupsStore {
// eslint-disable-next-line class-methods-use-this
removeGroup(group, collection) {
Vue.delete(collection, group.id);
Vue.delete(collection, `id${group.id}`);
}
// eslint-disable-next-line class-methods-use-this
......
......@@ -53,7 +53,7 @@
},
methods: {
renderGFM() {
$(this.$refs['gfm-entry-content']).renderGFM();
$(this.$refs['gfm-content']).renderGFM();
if (this.canUpdate) {
// eslint-disable-next-line no-new
......
......@@ -34,7 +34,7 @@ window.dateFormat = dateFormat;
w.gl.utils.localTimeAgo = function($timeagoEls, setTimeago = true) {
$timeagoEls.each((i, el) => {
el.setAttribute('title', gl.utils.formatDate(el.getAttribute('datetime')));
el.setAttribute('title', el.getAttribute('title'));
if (setTimeago) {
// 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.
......@@ -284,7 +284,7 @@ import BlobForkSuggestion from './blob/blob_fork_suggestion';
// Scroll any linked note into view
// Similar to `toggler_behavior` in the discussion tab
const hash = window.gl.utils.getLocationHash();
const anchor = hash && $container.find(`[id="${hash}"]`);
const anchor = hash && $container.find(`.note[id="${hash}"]`);
if (anchor && anchor.length > 0) {
const notesContent = anchor.closest('.notes_content');
const lineType = notesContent.hasClass('new') ? 'new' : 'old';
......
......@@ -4,87 +4,7 @@
(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() {
this.issuesSortEndpoint = $('#tab-issues').data('sort-endpoint');
this.mergeRequestsSortEndpoint = $('#tab-merge-requests').data('sort-endpoint');
this.bindIssuesSorting();
this.bindTabsSwitching();
// Load merge request tab if it is active
......@@ -94,22 +14,6 @@
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() {
return $('a[data-toggle="tab"]').on('show.bs.tab', (e) => {
const $target = $(e.target);
......@@ -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() {
const $target = $(`.js-milestone-tabs a[href="${location.hash}"]`);
......@@ -203,10 +44,6 @@
.done((data) => {
$(tabElId).html(data.html);
$target.addClass('is-loaded');
if (tabElId === '#tab-merge-requests') {
this.bindMergeRequestSorting();
}
});
}
};
......
......@@ -322,7 +322,9 @@ const normalizeNewlines = function(str) {
Notes.updateNoteTargetSelector = function($note) {
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 Translate from '../../vue_shared/translate';
Vue.use(Translate);
const inputNameAttribute = 'schedule[cron]';
......@@ -72,11 +75,11 @@ export default {
/>
<label for="custom">
Custom
{{ s__('PipelineSheduleIntervalPattern|Custom') }}
</label>
<span class="cron-syntax-link-wrap">
(<a :href="cronSyntaxUrl" target="_blank">Cron syntax</a>)
(<a :href="cronSyntaxUrl" target="_blank">{{ __('Cron syntax') }}</a>)
</span>
</div>
......@@ -92,7 +95,7 @@ export default {
/>
<label class="label-light" for="every-day">
Every day (at 4:00am)
{{ __('Every day (at 4:00am)') }}
</label>
</div>
......@@ -108,7 +111,7 @@ export default {
/>
<label class="label-light" for="every-week">
Every week (Sundays at 4:00am)
{{ __('Every week (Sundays at 4:00am)') }}
</label>
</div>
......@@ -124,7 +127,7 @@ export default {
/>
<label class="label-light" for="every-month">
Every month (on the 1st at 4:00am)
{{ __('Every month (on the 1st at 4:00am)') }}
</label>
</div>
......@@ -133,7 +136,7 @@ export default {
id="schedule_cron"
class="form-control inline cron-interval-input"
type="text"
placeholder="Define a custom pattern with cron syntax"
:placeholder="__('Define a custom pattern with cron syntax')"
required="true"
v-model="cronInterval"
:name="inputNameAttribute"
......
import Vue from 'vue';
import Cookies from 'js-cookie';
import Translate from '../../vue_shared/translate';
import illustrationSvg from '../icons/intro_illustration.svg';
Vue.use(Translate);
const cookieKey = 'pipeline_schedules_callout_dismissed';
export default {
......@@ -29,20 +33,18 @@ export default {
</button>
<div class="svg-container" v-html="illustrationSvg"></div>
<div class="user-callout-copy">
<h4>Scheduling Pipelines</h4>
<h4>{{ __('Scheduling Pipelines') }}</h4>
<p>
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.
{{ __('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.') }}
</p>
<p> Learn more in the
<p> {{ __('Learn more in the') }}
<a
:href="docsUrl"
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>
</div>
</div>
</div>
`,
};
......@@ -23,7 +23,7 @@ export default {
};
</script>
<template>
<td>
<div class="table-section section-15 hidden-xs hidden-sm">
<a
:href="pipeline.path"
class="js-pipeline-url-link">
......@@ -42,24 +42,26 @@ export default {
class="js-pipeline-url-api api">
API
</span>
<span
v-if="pipeline.flags.latest"
class="js-pipeline-url-lastest label label-success"
title="Latest pipeline for this branch"
ref="tooltip">
latest
</span>
<span
v-if="pipeline.flags.yaml_errors"
class="js-pipeline-url-yaml label label-danger"
:title="pipeline.yaml_errors"
ref="tooltip">
yaml invalid
</span>
<span
v-if="pipeline.flags.stuck"
class="js-pipeline-url-stuck label label-warning">
stuck
</span>
</td>
<div class="label-container">
<span
v-if="pipeline.flags.latest"
class="js-pipeline-url-latest label label-success"
title="Latest pipeline for this branch"
ref="tooltip">
latest
</span>
<span
v-if="pipeline.flags.yaml_errors"
class="js-pipeline-url-yaml label label-danger"
:title="pipeline.yaml_errors"
ref="tooltip">
yaml invalid
</span>
<span
v-if="pipeline.flags.stuck"
class="js-pipeline-url-stuck label label-warning">
stuck
</span>
</div>
</div>
</template>
......@@ -56,7 +56,7 @@
<div class="btn-group">
<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"
data-toggle="dropdown"
data-placement="top"
......
......@@ -55,31 +55,39 @@
};
</script>
<template>
<td class="pipelines-time-ago">
<p
class="duration"
v-if="hasDuration">
<span v-html="iconTimerSvg">
</span>
{{durationFormated}}
</p>
<div class="table-section section-15 pipelines-time-ago">
<div
class="table-mobile-header"
role="rowheader">
Duration
</div>
<div class="table-mobile-content">
<p
class="duration"
v-if="hasDuration">
<span
v-html="iconTimerSvg">
</span>
{{durationFormated}}
</p>
<p
class="finished-at"
v-if="hasFinishedTime">
<p
class="finished-at hidden-xs hidden-sm"
v-if="hasFinishedTime">
<i
class="fa fa-calendar"
aria-hidden="true">
</i>
<i
class="fa fa-calendar"
aria-hidden="true">
</i>
<time
ref="tooltip"
data-placement="top"
data-container="body"
:title="tooltipTitle(finishedTime)">
{{timeFormated(finishedTime)}}
</time>
</p>
</td>
<time
ref="tooltip"
data-placement="top"
data-container="body"
:title="tooltipTitle(finishedTime)">
{{timeFormated(finishedTime)}}
</time>
</p>
</div>
</div>
</script>
import statusCodes from '~/lib/utils/http_status';
import { bytesToMiB } from '~/lib/utils/number_utils';
import statusCodes from '../../lib/utils/http_status';
import { bytesToMiB } from '../../lib/utils/number_utils';
import MemoryGraph from '../../vue_shared/components/memory_graph';
import MRWidgetService from '../services/mr_widget_service';
......
......@@ -110,7 +110,7 @@
</script>
<template>
<div class="branch-commit">
<div v-if="hasCommitRef" class="icon-container">
<div v-if="hasCommitRef" class="icon-container hidden-xs">
<i
v-if="tag"
class="fa fa-tag"
......@@ -125,7 +125,7 @@
<a
v-if="hasCommitRef"
class="ref-name"
class="ref-name hidden-xs"
:href="commitRef.ref_url">
{{commitRef.name}}
</a>
......
......@@ -28,28 +28,37 @@
};
</script>
<template>
<table class="table ci-table">
<thead>
<tr>
<th class="js-pipeline-status pipeline-status">Status</th>
<th class="js-pipeline-info pipeline-info">Pipeline</th>
<th class="js-pipeline-commit pipeline-commit">Commit</th>
<th class="js-pipeline-stages pipeline-stages">Stages</th>
<th class="js-pipeline-date pipeline-date"></th>
<th class="js-pipeline-actions pipeline-actions"></th>
</tr>
</thead>
<tbody>
<template
v-for="model in pipelines"
:model="model">
<tr
is="pipelines-table-row-component"
:pipeline="model"
:service="service"
:update-graph-dropdown="updateGraphDropdown"
/>
</template>
</tbody>
</table>
<div class="ci-table">
<div
class="gl-responsive-table-row table-row-header"
role="row">
<div
class="table-section section-10 js-pipeline-status pipeline-status"
role="rowheader">
Status
</div>
<div
class="table-section section-15 js-pipeline-info pipeline-info"
role="rowheader">
Pipeline
</div>
<div
class="table-section section-25 js-pipeline-commit pipeline-commit"
role="rowheader">
Commit
</div>
<div
class="table-section section-15 js-pipeline-stages pipeline-stages"
role="rowheader">
Stages
</div>
</div>
<pipelines-table-row-component
v-for="model in pipelines"
:key="model.id"
:pipeline="model"
:service="service"
:update-graph-dropdown="updateGraphDropdown"
/>
</div>
</template>
......@@ -200,47 +200,74 @@ export default {
}
return {};
},
displayPipelineActions() {
return this.pipeline.flags.retryable ||
this.pipeline.flags.cancelable ||
this.pipeline.details.manual_actions.length ||
this.pipeline.details.artifacts.length;
},
},
};
</script>
<template>
<tr class="commit">
<td class="commit-link">
<ci-badge :status="pipelineStatus" />
</td>
<div class="commit gl-responsive-table-row">
<div class="table-section section-10 commit-link">
<div class="table-mobile-header"
role="rowheader">
Status
</div>
<div class="table-mobile-content">
<ci-badge :status="pipelineStatus"/>
</div>
</div>
<pipeline-url :pipeline="pipeline" />
<td>
<commit-component
:tag="commitTag"
:commit-ref="commitRef"
:commit-url="commitUrl"
:short-sha="commitShortSha"
:title="commitTitle"
:author="commitAuthor"
/>
</td>
<td class="stage-cell">
<div class="stage-container dropdown js-mini-pipeline-graph"
v-if="pipeline.details.stages.length > 0"
v-for="stage in pipeline.details.stages">
<div class="table-section section-25">
<div
class="table-mobile-header"
role="rowheader">
Commit
</div>
<div class="table-mobile-content">
<commit-component
:tag="commitTag"
:commit-ref="commitRef"
:commit-url="commitUrl"
:short-sha="commitShortSha"
:title="commitTitle"
:author="commitAuthor"/>
</div>
</div>
<pipeline-stage
:stage="stage"
:update-dropdown="updateGraphDropdown"
/>
<div class="table-section section-wrap section-15 stage-cell">
<div
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>
</td>
</div>
<pipelines-timeago
:duration="pipelineDuration"
:finished-time="pipelineFinishedAt"
/>
<td class="pipeline-actions">
<div class="pull-right btn-group">
<div
v-if="displayPipelineActions"
class="table-section section-20 table-button-footer pipeline-actions">
<div class="btn-group table-action-buttons">
<pipelines-actions-component
v-if="pipeline.details.manual_actions.length"
:actions="pipeline.details.manual_actions"
......@@ -249,6 +276,7 @@ export default {
<pipelines-artifacts-component
v-if="pipeline.details.artifacts.length"
class="hidden-xs hidden-sm"
:artifacts="pipeline.details.artifacts"
/>
......@@ -271,6 +299,6 @@ export default {
confirm-action-message="Are you sure you want to cancel this pipeline?"
/>
</div>
</td>
</tr>
</div>
</div>
</template>
......@@ -63,6 +63,7 @@
background-color: $gray-light;
text-align: right;
padding: 8px $gl-padding;
border-bottom: 1px solid $border-color;
@media (max-width: $screen-xs-max) {
text-align: left;
......
......@@ -152,7 +152,7 @@
}
.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 {
.select-wrapper {
position: relative;
.fa-caret-down {
.fa-chevron-down {
position: absolute;
font-size: 10px;
right: 10px;
top: 10px;
top: 12px;
color: $gray-darkest;
pointer-events: none;
}
......@@ -138,6 +139,12 @@ label {
padding-left: 10px;
padding-right: 10px;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
&::-ms-expand {
display: none;
}
}
.form-control-inline {
......
......@@ -174,3 +174,14 @@
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 @@
margin: 0 2px 0 3px;
}
}
.ci-status {
margin-right: 10px;
}
}
......@@ -31,14 +31,6 @@
align-items: center;
}
.panel-empty-heading {
border-bottom: 0;
}
.panel-body {
padding: $gl-padding;
}
.left {
flex: 1 1 auto;
}
......
......@@ -36,13 +36,58 @@
align-self: stretch;
padding: 10px;
align-items: center;
height: 62px;
min-height: 62px;
&:not(:first-of-type) {
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 @@
.table-mobile-header {
color: $gl-text-color-secondary;
text-align: left;
@include flex-max-width(40);
@media (min-width: $screen-md-min) {
......
......@@ -18,19 +18,28 @@
background-image: none;
background-color: transparent;
border: none;
padding-top: 6px;
padding-right: 10px;
padding-top: 12px;
padding-right: 20px;
font-size: 10px;
b {
display: inline-block;
width: 0;
height: 0;
margin-left: 2px;
vertical-align: middle;
border-top: 5px dashed;
border-right: 5px solid transparent;
border-left: 5px solid transparent;
display: none;
}
&::after {
content: "\f078";
position: absolute;
z-index: 1;
text-align: center;
pointer-events: none;
box-sizing: border-box;
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%);
/*
* Filtered Search
*/
$filtered-search-term-shadow-color: rgba(0, 0, 0, 0.09);
$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 @@
}
.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 {
max-width: 100%;
}
......
......@@ -111,8 +111,8 @@
}
}
.issues-sortable-list,
.merge_requests-sortable-list {
.milestone-issues-list,
.milestone-merge_requests-list {
.issuable-detail {
display: block;
margin-top: 7px;
......@@ -197,8 +197,6 @@
.issuable-row {
background-color: $white-light;
cursor: -webkit-grab;
cursor: grab;
}
// EE-only
......
......@@ -509,11 +509,6 @@ ul.notes {
display: inline;
line-height: 20px;
@include notes-media('min', $screen-sm-min) {
margin-left: 10px;
line-height: 24px;
}
.fa {
color: $gray-darkest;
position: relative;
......
......@@ -37,17 +37,13 @@
.table-holder {
width: 100%;
@media (max-width: $screen-sm-max) {
overflow: auto;
}
}
.commit-title {
margin: 0;
}
.table.ci-table {
.ci-table {
.label {
margin-bottom: 3px;
......@@ -57,11 +53,6 @@
color: $black;
}
.stage-cell {
min-width: 130px; // Guarantees we show at least 4 stages in line
width: 20%;
}
.pipelines-time-ago {
text-align: right;
}
......@@ -135,43 +126,7 @@
}
}
.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;
}
.ci-table {
.build.retried {
background-color: $gray-lightest;
}
......@@ -225,13 +180,6 @@
color: $gl-link-color;
}
.commit-title {
max-width: 225px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.label {
margin-right: 4px;
}
......@@ -284,11 +232,7 @@
}
.stage-cell {
font-size: 0;
padding: 0 4px;
> .stage-container > div > button > span > svg,
> .stage-container > button > svg {
.mini-pipeline-graph-dropdown-toggle svg {
height: 22px;
width: 22px;
position: absolute;
......@@ -656,6 +600,23 @@
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
.mini-pipeline-graph-dropdown-toggle,
.linked-pipeline-mini-item {
......@@ -696,100 +657,32 @@
// Dropdown button animation in mini pipeline graph
&.ci-status-icon-success {
border-color: $green-500;
color: $green-500;
&:hover,
&:focus,
&:active {
background-color: $green-50;
border-color: $green-600;
color: $green-600;
svg {
fill: $green-600;
}
}
@include mini-pipeline-graph-color($green-50, $green-500, $green-600);
}
&.ci-status-icon-failed {
border-color: $red-500;
color: $red-500;
&:hover,
&:focus,
&:active {
background-color: $red-50;
border-color: $red-600;
color: $red-600;
svg {
fill: $red-600;
}
}
@include mini-pipeline-graph-color($red-50, $red-500, $red-600);
}
&.ci-status-icon-pending,
&.ci-status-icon-success_with_warnings {
border-color: $orange-500;
color: $orange-500;
&:hover,
&:focus,
&:active {
background-color: $orange-50;
border-color: $orange-600;
color: $orange-600;
svg {
fill: $orange-600;
}
}
@include mini-pipeline-graph-color($orange-50, $orange-500, $orange-600);
}
&.ci-status-icon-running {
border-color: $blue-400;
color: $blue-400;
&:hover,
&:focus,
&:active {
background-color: $blue-50;
border-color: $blue-600;
color: $blue-600;
svg {
fill: $blue-600;
}
}
@include mini-pipeline-graph-color($blue-50, $blue-400, $blue-600);
}
&.ci-status-icon-canceled,
&.ci-status-icon-disabled,
&.ci-status-icon-not-found,
&.ci-status-icon-manual {
border-color: $gl-text-color;
color: $gl-text-color;
&:hover,
&:focus,
&:active {
background-color: rgba($gl-text-color, 0.1);
border-color: $gl-text-color;
}
@include mini-pipeline-graph-color(rgba($gl-text-color, 0.1), $gl-text-color, $gl-text-color);
}
&.ci-status-icon-created,
&.ci-status-icon-skipped {
border-color: $gray-darkest;
color: $gray-darkest;
&:hover,
&:focus,
&:active {
background-color: rgba($gray-darkest, 0.1);
border-color: $gray-darkest;
}
@include mini-pipeline-graph-color(rgba($gray-darkest, 0.1), $gray-darkest, $gray-darkest);
}
}
......@@ -868,6 +761,10 @@
top: 1px;
vertical-align: text-bottom;
position: relative;
@media (max-width: $screen-xs-max) {
max-width: 60%;
}
}
// status icon on the left
......@@ -958,6 +855,11 @@
left: 50%;
transform: translate(-50%, 0);
border-width: 0 5px 6px;
@media (max-width: $screen-sm-max) {
left: 100%;
margin-left: -12px;
}
}
&::before {
......@@ -975,9 +877,15 @@
* Center dropdown menu in mini graph
*/
.mini-pipeline-graph-dropdown-menu.dropdown-menu {
right: auto;
left: 50%;
transform: translate(-50%, 0);
transform: translate(-80%, 0);
min-width: 150px;
@media(min-width: $screen-md-min) {
transform: translate(-50%, 0);
right: auto;
left: 50%;
min-width: 240px;
}
}
/**
* Terminal
......@@ -1183,7 +1091,6 @@
&.has-only-one-job:not(:first-child) {
margin-right: 36px;
margin-left: 0;
.left-connector {
@include flat-connector-before;
......@@ -1234,4 +1141,3 @@
}
}
}
.container-fluid {
.ci-status {
padding: 2px 7px 4px;
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;
}
@mixin status-color($color-light, $color-main, $color-dark) {
color: $color-main;
border-color: $color-main;
&.ci-failed {
color: $red-500;
border-color: $red-500;
&:not(span):hover {
background-color: $color-light;
color: $color-dark;
border-color: $color-dark;
&:not(span):hover {
background-color: $red-50;
color: $red-600;
border-color: $red-600;
svg {
fill: $red-600;
}
}
svg {
fill: $red-500;
}
svg {
fill: $color-dark;
}
}
&.ci-success {
color: $green-600;
border-color: $green-500;
svg {
fill: $color-main;
}
}
&:not(span):hover {
background-color: $green-50;
color: $green-700;
border-color: $green-600;
.ci-status {
padding: 2px 7px 4px;
border: 1px solid $gray-darker;
white-space: nowrap;
border-radius: 4px;
svg {
fill: $green-600;
}
}
&:hover,
&:focus {
text-decoration: none;
}
svg {
fill: $green-500;
}
}
svg {
height: 13px;
width: 13px;
position: relative;
top: 2px;
overflow: visible;
}
&.ci-canceled,
&.ci-disabled {
color: $gl-text-color;
border-color: $gl-text-color;
&.ci-failed {
@include status-color($red-50, $red-500, $red-600);
}
&:not(span):hover {
background-color: rgba($gl-text-color, .07);
}
&.ci-success {
@include status-color($green-50, $green-500, $green-700);
}
svg {
fill: $gl-text-color;
}
}
&.ci-canceled,
&.ci-disabled,
&.ci-manual {
color: $gl-text-color;
border-color: $gl-text-color;
&.ci-pending,
&.ci-success_with_warnings,
&.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;
}
&:not(span):hover {
background-color: rgba($gl-text-color, .07);
}
}
&.ci-info,
&.ci-running {
color: $blue-500;
border-color: $blue-500;
&:not(span):hover {
background-color: $blue-50;
color: $blue-600;
border-color: $blue-600;
svg {
fill: $blue-600;
}
}
svg {
fill: $blue-500;
}
}
&.ci-pending,
&.ci-failed_with_warnings,
&.ci-success_with_warnings {
@include status-color($orange-50, $orange-500, $orange-700);
}
&.ci-created,
&.ci-skipped {
color: $gl-text-color-secondary;
border-color: $gl-text-color-secondary;
&.ci-info,
&.ci-running {
@include status-color($blue-50, $blue-500, $blue-600);
}
&:not(span):hover {
background-color: rgba($gl-text-color-secondary, .07);
}
&.ci-created,
&.ci-skipped {
color: $gl-text-color-secondary;
border-color: $gl-text-color-secondary;
svg {
fill: $gl-text-color-secondary;
}
&:not(span):hover {
background-color: rgba($gl-text-color-secondary, .07);
}
&.ci-manual {
color: $gl-text-color;
border-color: $gl-text-color;
&:not(span):hover {
background-color: rgba($gl-text-color, .07);
}
svg {
fill: $gl-text-color;
}
svg {
fill: $gl-text-color-secondary;
}
}
}
......
......@@ -6,7 +6,7 @@ module MilestoneActions
format.html { redirect_to milestone_redirect_path }
format.json do
render json: tabs_json("shared/milestones/_merge_requests_tab", {
merge_requests: @milestone.merge_requests,
merge_requests: @milestone.sorted_merge_requests,
show_project_name: true
})
end
......
......@@ -2,7 +2,7 @@ class Projects::MilestonesController < Projects::ApplicationController
include MilestoneActions
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
before_action :authorize_read_milestone!
......@@ -86,22 +86,6 @@ class Projects::MilestonesController < Projects::ApplicationController
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
def milestone
......
......@@ -33,8 +33,8 @@ class EventsFinder
private
def by_current_user_access(events)
events.merge(ProjectsFinder.new(current_user: current_user).execute).
joins(:project)
events.merge(ProjectsFinder.new(current_user: current_user).execute)
.joins(:project)
end
def by_action(events)
......
......@@ -5,8 +5,10 @@ class GroupsFinder < UnionFinder
end
def execute
groups = find_union(all_groups, Group).with_route.order_id_desc
by_parent(groups)
items = all_groups.map do |item|
by_parent(item)
end
find_union(items, Group).with_route.order_id_desc
end
private
......@@ -16,12 +18,22 @@ class GroupsFinder < UnionFinder
def all_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
end
def groups_for_ancestors
current_user.authorized_groups
end
def groups_for_descendants
current_user.groups
end
def by_parent(groups)
return groups unless params[:parent]
......
......@@ -46,6 +46,7 @@ class IssuableFinder
items = by_iids(items)
items = by_milestone(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
items = by_project(items)
......@@ -432,6 +433,18 @@ class IssuableFinder
params[:non_archived].present? ? items.non_archived : items
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?
params[:scope] == 'created-by-me' || params[:scope] == 'authored' || params[:scope] == 'assigned-to-me'
end
......
......@@ -170,9 +170,9 @@ module ApplicationHelper
css_classes = short_format ? 'js-short-timeago' : 'js-timeago'
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,
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,
data: {
toggle: 'tooltip',
......
......@@ -66,12 +66,12 @@ module DiffHelper
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)
discussions_left = @grouped_diff_discussions[line_code]
end
if right&.discussable?
if right && right.discussable? && right.added?
line_code = diff_file.line_code(right)
discussions_right = @grouped_diff_discussions[line_code]
end
......
......@@ -66,4 +66,17 @@ module EmailsHelper
)
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
......@@ -80,7 +80,7 @@ module ProjectsHelper
end
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 }
end
......@@ -151,14 +151,21 @@ module ProjectsHelper
disabled: disabled_option
)
content_tag(
:select,
options,
name: "project[project_feature_attributes][#{field}]",
id: "project_project_feature_attributes_#{field}",
class: "pull-right form-control #{repo_children_classes(field)}",
data: { field: field }
).html_safe
content_tag :div, class: "select-wrapper" do
concat(
content_tag(
:select,
options,
name: "project[project_feature_attributes][#{field}]",
id: "project_project_feature_attributes_#{field}",
class: "pull-right form-control select-control #{repo_children_classes(field)} ",
data: { field: field }
)
)
concat(
icon('chevron-down')
)
end.html_safe
end
def link_to_autodeploy_doc
......
......@@ -2,7 +2,9 @@ class DeviseMailer < Devise::Mailer
default from: "#{Gitlab.config.gitlab.email_display_name} <#{Gitlab.config.gitlab.email_from}>"
default reply_to: Gitlab.config.gitlab.email_reply_to
layout 'devise_mailer'
layout 'mailer/devise'
helper EmailsHelper
protected
......
......@@ -67,7 +67,6 @@ module Issuable
scope :authored, ->(user) { where(author_id: user) }
scope :recent, -> { reorder(id: :desc) }
scope :order_position_asc, -> { reorder(position: :asc) }
scope :of_projects, ->(ids) { where(project_id: ids) }
scope :of_milestones, ->(ids) { where(milestone_id: ids) }
scope :with_milestone, ->(title) { left_joins_milestones.where(milestones: { title: title }) }
......@@ -142,7 +141,6 @@ module Issuable
when 'upvotes_desc' then order_upvotes_desc
when 'label_priority' then order_labels_priority(excluded_labels: excluded_labels)
when 'priority' then order_due_date_and_labels_priority(excluded_labels: excluded_labels)
when 'position_asc' then order_position_asc
else
order_by(method)
end
......
......@@ -40,10 +40,18 @@ module Milestoneish
def issues_visible_to_user(user)
memoize_per_user(user, :issues_visible_to_user) do
IssuesFinder.new(user, issues_finder_params)
.execute.includes(:assignees).where(milestone_id: milestoneish_ids)
.execute.preload(:assignees).where(milestone_id: milestoneish_ids)
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?
start_date && start_date.future?
end
......
......@@ -47,7 +47,7 @@ module EE
end
if current_application_settings.elasticsearch_indexing?
ElasticCommitIndexerWorker.perform_async(project.id)
project.run_after_commit { ElasticCommitIndexerWorker.perform_async(project.id) }
end
end
......
class Geo::DeletedProject < ::Project
after_initialize :readonly!
attr_reader :full_path
def initialize(id:, name:, full_path:, repository_storage:)
repository_storage ||= current_application_settings.pick_repository_storage
super(id: id, name: name, repository_storage: repository_storage)
@full_path = full_path
end
def repository
@repository ||= Repository.new(full_path, self)
end
end
......@@ -12,6 +12,9 @@ class Issue < ActiveRecord::Base
include Elastic::IssuesSearch
include FasterCacheKeys
include RelativePositioning
include IgnorableColumn
ignore_column :position
WEIGHT_RANGE = 1..9
WEIGHT_ALL = 'Everything'.freeze
......@@ -54,7 +57,7 @@ class Issue < ActiveRecord::Base
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
......
......@@ -47,7 +47,7 @@ class LegacyDiffNote < Note
end
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
def original_line_code
......
......@@ -135,6 +135,12 @@ class License < ActiveRecord::Base
self.data = file.read
end
def md5
normalized_data = self.data.gsub("\r\n", "\n").gsub(/\n+$/, '') + "\n"
Digest::MD5.hexdigest(normalized_data)
end
def license
return nil unless self.data
......
......@@ -6,6 +6,9 @@ class MergeRequest < ActiveRecord::Base
include Sortable
include Elastic::MergeRequestsSearch
include Approvable
include IgnorableColumn
ignore_column :position
belongs_to :target_project, class_name: "Project"
belongs_to :source_project, class_name: "Project"
......
......@@ -10,6 +10,7 @@ class MergeRequestDiff < ActiveRecord::Base
VALID_CLASSES = [Hash, Rugged::Patch, Rugged::Diff::Delta].freeze
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_diffs # rubocop:disable Cop/ActiverecordSerialize
......@@ -91,7 +92,7 @@ class MergeRequestDiff < ActiveRecord::Base
head_commit_sha).diffs(options)
else
@raw_diffs ||= {}
@raw_diffs[options] ||= load_diffs(st_diffs, options)
@raw_diffs[options] ||= load_diffs(options)
end
end
......@@ -253,24 +254,44 @@ class MergeRequestDiff < ActiveRecord::Base
update_columns_serialized(new_attributes)
end
def dump_diffs(diffs)
if diffs.respond_to?(:map)
diffs.map(&:to_hash)
def create_merge_request_diff_files(diffs)
rows = diffs.map.with_index do |diff, index|
diff.to_hash.merge(
merge_request_diff_id: self.id,
relative_order: index
)
end
Gitlab::Database.bulk_insert('merge_request_diff_files', rows)
end
def load_diffs(raw, options)
if valid_raw_diff?(raw)
if paths = options[:paths]
raw = raw.select do |diff|
paths.include?(diff[:old_path]) || paths.include?(diff[:new_path])
end
end
def load_diffs(options)
return Gitlab::Git::DiffCollection.new([]) unless diffs_from_database
Gitlab::Git::DiffCollection.new(raw, options)
else
Gitlab::Git::DiffCollection.new([])
raw = 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)
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
# Load diffs between branches related to current merge request diff from repo
......@@ -285,11 +306,10 @@ class MergeRequestDiff < ActiveRecord::Base
new_attributes[:real_size] = diff_collection.real_size
if diff_collection.any?
new_diffs = dump_diffs(diff_collection)
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?
# 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
write_attribute(:title, sanitize_title(value)) if value.present?
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
def milestone_format_reference(format = :iid)
......
......@@ -41,10 +41,8 @@ class NotificationSetting < ActiveRecord::Base
:success_pipeline
].freeze
store :events, accessors: EMAIL_EVENTS, coder: JSON
before_create :set_events
before_save :events_to_boolean
store :events, coder: JSON
before_save :convert_events
def self.find_or_create_for(source)
setting = find_or_initialize_by(source: source)
......@@ -56,21 +54,18 @@ class NotificationSetting < ActiveRecord::Base
setting
end
# Set all event attributes to false when level is not custom or being initialized for UX reasons
def set_events
return if custom?
self.events = {}
end
# 1. Check if this event has a value stored in its database column.
# 2. If it does, return that value.
# 3. If it doesn't (the value is nil), return the value from the serialized
# JSON hash in `events`.
(EMAIL_EVENTS - [:failed_pipeline]).each do |event|
define_method(event) do
bool = super()
# Validates store accessors values as boolean
# 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
bool.nil? ? !!events[event] : bool
end
alias_method :"#{event}?", event
end
# Allow people to receive failed pipeline notifications if they already have
......@@ -78,7 +73,23 @@ class NotificationSetting < ActiveRecord::Base
# custom settings.
def failed_pipeline
bool = super
bool = events[:failed_pipeline] if bool.nil?
bool.nil? || bool
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
......@@ -275,10 +275,10 @@ class Project < ActiveRecord::Base
# logged in user.
def self.public_or_visible_to_user(user = nil)
if user
authorized = user.
project_authorizations.
select(1).
where('project_authorizations.project_id = projects.id')
authorized = user
.project_authorizations
.select(1)
.where('project_authorizations.project_id = projects.id')
levels = Gitlab::VisibilityLevel.levels_for_user(user)
......@@ -303,11 +303,11 @@ class Project < ActiveRecord::Base
elsif user
column = ProjectFeature.quoted_access_level_column(feature)
authorized = user.project_authorizations.select(1).
where('project_authorizations.project_id = projects.id')
authorized = user.project_authorizations.select(1)
.where('project_authorizations.project_id = projects.id')
with_project_feature.
where("#{column} IN (?) OR (#{column} = ? AND EXISTS (?))",
with_project_feature
.where("#{column} IN (?) OR (#{column} = ? AND EXISTS (?))",
visible,
ProjectFeature::PRIVATE,
authorized)
......
......@@ -150,21 +150,21 @@ class User < ActiveRecord::Base
presence: true,
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 :unique_email, if: ->(user) { user.email_changed? }
validate :owns_notification_email, if: ->(user) { user.notification_email_changed? }
validate :owns_public_email, if: ->(user) { user.public_email_changed? }
validate :unique_email, if: :email_changed?
validate :owns_notification_email, if: :notification_email_changed?
validate :owns_public_email, if: :public_email_changed?
validate :signup_domain_valid?, on: :create, if: ->(user) { !user.created_by_id }
validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
before_validation :sanitize_attrs
before_validation :set_notification_email, if: ->(user) { user.email_changed? }
before_validation :set_public_email, if: ->(user) { user.public_email_changed? }
before_validation :set_notification_email, if: :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_external_user_rights
before_save :ensure_user_rights_and_limits, if: :external_changed?
after_save :ensure_namespace_correct
after_initialize :set_projects_limit
after_destroy :post_destroy_hook
......@@ -1069,11 +1069,14 @@ class User < ActiveRecord::Base
super
end
def ensure_external_user_rights
return unless external?
self.can_create_group = false
self.projects_limit = 0
def ensure_user_rights_and_limits
if external?
self.can_create_group = false
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
def signup_domain_valid?
......
......@@ -6,10 +6,11 @@ class GroupEntity < Grape::Entity
expose :id, :name, :path, :description, :visibility
expose :full_name, :full_path
expose :web_url
expose :parent_id
expose :created_at, :updated_at
expose :web_url do |group|
expose :group_path do |group|
group_path(group)
end
......
......@@ -5,7 +5,6 @@ class IssuableEntity < Grape::Entity
expose :description
expose :lock_version
expose :milestone_id
expose :position
expose :state
expose :title
expose :updated_by_id
......
......@@ -8,7 +8,7 @@ class NotificationRecipientService
@project = project
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)
recipients = target.participants(current_user)
......@@ -59,7 +59,7 @@ class NotificationRecipientService
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)
......@@ -176,7 +176,7 @@ class NotificationRecipientService
if 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)
else
resource.notification_settings.pluck(:user_id)
......@@ -225,7 +225,7 @@ class NotificationRecipientService
def user_ids_with_global_level_custom(ids, action)
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)
end
......
......@@ -296,7 +296,7 @@ class NotificationService
end
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|
email = mailer.issue_moved_email(recipient, issue, new_issue, current_user)
......@@ -403,7 +403,7 @@ class NotificationService
end
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|
mailer.approved_merge_request_email(recipient.id, merge_request.id, current_user.id).deliver_later
......@@ -411,7 +411,7 @@ class NotificationService
end
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|
mailer.unapproved_merge_request_email(recipient.id, merge_request.id, current_user.id).deliver_later
......
......@@ -34,7 +34,7 @@ module Users
# 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
# hammering Redis too much we'll wait for a bit between retries.
sleep(1)
sleep(0.1)
end
begin
......
......@@ -341,8 +341,9 @@
%fieldset
%legend Metrics - Prometheus
%p
Setup Prometheus to measure a variety of statistics that partially overlap and complement Influx based metrics.
This setting requires a
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
= link_to 'here', admin_health_check_path
\. This setting requires a
= link_to 'restart', help_page_path('administration/restart_gitlab')
to take effect.
= link_to icon('question-circle'), help_page_path('administration/monitoring/performance/introduction')
......
......@@ -21,11 +21,11 @@
.form-group.js-toggle-colors-container.hide
= f.label :color, "Background Color", class: 'control-label'
.col-sm-10
= f.text_field :color, class: "form-control"
= f.color_field :color, class: "form-control"
.form-group.js-toggle-colors-container.hide
= f.label :font, "Font Color", class: 'control-label'
.col-sm-10
= f.text_field :font, class: "form-control"
= f.color_field :font, class: "form-control"
.form-group
= f.label :starts_at, class: 'control-label'
.col-sm-10.datetime-controls
......
.center
- if @resource.unconfirmed_email.present?
#content
%h2= @resource.unconfirmed_email
%p Click the link below to confirm your email address.
#cta
= link_to 'Confirm your email address', confirmation_url(@resource, confirmation_token: @token)
- else
#content
- if Gitlab.com?
%h2 Thanks for signing up to GitLab!
- else
%h2 Welcome, #{@resource.name}!
%p To get started, click the link below to confirm your account.
#cta
= link_to 'Confirm your account', confirmation_url(@resource, confirmation_token: @token)
- if @resource.unconfirmed_email.present?
#content
= email_default_heading(@resource.unconfirmed_email)
%p Click the link below to confirm your email address.
#cta
= link_to 'Confirm your email address', confirmation_url(@resource, confirmation_token: @token)
- else
#content
- if Gitlab.com?
= email_default_heading('Thanks for signing up to GitLab!')
- else
= email_default_heading("Welcome, #{@resource.name}!")
%p To get started, click the link below to confirm your account.
#cta
= link_to 'Confirm your account', confirmation_url(@resource, confirmation_token: @token)
.center
#content
%h2 Hello, #{@resource.name}!
%p
The password for your GitLab account on
#{link_to(Gitlab.config.gitlab.url, Gitlab.config.gitlab.url)}
has successfully been changed.
%p
If you did not initiate this change, please contact your administrator
immediately.
= email_default_heading("Hello, #{@resource.name}!")
%p
The password for your GitLab account on
#{link_to(Gitlab.config.gitlab.url, Gitlab.config.gitlab.url)}
has successfully been changed.
%p
If you did not initiate this change, please contact your administrator
immediately.
.center
#content
%h2 Hello, #{@resource.name}!
%p
Someone, hopefully you, has requested to reset the password for your
GitLab account on #{link_to(Gitlab.config.gitlab.url, Gitlab.config.gitlab.url)}.
%p
If you did not perform this request, you can safely ignore this email.
%p
Otherwise, click the link below to complete the process.
#cta
= link_to('Reset password', edit_password_url(@resource, reset_password_token: @token))
= email_default_heading("Hello, #{@resource.name}!")
%p
Someone, hopefully you, has requested to reset the password for your
GitLab account on #{link_to(Gitlab.config.gitlab.url, Gitlab.config.gitlab.url)}.
%p
If you did not perform this request, you can safely ignore this email.
%p
Otherwise, click the link below to complete the process.
#cta
= link_to('Reset password', edit_password_url(@resource, reset_password_token: @token))
.center
#content
%h2 Hello, #{@resource.name}!
%p
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)}
or you may click the link below to unlock now.
#cta
= link_to('Unlock account', unlock_url(@resource, unlock_token: @token))
#content
= email_default_heading("Hello, #{@resource.name}!")
%p
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)}
or you may click the link below to unlock now.
#cta
= 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">
%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
= render 'layouts/mailer'
- 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 @@
- if current_user.ldap_user?
Some options are unavailable for LDAP accounts
.col-lg-9
.form-group
= f.label :name, class: "label-light"
= f.text_field :name, class: "form-control", required: true
%span.help-block Enter your name, so people you know can recognize you.
.row
.form-group.col-md-9
= f.label :name, class: "label-light"
= 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
= f.label :email, class: "label-light"
......
- 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
.info.js-locked{ data: { help_block: visibility_level_description(@project.visibility_level, @project) } }
= visibility_level_icon(@project.visibility_level)
......
......@@ -9,8 +9,10 @@
.dropzone
.dropzone-previews.blob-upload-dropzone-previews
%p.dz-message.light
Attach a file by drag &amp; drop or
= link_to 'click to upload', '#', class: "markdown-selector"
- upload_link = link_to n_('UploadLink|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
.dropzone-alerts.alert.alert-danger.data{ style: "display:none" }
......@@ -18,7 +20,7 @@
.form-actions
= 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)
.inline.prepend-left-10
......
......@@ -2,7 +2,7 @@
- if !project.empty_repo? && can?(current_user, :download_code, project)
.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("caret-down")
%span.sr-only= _('Select Archive Format')
......
- if current_user
.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("caret-down")
%ul.dropdown-menu.dropdown-menu-align-right.project-home-dropdown
......
......@@ -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
= custom_icon('icon_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
= link_to new_namespace_project_fork_path(@project.namespace, @project), class: 'btn' do
= custom_icon('icon_fork')
%span= s_('CreateNewFork|Fork')
.count-with-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
- case type.to_s
- when 'revert'
- label = 'Revert'
- branch_label = 'Revert in branch'
- label = s_('ChangeTypeAction|Revert')
- 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'
- label = 'Cherry-pick'
- branch_label = 'Pick into branch'
- label = s_('ChangeTypeAction|Cherry-pick')
- 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-dialog
.modal-content
.modal-header
%a.close{ href: "#", "data-dismiss" => "modal" } ×
%h3.page-title== #{label} this #{commit.change_type_title(current_user)}
%h3.page-title= title
.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-group.branch
= label_tag 'start_branch', branch_label, class: 'control-label'
.col-sm-10
= 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)
.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
= render 'shared/new_merge_request_checkbox'
- else
= hidden_field_tag 'create_merge_request', 1, id: nil
.form-actions
= 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)
.inline.prepend-left-10
......
.page-content-header
.header-main-content
%strong
Commit
#{ s_('CommitBoxTitle|Commit') }
%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
#{time_ago_with_tooltip(@commit.authored_date)}
%span by
%span= s_('ByAuthor|by')
= author_avatar(@commit, size: 24)
%strong
= commit_author_link(@commit, avatar: true, size: 24)
- if @commit.different_committer?
%span.light Committed by
%span.light= _('Committed by')
%strong
= commit_committer_link(@commit, avatar: true, size: 24)
#{time_ago_with_tooltip(@commit.committed_date)}
......@@ -22,15 +22,15 @@
= icon('comment')
= @notes_count
= 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
%a.btn.btn-default.dropdown-toggle{ data: { toggle: "dropdown" } }
%span Options
%span= _('Options')
= icon('caret-down')
%ul.dropdown-menu.dropdown-menu-align-right
%li.visible-xs-block.visible-sm-block
= link_to namespace_project_tree_path(@project.namespace, @project, @commit) do
Browse Files
_('Browse Files')
- unless @commit.has_been_reverted?(current_user)
%li.clearfix
= revert_commit_link(@commit, namespace_project_commit_path(@project.namespace, @project, @commit.id), has_tooltip: false)
......@@ -38,13 +38,13 @@
= cherry_pick_commit_link(@commit, namespace_project_commit_path(@project.namespace, @project, @commit.id), has_tooltip: false)
- if can_collaborate_with_project?
%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.dropdown-header
Download
#{ _('Download') }
- unless @commit.parents.length > 1
%li= link_to "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|Email Patches"), namespace_project_commit_path(@project.namespace, @project, @commit, format: :patch)
%li= link_to s_("DownloadCommit|Plain Diff"), namespace_project_commit_path(@project.namespace, @project, @commit, format: :diff)
.commit-box
%h3.commit-title
......@@ -57,7 +57,7 @@
.well-segment.branch-info
.icon-container.commit-icon
= custom_icon("icon_commit")
%span.cgray= pluralize(@commit.parents.count, "parent")
%span.cgray= n_('parent', 'parents', @commit.parents.count)
- @commit.parents.each do |parent|
= link_to parent.short_id, namespace_project_commit_path(@project.namespace, @project, parent), class: "commit-sha"
%span.commit-info.branches
......@@ -69,11 +69,11 @@
.status-icon-container{ class: "ci-status-icon-#{@commit.status}" }
= link_to namespace_project_pipeline_path(@project.namespace, @project, last_pipeline.id) do
= ci_icon_for_status(last_pipeline.status)
Pipeline
#{ _('Pipeline') }
= link_to "##{last_pipeline.id}", namespace_project_pipeline_path(@project.namespace, @project, last_pipeline.id)
= ci_label_for_status(last_pipeline.status)
- 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
= render 'shared/mini_pipeline_graph', pipeline: last_pipeline, klass: 'js-commit-pipeline-graph'
in
......
......@@ -30,9 +30,11 @@
%pre.commit-row-description.js-toggle-content
= preserve(markdown(commit.description, pipeline: :single_line, author: commit.author))
.commiter
= commit_author_link(commit, avatar: false, size: 24)
#{ _('committed') }
#{time_ago_with_tooltip(commit.committed_date)}
- commit_author_link = commit_author_link(commit, avatar: false, size: 24)
- commit_timeago = 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
- if commit.status(ref)
......
......@@ -4,7 +4,7 @@
%h4
Deploy Keys
%button.btn.js-settings-toggle
= expanded ? 'Close' : 'Expand'
= expanded ? 'Collapse' : 'Expand'
%p
Deploy keys allow read-only or read-write (if enabled) access to your repository. Deploy keys can be used for CI, staging or production servers. You can create a deploy key or add an existing one.
.settings-content.no-animate{ class: ('expanded' if expanded) }
......
......@@ -20,7 +20,7 @@
.table-mobile-header{ role: 'rowheader' } Created
%span.table-mobile-content= time_ago_with_tooltip(deployment.created_at)
.table-section.section-20.environments-actions.table-button-footer{ role: 'gridcell' }
.btn-group.environment-action-buttons
.table-section.section-20.table-button-footer{ role: 'gridcell' }
.btn-group.table-action-button
= render 'projects/deployments/actions', deployment: deployment
= render 'projects/deployments/rollback', deployment: deployment
......@@ -113,9 +113,9 @@
Git Large File Storage
= link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs')
.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
.form-group.js-container-registry{ style: ("display: none;" if @project.project_feature.send(:repository_access_level) == 0) }
.checkbox
......
.form-horizontal.resolve-conflicts-form
.form-group
%label.col-sm-2.control-label{ "for" => "commit-message" }
Commit message
#{ _('Commit message') }
.col-sm-10
.commit-message-container
.max-width-marker
......
......@@ -4,7 +4,7 @@
%h4
Pull from a remote repository
%button.btn.js-settings-toggle
= expanded ? 'Close' : 'Expand'
= expanded ? 'Collapse' : 'Expand'
%p
Set up your project to automatically have its branches, tags, and commits
updated from an upstream repository every hour.
......@@ -53,7 +53,7 @@
%h4
Push to a remote repository
%button.btn.js-settings-toggle
= expanded ? 'Close' : 'Expand'
= expanded ? 'Collapse' : 'Expand'
%p
Set up the remote repository that you want to update with the content of the current repository
every time someone pushes to it.
......
......@@ -7,7 +7,7 @@
.form-group
.col-md-9
= 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
.col-md-9
= f.label :cron, _('Interval Pattern'), class: 'label-light'
......@@ -15,19 +15,19 @@
.form-group
.col-md-9
= 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
.form-group
.col-md-9
= 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
.form-group
.col-md-9
= f.label :active, _('PipelineSchedules|Activated'), class: 'label-light'
= f.label :active, s_('PipelineSchedules|Activated'), class: 'label-light'
%div
= f.check_box :active, required: false, value: @schedule.active?
Active
= _('Active')
.footer-block.row-content-block
= f.submit _('Save pipeline schedule'), class: 'btn btn-create', tabindex: 3
= link_to _('Cancel'), pipeline_schedules_path(@project), class: 'btn btn-cancel'
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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