Commit 6b63e11d authored by Constance Okoghenun's avatar Constance Okoghenun

Merge branch 'master' of https://gitlab.com/gitlab-org/gitlab-ce into boards-bundle-refactor

parents 7282dbea dfb14e4d
......@@ -36,7 +36,7 @@
"import/no-commonjs": "error",
"no-multiple-empty-lines": ["error", { "max": 1 }],
"promise/catch-or-return": "error",
"no-underscore-dangle": ["error", { "allow": ["__"]}],
"no-underscore-dangle": ["error", { "allow": ["__", "_links"]}],
"vue/html-self-closing": ["error", {
"html": {
"void": "always",
......
......@@ -2,6 +2,25 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
## 10.4.4 (2018-02-16)
### Security (1 change)
- Update nokogiri to 1.8.2. !16807
### Fixed (9 changes)
- Fix 500 error when loading a merge request with an invalid comment. !16795
- Cleanup new branch/merge request form in issues. !16854
- Fix GitLab import leaving group_id on ProjectLabel. !16877
- Fix forking projects when no restricted visibility levels are defined applicationwide. !16881
- Resolve PrepareUntrackedUploads PostgreSQL syntax error. !17019
- Fixed error 500 when removing an identity with synced attributes and visiting the profile page. !17054
- Validate user namespace before saving so that errors persist on model.
- LDAP Person no longer throws exception on invalid entry.
- Fix JIRA not working when a trailing slash is included.
## 10.4.3 (2018-02-05)
### Security (4 changes)
......
......@@ -75,6 +75,7 @@ export default class AjaxVariableList {
if (res.status === statusCodes.OK && res.data) {
this.updateRowsWithPersistedVariables(res.data.variables);
this.variableList.hideValues();
} else if (res.status === statusCodes.BAD_REQUEST) {
// Validation failed
this.errorBox.innerHTML = generateErrorBoxContent(res.data);
......
......@@ -178,6 +178,10 @@ export default class VariableList {
this.$container.find('.js-row-remove-button').attr('disabled', !isEnabled);
}
hideValues() {
this.secretValues.updateDom(false);
}
getAllData() {
// Ignore the last empty row because we don't want to try persist
// a blank variable and run into validation problems.
......
/* eslint-disable func-names, wrap-iife, consistent-return,
no-return-assign, no-param-reassign, one-var-declaration-per-line, no-unused-vars,
prefer-template, object-shorthand, prefer-arrow-callback */
import { pluralize } from './lib/utils/text_utility';
import { localTimeAgo } from './lib/utils/datetime_utility';
import Pager from './pager';
import axios from './lib/utils/axios_utils';
export default (function () {
const CommitsList = {};
CommitsList.timer = null;
export default class CommitsList {
constructor(limit = 0) {
this.timer = null;
CommitsList.init = function (limit) {
this.$contentList = $('.content_list');
$('body').on('click', '.day-commits-table li.commit', function (e) {
if (e.target.nodeName !== 'A') {
location.href = $(this).attr('url');
e.stopPropagation();
return false;
}
});
Pager.init(parseInt(limit, 10), false, false, this.processCommits);
Pager.init(parseInt(limit, 10), false, false, this.processCommits.bind(this));
this.content = $('#commits-list');
this.searchField = $('#commits-search');
this.lastSearch = this.searchField.val();
return this.initSearch();
};
this.initSearch();
}
CommitsList.initSearch = function () {
initSearch() {
this.timer = null;
return this.searchField.keyup((function (_this) {
return function () {
clearTimeout(_this.timer);
return _this.timer = setTimeout(_this.filterResults, 500);
};
})(this));
};
this.searchField.on('keyup', () => {
clearTimeout(this.timer);
this.timer = setTimeout(this.filterResults.bind(this), 500);
});
}
CommitsList.filterResults = function () {
filterResults() {
const form = $('.commits-search-form');
const search = CommitsList.searchField.val();
if (search === CommitsList.lastSearch) return Promise.resolve();
const commitsUrl = form.attr('action') + '?' + form.serialize();
CommitsList.content.fadeTo('fast', 0.5);
const search = this.searchField.val();
if (search === this.lastSearch) return Promise.resolve();
const commitsUrl = `${form.attr('action')}?${form.serialize()}`;
this.content.fadeTo('fast', 0.5);
const params = form.serializeArray().reduce((acc, obj) => Object.assign(acc, {
[obj.name]: obj.value,
}), {});
......@@ -55,9 +39,9 @@ export default (function () {
params,
})
.then(({ data }) => {
CommitsList.lastSearch = search;
CommitsList.content.html(data.html);
CommitsList.content.fadeTo('fast', 1.0);
this.lastSearch = search;
this.content.html(data.html);
this.content.fadeTo('fast', 1.0);
// Change url so if user reload a page - search results are saved
history.replaceState({
......@@ -65,16 +49,16 @@ export default (function () {
}, document.title, commitsUrl);
})
.catch(() => {
CommitsList.content.fadeTo('fast', 1.0);
CommitsList.lastSearch = null;
this.content.fadeTo('fast', 1.0);
this.lastSearch = null;
});
};
}
// Prepare loaded data.
CommitsList.processCommits = (data) => {
processCommits(data) {
let processedData = data;
const $processedData = $(processedData);
const $commitsHeadersLast = CommitsList.$contentList.find('li.js-commit-header').last();
const $commitsHeadersLast = this.$contentList.find('li.js-commit-header').last();
const lastShownDay = $commitsHeadersLast.data('day');
const $loadedCommitsHeadersFirst = $processedData.filter('li.js-commit-header').first();
const loadedShownDayFirst = $loadedCommitsHeadersFirst.data('day');
......@@ -97,7 +81,5 @@ export default (function () {
localTimeAgo($processedData.find('.js-timeago'));
return processedData;
};
return CommitsList;
})();
}
}
......@@ -230,6 +230,11 @@ var Dispatcher;
.then(callDefault)
.catch(fail);
break;
case 'projects:services:edit':
import('./pages/projects/services/edit')
.then(callDefault)
.catch(fail);
break;
case 'projects:snippets:edit':
case 'projects:snippets:update':
import('./pages/projects/snippets/edit')
......@@ -468,11 +473,6 @@ var Dispatcher;
.then(callDefault)
.catch(fail);
break;
case 'users:show':
import('./pages/users/show')
.then(callDefault)
.catch(fail);
break;
case 'admin:conversational_development_index:show':
import('./pages/admin/conversational_development_index/show')
.then(callDefault)
......
......@@ -59,29 +59,36 @@ class ImporterStatus {
.catch(() => flash(__('An error occurred while importing project')));
}
setAutoUpdate() {
return setInterval(() => $.get(this.jobsUrl, data => $.each(data, (i, job) => {
const jobItem = $(`#project_${job.id}`);
const statusField = jobItem.find('.job-status');
autoUpdate() {
return axios.get(this.jobsUrl)
.then(({ data = [] }) => {
data.forEach((job) => {
const jobItem = $(`#project_${job.id}`);
const statusField = jobItem.find('.job-status');
const spinner = '<i class="fa fa-spinner fa-spin"></i>';
const spinner = '<i class="fa fa-spinner fa-spin"></i>';
switch (job.import_status) {
case 'finished':
jobItem.removeClass('active').addClass('success');
statusField.html('<span><i class="fa fa-check"></i> done</span>');
break;
case 'scheduled':
statusField.html(`${spinner} scheduled`);
break;
case 'started':
statusField.html(`${spinner} started`);
break;
default:
statusField.html(job.import_status);
break;
}
});
});
}
switch (job.import_status) {
case 'finished':
jobItem.removeClass('active').addClass('success');
statusField.html('<span><i class="fa fa-check"></i> done</span>');
break;
case 'scheduled':
statusField.html(`${spinner} scheduled`);
break;
case 'started':
statusField.html(`${spinner} started`);
break;
default:
statusField.html(job.import_status);
break;
}
})), 4000);
setAutoUpdate() {
setInterval(this.autoUpdate.bind(this), 4000);
}
}
......
/* eslint-disable no-new */
import IntegrationSettingsForm from './integration_settings_form';
$(() => {
const integrationSettingsForm = new IntegrationSettingsForm('.js-integration-settings-form');
integrationSettingsForm.init();
});
<script>
import axios from '~/lib/utils/axios_utils';
import Flash from '~/flash';
import modal from '~/vue_shared/components/modal.vue';
import { s__ } from '~/locale';
import createFlash from '~/flash';
import GlModal from '~/vue_shared/components/gl_modal.vue';
import { redirectTo } from '~/lib/utils/url_utility';
import { s__ } from '~/locale';
export default {
components: {
modal,
GlModal,
},
props: {
url: {
......@@ -17,7 +17,7 @@
},
computed: {
text() {
return s__('AdminArea|You’re about to stop all jobs. This will halt all current jobs that are running.');
return s__('AdminArea|You’re about to stop all jobs.This will halt all current jobs that are running.');
},
},
methods: {
......@@ -28,7 +28,7 @@
redirectTo(response.request.responseURL);
})
.catch((error) => {
Flash(s__('AdminArea|Stopping jobs failed'));
createFlash(s__('AdminArea|Stopping jobs failed'));
throw error;
});
},
......@@ -37,11 +37,13 @@
</script>
<template>
<modal
<gl-modal
id="stop-jobs-modal"
:title="s__('AdminArea|Stop all jobs?')"
:text="text"
kind="danger"
:primary-button-label="s__('AdminArea|Stop jobs')"
@submit="onSubmit" />
:header-title-text="s__('AdminArea|Stop all jobs?')"
footer-primary-button-variant="danger"
:footer-primary-button-text="s__('AdminArea|Stop jobs')"
@submit="onSubmit"
>
{{ text }}
</gl-modal>
</template>
......@@ -8,22 +8,23 @@ Vue.use(Translate);
export default () => {
const stopJobsButton = document.getElementById('stop-jobs-button');
// eslint-disable-next-line no-new
new Vue({
el: '#stop-jobs-modal',
components: {
stopJobsModal,
},
mounted() {
stopJobsButton.classList.remove('disabled');
},
render(createElement) {
return createElement('stop-jobs-modal', {
props: {
url: stopJobsButton.dataset.url,
},
});
},
});
if (stopJobsButton) {
// eslint-disable-next-line no-new
new Vue({
el: '#stop-jobs-modal',
components: {
stopJobsModal,
},
mounted() {
stopJobsButton.classList.remove('disabled');
},
render(createElement) {
return createElement('stop-jobs-modal', {
props: {
url: stopJobsButton.dataset.url,
},
});
},
});
}
};
......@@ -3,7 +3,7 @@ import GpgBadges from '~/gpg_badges';
import ShortcutsNavigation from '~/shortcuts_navigation';
export default () => {
CommitsList.init(document.querySelector('.js-project-commits-show').dataset.commitsLimit);
new CommitsList(document.querySelector('.js-project-commits-show').dataset.commitsLimit); // eslint-disable-line no-new
new ShortcutsNavigation(); // eslint-disable-line no-new
GpgBadges.fetch();
};
import initForm from '../shared/init_form';
document.addEventListener('DOMContentLoaded', initForm);
import initForm from '../shared/init_form';
document.addEventListener('DOMContentLoaded', initForm);
import Vue from 'vue';
import PipelineSchedulesCallout from './components/pipeline_schedules_callout.vue';
import PipelineSchedulesCallout from '../shared/components/pipeline_schedules_callout.vue';
document.addEventListener('DOMContentLoaded', () => new Vue({
el: '#pipeline-schedules-callout',
......
import initForm from '../shared/init_form';
document.addEventListener('DOMContentLoaded', initForm);
<script>
import Vue from 'vue';
import Cookies from 'js-cookie';
import Translate from '../../vue_shared/translate';
import Translate from '../../../../../vue_shared/translate';
import illustrationSvg from '../icons/intro_illustration.svg';
Vue.use(Translate);
......
import Vue from 'vue';
import Translate from '../vue_shared/translate';
import GlFieldErrors from '../gl_field_errors';
import Translate from '../../../../vue_shared/translate';
import GlFieldErrors from '../../../../gl_field_errors';
import intervalPatternInput from './components/interval_pattern_input.vue';
import TimezoneDropdown from './components/timezone_dropdown';
import TargetBranchDropdown from './components/target_branch_dropdown';
import setupNativeFormVariableList from '../ci_variable_list/native_form_variable_list';
import setupNativeFormVariableList from '../../../../ci_variable_list/native_form_variable_list';
Vue.use(Translate);
......@@ -27,7 +27,7 @@ function initIntervalPatternInput() {
});
}
document.addEventListener('DOMContentLoaded', () => {
export default () => {
/* Most of the form is written in haml, but for fields with more complex behaviors,
* you should mount individual Vue components here. If at some point components need
* to share state, it may make sense to refactor the whole form to Vue */
......@@ -46,4 +46,4 @@ document.addEventListener('DOMContentLoaded', () => {
container: $('.js-ci-variable-list-section'),
formField: 'schedule',
});
});
};
import initForm from '../shared/init_form';
document.addEventListener('DOMContentLoaded', initForm);
import Chart from 'vendor/Chart';
const options = {
scaleOverlay: true,
responsive: true,
maintainAspectRatio: false,
};
const buildChart = (chartScope) => {
const data = {
labels: chartScope.labels,
datasets: [{
fillColor: '#707070',
strokeColor: '#707070',
pointColor: '#707070',
pointStrokeColor: '#EEE',
data: chartScope.totalValues,
},
{
fillColor: '#1aaa55',
strokeColor: '#1aaa55',
pointColor: '#1aaa55',
pointStrokeColor: '#fff',
data: chartScope.successValues,
},
],
};
const ctx = $(`#${chartScope.scope}Chart`).get(0).getContext('2d');
new Chart(ctx).Line(data, options);
};
document.addEventListener('DOMContentLoaded', () => {
const chartTimesData = JSON.parse(document.getElementById('pipelinesTimesChartsData').innerHTML);
const chartsData = JSON.parse(document.getElementById('pipelinesChartsData').innerHTML);
const data = {
labels: chartTimesData.labels,
datasets: [{
fillColor: 'rgba(220,220,220,0.5)',
strokeColor: 'rgba(220,220,220,1)',
barStrokeWidth: 1,
barValueSpacing: 1,
barDatasetSpacing: 1,
data: chartTimesData.values,
}],
};
if (window.innerWidth < 768) {
// Scale fonts if window width lower than 768px (iPad portrait)
options.scaleFontSize = 8;
}
new Chart($('#build_timesChart').get(0).getContext('2d')).Bar(data, options);
chartsData.forEach(scope => buildChart(scope));
});
import IntegrationSettingsForm from '~/integrations/integration_settings_form';
import PrometheusMetrics from '~/prometheus_metrics/prometheus_metrics';
export default () => {
const prometheusSettingsWrapper = document.querySelector('.js-prometheus-metrics-monitoring');
const integrationSettingsForm = new IntegrationSettingsForm('.js-integration-settings-form');
integrationSettingsForm.init();
if (prometheusSettingsWrapper) {
const prometheusMetrics = new PrometheusMetrics('.js-prometheus-metrics-monitoring');
prometheusMetrics.loadActiveMetrics();
}
};
import UserCallout from '~/user_callout';
import Cookies from 'js-cookie';
import UserTabs from './user_tabs';
......@@ -22,4 +23,5 @@ document.addEventListener('DOMContentLoaded', () => {
const page = $('body').attr('data-page');
const action = page.split(':')[1];
initUserProfile(action);
new UserCallout(); // eslint-disable-line no-new
});
import UserCallout from '~/user_callout';
export default () => new UserCallout();
import axios from '../lib/utils/axios_utils';
import Activities from '../activities';
import axios from '~/lib/utils/axios_utils';
import Activities from '~/activities';
import { localTimeAgo } from '~/lib/utils/datetime_utility';
import { __ } from '~/locale';
import flash from '~/flash';
import ActivityCalendar from './activity_calendar';
import { localTimeAgo } from '../lib/utils/datetime_utility';
import { __ } from '../locale';
import flash from '../flash';
/**
* UserTabs
......
import Chart from 'vendor/Chart';
document.addEventListener('DOMContentLoaded', () => {
const chartData = JSON.parse(document.getElementById('pipelinesChartsData').innerHTML);
const buildChart = (chartScope) => {
const data = {
labels: chartScope.labels,
datasets: [{
fillColor: '#707070',
strokeColor: '#707070',
pointColor: '#707070',
pointStrokeColor: '#EEE',
data: chartScope.totalValues,
},
{
fillColor: '#1aaa55',
strokeColor: '#1aaa55',
pointColor: '#1aaa55',
pointStrokeColor: '#fff',
data: chartScope.successValues,
},
],
};
const ctx = $(`#${chartScope.scope}Chart`).get(0).getContext('2d');
const options = {
scaleOverlay: true,
responsive: true,
maintainAspectRatio: false,
};
if (window.innerWidth < 768) {
// Scale fonts if window width lower than 768px (iPad portrait)
options.scaleFontSize = 8;
}
new Chart(ctx).Line(data, options);
};
chartData.forEach(scope => buildChart(scope));
});
import Chart from 'vendor/Chart';
document.addEventListener('DOMContentLoaded', () => {
const chartData = JSON.parse(document.getElementById('pipelinesTimesChartsData').innerHTML);
const data = {
labels: chartData.labels,
datasets: [{
fillColor: 'rgba(220,220,220,0.5)',
strokeColor: 'rgba(220,220,220,1)',
barStrokeWidth: 1,
barValueSpacing: 1,
barDatasetSpacing: 1,
data: chartData.values,
}],
};
const ctx = $('#build_timesChart').get(0).getContext('2d');
const options = {
scaleOverlay: true,
responsive: true,
maintainAspectRatio: false,
};
if (window.innerWidth < 768) {
// Scale fonts if window width lower than 768px (iPad portrait)
options.scaleFontSize = 8;
}
new Chart(ctx).Bar(data, options);
});
import PrometheusMetrics from './prometheus_metrics';
$(() => {
const prometheusMetrics = new PrometheusMetrics('.js-prometheus-metrics-monitoring');
prometheusMetrics.loadActiveMetrics();
});
<script>
const buttonVariants = [
'danger',
'primary',
'success',
'warning',
];
export default {
name: 'GlModal',
props: {
id: {
type: String,
required: false,
default: null,
},
headerTitleText: {
type: String,
required: false,
default: '',
},
footerPrimaryButtonVariant: {
type: String,
required: false,
default: 'primary',
validator: value => buttonVariants.indexOf(value) !== -1,
},
footerPrimaryButtonText: {
type: String,
required: false,
default: '',
},
},
methods: {
emitCancel(event) {
this.$emit('cancel', event);
},
emitSubmit(event) {
this.$emit('submit', event);
},
},
};
</script>
<template>
<div
:id="id"
class="modal fade"
tabindex="-1"
role="dialog"
>
<div
class="modal-dialog"
role="document"
>
<div class="modal-content">
<div class="modal-header">
<slot name="header">
<button
type="button"
class="close"
data-dismiss="modal"
:aria-label="s__('Modal|Close')"
@click="emitCancel($event)"
>
<span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title">
<slot name="title">
{{ headerTitleText }}
</slot>
</h4>
</slot>
</div>
<div class="modal-body">
<slot></slot>
</div>
<div class="modal-footer">
<slot name="footer">
<button
type="button"
class="btn"
data-dismiss="modal"
@click="emitCancel($event)"
>
{{ s__('Modal|Cancel') }}
</button>
<button
type="button"
class="btn"
:class="`btn-${footerPrimaryButtonVariant}`"
data-dismiss="modal"
@click="emitSubmit($event)"
>
{{ footerPrimaryButtonText }}
</button>
</slot>
</div>
</div>
</div>
</div>
</template>
......@@ -255,8 +255,6 @@ ul.controls {
}
.author_link {
display: inline-block;
.avatar-inline {
margin-left: 0;
margin-right: 0;
......
......@@ -9,6 +9,7 @@ class Identity < ActiveRecord::Base
validates :user_id, uniqueness: { scope: :provider }
before_save :ensure_normalized_extern_uid, if: :extern_uid_changed?
after_destroy :clear_user_synced_attributes, if: :user_synced_attributes_metadata_from_provider?
scope :with_provider, ->(provider) { where(provider: provider) }
scope :with_extern_uid, ->(provider, extern_uid) do
......@@ -34,4 +35,12 @@ class Identity < ActiveRecord::Base
self.extern_uid = Identity.normalize_uid(self.provider, self.extern_uid)
end
def user_synced_attributes_metadata_from_provider?
user.user_synced_attributes_metadata&.provider == provider
end
def clear_user_synced_attributes
user.user_synced_attributes_metadata&.destroy
end
end
......@@ -593,7 +593,15 @@ class Repository
def license_key
return unless exists?
Licensee.license(path).try(:key)
# The licensee gem creates a Rugged object from the path:
# https://github.com/benbalter/licensee/blob/v8.7.0/lib/licensee/projects/git_project.rb
begin
Licensee.license(path).try(:key)
# Normally we would rescue Rugged::Error, but that is banned by lint-rugged
# and we need to migrate this endpoint to Gitaly:
# https://gitlab.com/gitlab-org/gitaly/issues/1026
rescue
end
end
cache_method :license_key
......
......@@ -249,7 +249,7 @@ class User < ActiveRecord::Base
def find_for_database_authentication(warden_conditions)
conditions = warden_conditions.dup
if login = conditions.delete(:login)
where(conditions).find_by("lower(username) = :value OR lower(email) = :value", value: login.downcase)
where(conditions).find_by("lower(username) = :value OR lower(email) = :value", value: login.downcase.strip)
else
find_by(conditions)
end
......
......@@ -19,19 +19,10 @@ module Issues
# on rewriting notes (unfolding references)
#
ActiveRecord::Base.transaction do
# New issue tasks
#
@new_issue = create_new_issue
rewrite_notes
rewrite_issue_award_emoji
add_note_moved_from
# Old issue tasks
#
add_note_moved_to
close_issue
mark_as_moved
update_new_issue
update_old_issue
end
notify_participants
......@@ -41,6 +32,18 @@ module Issues
private
def update_new_issue
rewrite_notes
rewrite_issue_award_emoji
add_note_moved_from
end
def update_old_issue
add_note_moved_to
close_issue
mark_as_moved
end
def create_new_issue
new_params = { id: nil, iid: nil, label_ids: cloneable_label_ids,
milestone_id: cloneable_milestone_id,
......
......@@ -7,10 +7,9 @@
- build_path_proc = ->(scope) { admin_jobs_path(scope: scope) }
= render "shared/builds/tabs", build_path_proc: build_path_proc, all_builds: @all_builds, scope: @scope
.nav-controls
- if @all_builds.running_or_pending.any?
#stop-jobs-modal
- if @all_builds.running_or_pending.any?
#stop-jobs-modal
.nav-controls
%button#stop-jobs-button.btn.btn-danger{ data: { toggle: 'modal',
target: '#stop-jobs-modal',
url: cancel_all_admin_jobs_path } }
......
......@@ -58,7 +58,7 @@
- if @project.avatar?
%hr
= link_to _('Remove avatar'), project_avatar_path(@project), data: { confirm: _("Avatar will be removed. Are you sure?") }, method: :delete, class: "btn btn-danger btn-inverted"
= f.submit 'Save changes', class: "btn btn-success"
= f.submit 'Save changes', class: "btn btn-success js-btn-save-general-project-settings"
%section.settings.sharing-permissions.no-animate{ class: ('expanded' if expanded) }
.settings-header
......
- content_for :page_specific_javascripts do
= webpack_bundle_tag 'common_vue'
= webpack_bundle_tag 'schedule_form'
= form_for [@project.namespace.becomes(Namespace), @project, @schedule], as: :schedule, html: { id: "new-pipeline-schedule-form", class: "form-horizontal js-pipeline-schedule-form" } do |f|
= form_errors(@schedule)
.form-group
......
- breadcrumb_title _("Schedules")
- content_for :page_specific_javascripts do
= webpack_bundle_tag 'common_vue'
= webpack_bundle_tag 'schedules_index'
- @no_container = true
- page_title _("Pipeline Schedules")
......
- @no_container = true
- breadcrumb_title "CI / CD Charts"
- page_title _("Charts"), _("Pipelines")
- content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('common_d3')
= page_specific_javascript_bundle_tag('graphs')
%div{ class: container_class }
.sub-header-block
......
- content_for :page_specific_javascripts do
= webpack_bundle_tag('pipelines_times')
%div
%p.light
= _("Commit duration in minutes for last 30 commits")
......
- content_for :page_specific_javascripts do
= webpack_bundle_tag('pipelines_charts')
%h4= _("Pipelines charts")
%p
&nbsp;
......
- content_for :page_specific_javascripts do
= webpack_bundle_tag('integrations')
.row.prepend-top-default.append-bottom-default
.col-lg-3
%h4.prepend-top-0
......
- content_for :page_specific_javascripts do
= webpack_bundle_tag('prometheus_metrics')
.row.prepend-top-default.append-bottom-default.prometheus-metrics-monitoring.js-prometheus-metrics-monitoring
.col-lg-3
%h4.prepend-top-0
......
......@@ -6,4 +6,4 @@
$(".project-edit-errors").html("#{escape_javascript(render('errors'))}");
$('.save-project-loader').hide();
$('.project-edit-container').show();
$('.edit-project .btn-save').enable();
$('.edit-project .js-btn-save-general-project-settings').enable();
......@@ -23,11 +23,11 @@
- if show_archive_options
%li.divider
%li.js-filter-archived-projects
= link_to group_children_path(@group, archived: nil), class: ("is-active" unless params[:archived].present?) do
= link_to filter_groups_path(archived: nil), class: ("is-active" unless params[:archived].present?) do
Hide archived projects
%li.js-filter-archived-projects
= link_to group_children_path(@group, archived: true), class: ("is-active" if Gitlab::Utils.to_boolean(params[:archived])) do
= link_to filter_groups_path(archived: true), class: ("is-active" if Gitlab::Utils.to_boolean(params[:archived])) do
Show archived projects
%li.js-filter-archived-projects
= link_to group_children_path(@group, archived: 'only'), class: ("is-active" if params[:archived] == 'only') do
= link_to filter_groups_path(archived: 'only'), class: ("is-active" if params[:archived] == 'only') do
Show archived projects only
......@@ -4,9 +4,6 @@
- page_description @user.bio
- header_title @user.name, user_path(@user)
- @no_container = true
- content_for :page_specific_javascripts do
= webpack_bundle_tag 'common_d3'
= webpack_bundle_tag 'users'
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, user_url(@user, format: :atom), title: "#{@user.name} activity")
......
---
title: "Fix user avatar's vertical align on the issues and merge requests pages"
merge_request: 17072
author: Laszlo Karpati
type: fixed
title: Fix 404 when listing archived projects in a group where all projects have been archived
merge_request: 17077
author: Ashley Dumaine
type: fixed
---
title: API endpoint for importing a project export
merge_request: 17025
author:
type: added
---
title: Fix 500 error when loading a merge request with an invalid comment
merge_request: 16795
author:
type: fixed
---
title: Update nokogiri to 1.8.2
merge_request: 16807
author:
type: security
---
title: Fix GitLab import leaving group_id on ProjectLabel
merge_request: 16877
author:
type: fixed
---
title: Hide CI secret variable values after saving
merge_request: 17044
author:
type: changed
---
title: Allows project rename after validation error
merge_request: 17150
author:
type: fixed
---
title: Asciidoc now support inter-document cross references between files in repository
merge_request: 17125
author: Turo Soisenniemi
type: changed
---
title: Fix forking projects when no restricted visibility levels are defined applicationwide
merge_request: 16881
author:
type: fixed
---
title: Remove whitespace from the username/email sign in form field
merge_request: 17020
author: Peter lauck
type: changed
---
title: Fix JIRA not working when a trailing slash is included
title: Escape HTML entities in commit messages
merge_request:
author:
type: fixed
---
title: Validate user namespace before saving so that errors persist on model
merge_request:
author:
type: fixed
---
title: Fixed bug with unauthenticated requests through git ssh
merge_request: 17149
author:
type: fixed
---
title: Only check LFS integrity for first ref in a push to avoid timeout
merge_request: 17098
author:
type: performance
---
title: Resolve PrepareUntrackedUploads PostgreSQL syntax error
merge_request: 17019
author:
type: fixed
---
title: LDAP Person no longer throws exception on invalid entry
merge_request:
author:
type: fixed
---
title: Cleanup new branch/merge request form in issues
merge_request: 16854
author:
type: fixed
---
title: Add new modal Vue component
merge_request: 17108
author:
type: changed
......@@ -69,6 +69,7 @@ module Gitlab
# - Webhook URLs (:hook)
# - Sentry DSN (:sentry_dsn)
# - Deploy keys (:key)
# - Secret variable values (:value)
config.filter_parameters += [/token$/, /password/, /secret/]
config.filter_parameters += %i(
certificate
......@@ -80,6 +81,7 @@ module Gitlab
sentry_dsn
trace
variables
value
)
# Enable escaping HTML in JSON.
......
......@@ -26,6 +26,7 @@ class Rack::Attack
throttle('throttle_unauthenticated', Gitlab::Throttle.unauthenticated_options) do |req|
Gitlab::Throttle.settings.throttle_unauthenticated_enabled &&
req.unauthenticated? &&
!req.api_internal_request? &&
req.ip
end
......@@ -54,6 +55,10 @@ class Rack::Attack
path.start_with?('/api')
end
def api_internal_request?
path =~ %r{^/api/v\d+/internal/}
end
def web_request?
!api_request?
end
......
......@@ -67,7 +67,6 @@ var config = {
help: './help/help.js',
how_to_merge: './how_to_merge.js',
issue_show: './issue_show/index.js',
integrations: './integrations',
job_details: './jobs/job_details_bundle.js',
locale: './locale/index.js',
main: './main.js',
......@@ -78,19 +77,14 @@ var config = {
notes: './notes/index.js',
pdf_viewer: './blob/pdf_viewer.js',
pipelines: './pipelines/pipelines_bundle.js',
pipelines_charts: './pipelines/pipelines_charts.js',
pipelines_details: './pipelines/pipeline_details_bundle.js',
pipelines_times: './pipelines/pipelines_times.js',
profile: './profile/profile_bundle.js',
project_import_gl: './projects/project_import_gitlab_project.js',
prometheus_metrics: './prometheus_metrics',
protected_branches: './protected_branches',
protected_tags: './protected_tags',
registry_list: './registry/index.js',
ide: './ide/index.js',
sidebar: './sidebar/sidebar_bundle.js',
schedule_form: './pipeline_schedules/pipeline_schedule_form_bundle.js',
schedules_index: './pipeline_schedules/pipeline_schedules_index_bundle.js',
snippet: './snippet/snippet_bundle.js',
sketch_viewer: './blob/sketch_viewer.js',
stl_viewer: './blob/stl_viewer.js',
......@@ -101,7 +95,6 @@ var config = {
vue_merge_request_widget: './vue_merge_request_widget/index.js',
test: './test.js',
two_factor_auth: './two_factor_auth.js',
users: './users/index.js',
webpack_runtime: './webpack.js',
},
......@@ -157,7 +150,7 @@ var config = {
include: /node_modules\/katex\/dist/,
use: [
{ loader: 'style-loader' },
{
{
loader: 'css-loader',
options: {
name: '[name].[hash].[ext]'
......
......@@ -9,7 +9,19 @@ created in snippets, wikis, and repos.
## PlantUML Server
Before you can enable PlantUML in GitLab; you need to set up your own PlantUML
server that will generate the diagrams. Installing and configuring your
server that will generate the diagrams.
### Docker
With Docker, you can just run a container like this:
`docker run -d --name plantuml -p 8080:8080 plantuml/plantuml-server:tomcat`
The **PlantUML URL** will be the hostname of the server running the container.
### Debian/Ubuntu
Installing and configuring your
own PlantUML server is easy in Debian/Ubuntu distributions using Tomcat.
First you need to create a `plantuml.war` file from the source code:
......
......@@ -56,7 +56,7 @@ new one, and attempting to pull a repo.
> **Warning:** Do not disable writes until SSH is confirmed to be working
perfectly, because the file will quickly become out-of-date.
In the case of lookup failures (which are not uncommon), the `authorized_keys`
In the case of lookup failures (which are common), the `authorized_keys`
file will still be scanned. So git SSH performance will still be slow for many
users as long as a large file exists.
......
......@@ -61,6 +61,21 @@ Before proceeding with the Pages configuration, you will need to:
NOTE: **Note:**
If your GitLab instance and the Pages daemon are deployed in a private network or behind a firewall, your GitLab Pages websites will only be accessible to devices/users that have access to the private network.
### Add the domain to the Public Suffix List
The [Public Suffix List](https://publicsuffix.org) is used by browsers to
decide how to treat subdomains. If your GitLab instance allows members of the
public to create GitLab Pages sites, it also allows those users to create
subdomains on the pages domain (`example.io`). Adding the domain to the Public
Suffix List prevents browsers from accepting
[supercookies](https://en.wikipedia.org/wiki/HTTP_cookie#Supercookie),
among other things.
Follow [these instructions](https://publicsuffix.org/submit/) to submit your
GitLab Pages subdomain. For instance, if your domain is `example.io`, you should
request that `*.example.io` is added to the Public Suffix List. GitLab.com
added `*.gitlab.io` [in 2016](https://gitlab.com/gitlab-com/infrastructure/issues/230).
### DNS configuration
GitLab Pages expect to run on their own virtual host. In your DNS server/provider
......
......@@ -43,6 +43,7 @@ following locations:
- [Pipeline Schedules](pipeline_schedules.md)
- [Projects](projects.md) including setting Webhooks
- [Project Access Requests](access_requests.md)
- [Project import/export](project_import_export.md)
- [Project Members](members.md)
- [Project Snippets](project_snippets.md)
- [Protected Branches](protected_branches.md)
......
# Project import API
[Introduced][ce-41899] in GitLab 10.6
[See also the project import/export documentation](../user/project/settings/import_export.md)
## Import a file
```http
POST /projects/import
```
| Attribute | Type | Required | Description |
| --------- | -------------- | -------- | ---------------------------------------- |
| `namespace` | integer/string | no | The ID or path of the namespace that the project will be imported to. Defaults to the current user's namespace |
| `file` | string | yes | The file to be uploaded |
| `path` | string | yes | Name and path for new project |
To upload a file from your filesystem, use the `--form` argument. This causes
cURL to post data using the header `Content-Type: multipart/form-data`.
The `file=` parameter must point to a file on your filesystem and be preceded
by `@`. For example:
```console
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --form "path=api-project" --form "file=@/path/to/file" https://gitlab.example.com/api/v4/projects/import
```
```json
{
"id": 1,
"description": null,
"name": "api-project",
"name_with_namespace": "Administrator / api-project",
"path": "api-project",
"path_with_namespace": "root/api-project",
"created_at": "2018-02-13T09:05:58.023Z",
"import_status": "scheduled"
}
```
## Import status
Get the status of an import.
```http
GET /projects/:id/import
```
| Attribute | Type | Required | Description |
| --------- | -------------- | -------- | ---------------------------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
```console
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/import
```
Status can be one of `none`, `scheduled`, `failed`, `started`, or `finished`.
If the status is `failed`, it will include the import error message under `import_error`.
```json
{
"id": 1,
"description": "Itaque perspiciatis minima aspernatur corporis consequatur.",
"name": "Gitlab Test",
"name_with_namespace": "Gitlab Org / Gitlab Test",
"path": "gitlab-test",
"path_with_namespace": "gitlab-org/gitlab-test",
"created_at": "2017-08-29T04:36:44.383Z",
"import_status": "started"
}
```
[ce-41899]: https://gitlab.com/gitlab-org/gitlab-ce/issues/41899
......@@ -1330,6 +1330,10 @@ POST /projects/:id/housekeeping
Read more in the [Branches](branches.md) documentation.
## Project Import/Export
Read more in the [Project import/export](project_import_export.md) documentation.
## Project members
Read more in the [Project members](members.md) documentation.
......@@ -70,6 +70,8 @@ learn how to leverage its potential even more.
- [Use SSH keys in your build environment](ssh_keys/README.md)
- [Trigger pipelines through the GitLab API](triggers/README.md)
- [Trigger pipelines on a schedule](../user/project/pipelines/schedules.md)
- [Kubernetes clusters](../user/project/clusters/index.md) - Integrate one or
more Kubernetes clusters to your project
## GitLab CI/CD for Docker
......
# Components
## Contents
* [Dropdowns](#dropdowns)
* [Modals](#modals)
## Dropdowns
See also the [corresponding UX guide](../ux_guide/components.md#dropdowns).
### How to style a bootstrap dropdown
1. Use the HTML structure provided by the [docs][bootstrap-dropdowns]
1. Add a specific class to the top level `.dropdown` element
```Haml
.dropdown.my-dropdown
%button{ type: 'button', data: { toggle: 'dropdown' }, 'aria-haspopup': true, 'aria-expanded': false }
%span.dropdown-toggle-text
Toggle Dropdown
= icon('chevron-down')
%ul.dropdown-menu
%li
%a
item!
```
Or use the helpers
```Haml
.dropdown.my-dropdown
= dropdown_toggle('Toogle!', { toggle: 'dropdown' })
= dropdown_content
%li
%a
item!
```
[bootstrap-dropdowns]: https://getbootstrap.com/docs/3.3/javascript/#dropdowns
## Modals
See also the [corresponding UX guide](../ux_guide/components.md#modals).
We have a reusable Vue component for modals: [vue_shared/components/gl-modal.vue](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/app/assets/javascripts/vue_shared/components/gl-modal.vue)
Here is an example of how to use it:
```html
<gl-modal
id="dogs-out-modal"
:header-title-text="s__('ModalExample|Let the dogs out?')"
footer-primary-button-variant="danger"
:footer-primary-button-text="s__('ModalExample|Let them out')"
@submit="letOut(theDogs)"
>
{{ s__('ModalExample|You’re about to let the dogs out.') }}
</gl-modal>
```
![example modal](img/gl-modal.png)
# Dropdowns
## How to style a bootstrap dropdown
1. Use the HTML structure provided by the [docs][bootstrap-dropdowns]
1. Add a specific class to the top level `.dropdown` element
```Haml
.dropdown.my-dropdown
%button{ type: 'button', data: { toggle: 'dropdown' }, 'aria-haspopup': true, 'aria-expanded': false }
%span.dropdown-toggle-text
Toggle Dropdown
= icon('chevron-down')
%ul.dropdown-menu
%li
%a
item!
```
Or use the helpers
```Haml
.dropdown.my-dropdown
= dropdown_toggle('Toogle!', { toggle: 'dropdown' })
= dropdown_content
%li
%a
item!
```
[bootstrap-dropdowns]: https://getbootstrap.com/docs/3.3/javascript/#dropdowns
This page has moved [here](components.md#dropdowns).
......@@ -21,6 +21,8 @@ Working with our frontend assets requires Node (v4.3 or greater) and Yarn
[jQuery][jquery] is used throughout the application's JavaScript, with
[Vue.js][vue] for particularly advanced, dynamic elements.
We also use [Axios][axios] to handle all of our network requests.
### Browser Support
For our currently-supported browsers, see our [requirements][requirements].
......@@ -77,8 +79,10 @@ Axios specific practices and gotchas.
## [Icons](icons.md)
How we use SVG for our Icons.
## [Dropdowns](dropdowns.md)
How we use dropdowns.
## [Components](components.md)
How we use UI components.
---
## Style Guides
......@@ -122,6 +126,7 @@ The [externalization part of the guide](../i18n/externalization.md) explains the
[webpack]: https://webpack.js.org/
[jquery]: https://jquery.com/
[vue]: http://vuejs.org/
[axios]: https://github.com/axios/axios
[airbnb-js-style-guide]: https://github.com/airbnb/javascript
[scss-lint]: https://github.com/brigade/scss-lint
[install]: ../../install/installation.md#4-node
......
......@@ -27,6 +27,17 @@ Gitlab::Profiler.profile('/my-user')
# Returns a RubyProf::Profile where 100 seconds is spent in UsersController#show
```
For routes that require authorization you will need to provide a user to
`Gitlab::Profiler`. You can do this like so:
```ruby
Gitlab::Profiler.profile('/gitlab-org/gitlab-test', user: User.first)
```
The user you provide will need to have a [personal access
token](https://docs.gitlab.com/ce/user/profile/personal_access_tokens.html) in
the GitLab instance.
Passing a `logger:` keyword argument to `Gitlab::Profiler.profile` will send
ActiveRecord and ActionController log output to that logger. Further options are
documented with the method source.
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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