Commit 5a7f3cc1 authored by GitLab Bot's avatar GitLab Bot

Merge remote-tracking branch 'upstream/master' into ce-to-ee-2018-05-03

# Conflicts:
#	app/models/identity.rb
#	doc/topics/autodevops/index.md

[ci skip]
parents 9763cc76 bba847fe
...@@ -33,7 +33,7 @@ gem 'grape-route-helpers', '~> 2.1.0' ...@@ -33,7 +33,7 @@ gem 'grape-route-helpers', '~> 2.1.0'
gem 'faraday', '~> 0.12' gem 'faraday', '~> 0.12'
# Authentication libraries # Authentication libraries
gem 'devise', '~> 4.2' gem 'devise', '~> 4.4'
gem 'doorkeeper', '~> 4.3' gem 'doorkeeper', '~> 4.3'
gem 'doorkeeper-openid_connect', '~> 1.3' gem 'doorkeeper-openid_connect', '~> 1.3'
gem 'omniauth', '~> 1.8' gem 'omniauth', '~> 1.8'
......
...@@ -151,7 +151,7 @@ GEM ...@@ -151,7 +151,7 @@ GEM
connection_pool (2.2.1) connection_pool (2.2.1)
crack (0.4.3) crack (0.4.3)
safe_yaml (~> 1.0.0) safe_yaml (~> 1.0.0)
crass (1.0.3) crass (1.0.4)
creole (0.5.0) creole (0.5.0)
css_parser (1.5.0) css_parser (1.5.0)
addressable addressable
...@@ -170,10 +170,10 @@ GEM ...@@ -170,10 +170,10 @@ GEM
descendants_tracker (0.0.4) descendants_tracker (0.0.4)
thread_safe (~> 0.3, >= 0.3.1) thread_safe (~> 0.3, >= 0.3.1)
device_detector (1.0.0) device_detector (1.0.0)
devise (4.2.0) devise (4.4.3)
bcrypt (~> 3.0) bcrypt (~> 3.0)
orm_adapter (~> 0.1) orm_adapter (~> 0.1)
railties (>= 4.1.0, < 5.1) railties (>= 4.1.0, < 6.0)
responders responders
warden (~> 1.2.3) warden (~> 1.2.3)
devise-two-factor (3.0.0) devise-two-factor (3.0.0)
...@@ -675,7 +675,7 @@ GEM ...@@ -675,7 +675,7 @@ GEM
pry (>= 0.9.10) pry (>= 0.9.10)
public_suffix (3.0.2) public_suffix (3.0.2)
pyu-ruby-sasl (0.0.3.3) pyu-ruby-sasl (0.0.3.3)
rack (1.6.9) rack (1.6.10)
rack-accept (0.4.5) rack-accept (0.4.5)
rack (>= 0.4) rack (>= 0.4)
rack-attack (4.4.1) rack-attack (4.4.1)
...@@ -723,7 +723,7 @@ GEM ...@@ -723,7 +723,7 @@ GEM
rainbow (2.2.2) rainbow (2.2.2)
rake rake
raindrops (0.18.0) raindrops (0.18.0)
rake (12.3.0) rake (12.3.1)
rb-fsevent (0.10.2) rb-fsevent (0.10.2)
rb-inotify (0.9.10) rb-inotify (0.9.10)
ffi (>= 0.5.0, < 2) ffi (>= 0.5.0, < 2)
...@@ -764,8 +764,9 @@ GEM ...@@ -764,8 +764,9 @@ GEM
declarative-option (< 0.2.0) declarative-option (< 0.2.0)
uber (< 0.2.0) uber (< 0.2.0)
request_store (1.3.1) request_store (1.3.1)
responders (2.3.0) responders (2.4.0)
railties (>= 4.2.0, < 5.1) actionpack (>= 4.2.0, < 5.3)
railties (>= 4.2.0, < 5.3)
rest-client (2.0.2) rest-client (2.0.2)
http-cookie (>= 1.0.2, < 2.0) http-cookie (>= 1.0.2, < 2.0)
mime-types (>= 1.16, < 4.0) mime-types (>= 1.16, < 4.0)
...@@ -995,7 +996,7 @@ GEM ...@@ -995,7 +996,7 @@ GEM
descendants_tracker (~> 0.0, >= 0.0.3) descendants_tracker (~> 0.0, >= 0.0.3)
equalizer (~> 0.0, >= 0.0.9) equalizer (~> 0.0, >= 0.0.9)
vmstat (2.3.0) vmstat (2.3.0)
warden (1.2.6) warden (1.2.7)
rack (>= 1.0) rack (>= 1.0)
webmock (2.3.2) webmock (2.3.2)
addressable (>= 2.3.6) addressable (>= 2.3.6)
...@@ -1058,7 +1059,7 @@ DEPENDENCIES ...@@ -1058,7 +1059,7 @@ DEPENDENCIES
deckar01-task_list (= 2.0.0) deckar01-task_list (= 2.0.0)
default_value_for (~> 3.0.0) default_value_for (~> 3.0.0)
device_detector device_detector
devise (~> 4.2) devise (~> 4.4)
devise-two-factor (~> 3.0.0) devise-two-factor (~> 3.0.0)
diffy (~> 3.1.0) diffy (~> 3.1.0)
doorkeeper (~> 4.3) doorkeeper (~> 4.3)
......
...@@ -162,6 +162,7 @@ GEM ...@@ -162,6 +162,7 @@ GEM
activerecord (>= 3.2.0, < 5.2) activerecord (>= 3.2.0, < 5.2)
descendants_tracker (0.0.4) descendants_tracker (0.0.4)
thread_safe (~> 0.3, >= 0.3.1) thread_safe (~> 0.3, >= 0.3.1)
device_detector (1.0.1)
devise (4.4.1) devise (4.4.1)
bcrypt (~> 3.0) bcrypt (~> 3.0)
orm_adapter (~> 0.1) orm_adapter (~> 0.1)
...@@ -375,7 +376,7 @@ GEM ...@@ -375,7 +376,7 @@ GEM
rake rake
grape_logging (1.7.0) grape_logging (1.7.0)
grape grape
grpc (1.10.0) grpc (1.11.0)
google-protobuf (~> 3.1) google-protobuf (~> 3.1)
googleapis-common-protos-types (~> 1.0.0) googleapis-common-protos-types (~> 1.0.0)
googleauth (>= 0.5.1, < 0.7) googleauth (>= 0.5.1, < 0.7)
...@@ -554,9 +555,6 @@ GEM ...@@ -554,9 +555,6 @@ GEM
jwt (>= 1.5) jwt (>= 1.5)
omniauth (>= 1.1.1) omniauth (>= 1.1.1)
omniauth-oauth2 (>= 1.5) omniauth-oauth2 (>= 1.5)
omniauth-jwt (0.0.2)
jwt
omniauth (~> 1.1)
omniauth-kerberos (0.3.0) omniauth-kerberos (0.3.0)
omniauth-multipassword omniauth-multipassword
timfel-krb5-auth (~> 0.8) timfel-krb5-auth (~> 0.8)
...@@ -1033,6 +1031,7 @@ DEPENDENCIES ...@@ -1033,6 +1031,7 @@ DEPENDENCIES
database_cleaner (~> 1.5.0) database_cleaner (~> 1.5.0)
deckar01-task_list (= 2.0.0) deckar01-task_list (= 2.0.0)
default_value_for (~> 3.0.5) default_value_for (~> 3.0.5)
device_detector
devise (~> 4.2) devise (~> 4.2)
devise-two-factor (~> 3.0.0) devise-two-factor (~> 3.0.0)
diffy (~> 3.1.0) diffy (~> 3.1.0)
...@@ -1080,7 +1079,7 @@ DEPENDENCIES ...@@ -1080,7 +1079,7 @@ DEPENDENCIES
grape-entity (~> 0.6.0) grape-entity (~> 0.6.0)
grape-route-helpers (~> 2.1.0) grape-route-helpers (~> 2.1.0)
grape_logging (~> 1.7) grape_logging (~> 1.7)
grpc (~> 1.10.0) grpc (~> 1.11.0)
haml_lint (~> 0.26.0) haml_lint (~> 0.26.0)
hamlit (~> 2.6.1) hamlit (~> 2.6.1)
hashie-forbidden_attributes hashie-forbidden_attributes
...@@ -1121,7 +1120,6 @@ DEPENDENCIES ...@@ -1121,7 +1120,6 @@ DEPENDENCIES
omniauth-github (~> 1.1.1) omniauth-github (~> 1.1.1)
omniauth-gitlab (~> 1.0.2) omniauth-gitlab (~> 1.0.2)
omniauth-google-oauth2 (~> 0.5.3) omniauth-google-oauth2 (~> 0.5.3)
omniauth-jwt (~> 0.0.2)
omniauth-kerberos (~> 0.3.0) omniauth-kerberos (~> 0.3.0)
omniauth-oauth2-generic (~> 0.2.2) omniauth-oauth2-generic (~> 0.2.2)
omniauth-saml (~> 1.10) omniauth-saml (~> 1.10)
......
/* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, no-var, object-shorthand, consistent-return, no-unused-vars, comma-dangle, vars-on-top, prefer-template, max-len */
import $ from 'jquery';
import { localTimeAgo } from './lib/utils/datetime_utility';
import axios from './lib/utils/axios_utils';
export default class Compare {
constructor(opts) {
this.opts = opts;
this.source_loading = $(".js-source-loading");
this.target_loading = $(".js-target-loading");
$('.js-compare-dropdown').each((function(_this) {
return function(i, dropdown) {
var $dropdown;
$dropdown = $(dropdown);
return $dropdown.glDropdown({
selectable: true,
fieldName: $dropdown.data('fieldName'),
filterable: true,
id: function(obj, $el) {
return $el.data('id');
},
toggleLabel: function(obj, $el) {
return $el.text().trim();
},
clicked: function(e, el) {
if ($dropdown.is('.js-target-branch')) {
return _this.getTargetHtml();
} else if ($dropdown.is('.js-source-branch')) {
return _this.getSourceHtml();
} else if ($dropdown.is('.js-target-project')) {
return _this.getTargetProject();
}
}
});
};
})(this));
this.initialState();
}
initialState() {
this.getSourceHtml();
this.getTargetHtml();
}
getTargetProject() {
$('.mr_target_commit').empty();
return axios.get(this.opts.targetProjectUrl, {
params: {
target_project_id: $("input[name='merge_request[target_project_id]']").val(),
},
}).then(({ data }) => {
$('.js-target-branch-dropdown .dropdown-content').html(data);
});
}
getSourceHtml() {
return this.constructor.sendAjax(this.opts.sourceBranchUrl, this.source_loading, '.mr_source_commit', {
ref: $("input[name='merge_request[source_branch]']").val()
});
}
getTargetHtml() {
return this.constructor.sendAjax(this.opts.targetBranchUrl, this.target_loading, '.mr_target_commit', {
target_project_id: $("input[name='merge_request[target_project_id]']").val(),
ref: $("input[name='merge_request[target_branch]']").val()
});
}
static sendAjax(url, loading, target, params) {
const $target = $(target);
loading.show();
$target.empty();
return axios.get(url, {
params,
}).then(({ data }) => {
loading.hide();
$target.html(data);
const className = '.' + $target[0].className.replace(' ', '.');
localTimeAgo($('.js-timeago', className));
});
}
}
...@@ -4,8 +4,9 @@ import $ from 'jquery'; ...@@ -4,8 +4,9 @@ import $ from 'jquery';
import { __ } from './locale'; import { __ } from './locale';
import axios from './lib/utils/axios_utils'; import axios from './lib/utils/axios_utils';
import flash from './flash'; import flash from './flash';
import { capitalizeFirstCharacter } from './lib/utils/text_utility';
export default function initCompareAutocomplete() { export default function initCompareAutocomplete(limitTo = null, clickHandler = () => {}) {
$('.js-compare-dropdown').each(function() { $('.js-compare-dropdown').each(function() {
var $dropdown, selected; var $dropdown, selected;
$dropdown = $(this); $dropdown = $(this);
...@@ -15,14 +16,27 @@ export default function initCompareAutocomplete() { ...@@ -15,14 +16,27 @@ export default function initCompareAutocomplete() {
const $filterInput = $('input[type="search"]', $dropdownContainer); const $filterInput = $('input[type="search"]', $dropdownContainer);
$dropdown.glDropdown({ $dropdown.glDropdown({
data: function(term, callback) { data: function(term, callback) {
axios.get($dropdown.data('refsUrl'), { const params = {
params: { ref: $dropdown.data('ref'),
ref: $dropdown.data('ref'), search: term,
search: term, };
},
}).then(({ data }) => { if (limitTo) {
callback(data); params.find = limitTo;
}).catch(() => flash(__('Error fetching refs'))); }
axios
.get($dropdown.data('refsUrl'), {
params,
})
.then(({ data }) => {
if (limitTo) {
callback(data[capitalizeFirstCharacter(limitTo)] || []);
} else {
callback(data);
}
})
.catch(() => flash(__('Error fetching refs')));
}, },
selectable: true, selectable: true,
filterable: true, filterable: true,
...@@ -32,9 +46,15 @@ export default function initCompareAutocomplete() { ...@@ -32,9 +46,15 @@ export default function initCompareAutocomplete() {
renderRow: function(ref) { renderRow: function(ref) {
var link; var link;
if (ref.header != null) { if (ref.header != null) {
return $('<li />').addClass('dropdown-header').text(ref.header); return $('<li />')
.addClass('dropdown-header')
.text(ref.header);
} else { } else {
link = $('<a />').attr('href', '#').addClass(ref === selected ? 'is-active' : '').text(ref).attr('data-ref', escape(ref)); link = $('<a />')
.attr('href', '#')
.addClass(ref === selected ? 'is-active' : '')
.text(ref)
.attr('data-ref', escape(ref));
return $('<li />').append(link); return $('<li />').append(link);
} }
}, },
...@@ -43,9 +63,10 @@ export default function initCompareAutocomplete() { ...@@ -43,9 +63,10 @@ export default function initCompareAutocomplete() {
}, },
toggleLabel: function(obj, $el) { toggleLabel: function(obj, $el) {
return $el.text().trim(); return $el.text().trim();
} },
clicked: () => clickHandler($dropdown),
}); });
$filterInput.on('keyup', (e) => { $filterInput.on('keyup', e => {
const keyCode = e.keyCode || e.which; const keyCode = e.keyCode || e.which;
if (keyCode !== 13) return; if (keyCode !== 13) return;
const text = $filterInput.val(); const text = $filterInput.val();
...@@ -54,7 +75,7 @@ export default function initCompareAutocomplete() { ...@@ -54,7 +75,7 @@ export default function initCompareAutocomplete() {
$dropdownContainer.removeClass('open'); $dropdownContainer.removeClass('open');
}); });
$dropdownContainer.on('click', '.dropdown-content a', (e) => { $dropdownContainer.on('click', '.dropdown-content a', e => {
$dropdown.prop('title', e.target.text.replace(/_+?/g, '-')); $dropdown.prop('title', e.target.text.replace(/_+?/g, '-'));
if ($dropdown.hasClass('has-tooltip')) { if ($dropdown.hasClass('has-tooltip')) {
$dropdown.tooltip('fixTitle'); $dropdown.tooltip('fixTitle');
......
<script> <script>
import playIconSvg from 'icons/_icon_play.svg'; import Icon from '~/vue_shared/components/icon.vue';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
import loadingIcon from '../../vue_shared/components/loading_icon.vue'; import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import tooltip from '../../vue_shared/directives/tooltip'; import tooltip from '../../vue_shared/directives/tooltip';
...@@ -8,9 +8,9 @@ ...@@ -8,9 +8,9 @@
directives: { directives: {
tooltip, tooltip,
}, },
components: { components: {
loadingIcon, loadingIcon,
Icon,
}, },
props: { props: {
actions: { actions: {
...@@ -19,20 +19,16 @@ ...@@ -19,20 +19,16 @@
default: () => [], default: () => [],
}, },
}, },
data() { data() {
return { return {
playIconSvg,
isLoading: false, isLoading: false,
}; };
}, },
computed: { computed: {
title() { title() {
return 'Deploy to...'; return 'Deploy to...';
}, },
}, },
methods: { methods: {
onClickAction(endpoint) { onClickAction(endpoint) {
this.isLoading = true; this.isLoading = true;
...@@ -65,7 +61,10 @@ ...@@ -65,7 +61,10 @@
:disabled="isLoading" :disabled="isLoading"
> >
<span> <span>
<span v-html="playIconSvg"></span> <icon
name="play"
:size="12"
/>
<i <i
class="fa fa-caret-down" class="fa fa-caret-down"
aria-hidden="true" aria-hidden="true"
...@@ -86,7 +85,10 @@ ...@@ -86,7 +85,10 @@
:class="{ disabled: isActionDisabled(action) }" :class="{ disabled: isActionDisabled(action) }"
:disabled="isActionDisabled(action)" :disabled="isActionDisabled(action)"
> >
<span v-html="playIconSvg"></span> <icon
name="play"
:size="12"
/>
<span> <span>
{{ action.name }} {{ action.name }}
</span> </span>
......
<script> <script>
import Icon from '~/vue_shared/components/icon.vue';
import tooltip from '../../vue_shared/directives/tooltip'; import tooltip from '../../vue_shared/directives/tooltip';
import { s__ } from '../../locale'; import { s__ } from '../../locale';
...@@ -6,6 +7,9 @@ ...@@ -6,6 +7,9 @@
* Renders the external url link in environments table. * Renders the external url link in environments table.
*/ */
export default { export default {
components: {
Icon,
},
directives: { directives: {
tooltip, tooltip,
}, },
...@@ -15,7 +19,6 @@ ...@@ -15,7 +19,6 @@
required: true, required: true,
}, },
}, },
computed: { computed: {
title() { title() {
return s__('Environments|Open'); return s__('Environments|Open');
...@@ -34,10 +37,9 @@ ...@@ -34,10 +37,9 @@
:aria-label="title" :aria-label="title"
:href="externalUrl" :href="externalUrl"
> >
<i <icon
class="fa fa-external-link" name="external-link"
aria-hidden="true" :size="12"
> />
</i>
</a> </a>
</template> </template>
...@@ -2,20 +2,22 @@ ...@@ -2,20 +2,22 @@
/** /**
* Renders the Monitoring (Metrics) link in environments table. * Renders the Monitoring (Metrics) link in environments table.
*/ */
import Icon from '~/vue_shared/components/icon.vue';
import tooltip from '../../vue_shared/directives/tooltip'; import tooltip from '../../vue_shared/directives/tooltip';
export default { export default {
components: {
Icon,
},
directives: { directives: {
tooltip, tooltip,
}, },
props: { props: {
monitoringUrl: { monitoringUrl: {
type: String, type: String,
required: true, required: true,
}, },
}, },
computed: { computed: {
title() { title() {
return 'Monitoring'; return 'Monitoring';
...@@ -33,10 +35,9 @@ ...@@ -33,10 +35,9 @@
:title="title" :title="title"
:aria-label="title" :aria-label="title"
> >
<i <icon
class="fa fa-area-chart" name="chart"
aria-hidden="true" :size="12"
> />
</i>
</a> </a>
</template> </template>
...@@ -12,7 +12,6 @@ ...@@ -12,7 +12,6 @@
components: { components: {
loadingIcon, loadingIcon,
}, },
props: { props: {
retryUrl: { retryUrl: {
type: String, type: String,
...@@ -24,13 +23,11 @@ ...@@ -24,13 +23,11 @@
default: true, default: true,
}, },
}, },
data() { data() {
return { return {
isLoading: false, isLoading: false,
}; };
}, },
methods: { methods: {
onClick() { onClick() {
this.isLoading = true; this.isLoading = true;
......
...@@ -3,14 +3,16 @@ ...@@ -3,14 +3,16 @@
* Renders a terminal button to open a web terminal. * Renders a terminal button to open a web terminal.
* Used in environments table. * Used in environments table.
*/ */
import terminalIconSvg from 'icons/_icon_terminal.svg'; import Icon from '~/vue_shared/components/icon.vue';
import tooltip from '../../vue_shared/directives/tooltip'; import tooltip from '../../vue_shared/directives/tooltip';
export default { export default {
components: {
Icon,
},
directives: { directives: {
tooltip, tooltip,
}, },
props: { props: {
terminalPath: { terminalPath: {
type: String, type: String,
...@@ -18,13 +20,6 @@ ...@@ -18,13 +20,6 @@
default: '', default: '',
}, },
}, },
data() {
return {
terminalIconSvg,
};
},
computed: { computed: {
title() { title() {
return 'Terminal'; return 'Terminal';
...@@ -40,7 +35,10 @@ ...@@ -40,7 +35,10 @@
:title="title" :title="title"
:aria-label="title" :aria-label="title"
:href="terminalPath" :href="terminalPath"
v-html="terminalIconSvg"
> >
<icon
name="terminal"
:size="12"
/>
</a> </a>
</template> </template>
...@@ -15,17 +15,10 @@ export default { ...@@ -15,17 +15,10 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
committedStateSvgPath: {
type: String,
required: true,
},
}, },
computed: { computed: {
...mapState(['lastCommitMsg', 'rightPanelCollapsed']), ...mapState(['lastCommitMsg', 'rightPanelCollapsed', 'changedFiles', 'stagedFiles']),
...mapGetters(['collapseButtonIcon', 'collapseButtonTooltip']), ...mapGetters(['collapseButtonIcon', 'collapseButtonTooltip']),
statusSvg() {
return this.lastCommitMsg ? this.committedStateSvgPath : this.noChangesStateSvgPath;
},
}, },
methods: { methods: {
...mapActions(['toggleRightPanelCollapsed']), ...mapActions(['toggleRightPanelCollapsed']),
...@@ -35,6 +28,7 @@ export default { ...@@ -35,6 +28,7 @@ export default {
<template> <template>
<div <div
v-if="!lastCommitMsg"
class="multi-file-commit-panel-section ide-commit-empty-state js-empty-state" class="multi-file-commit-panel-section ide-commit-empty-state js-empty-state"
> >
<header <header
...@@ -64,12 +58,11 @@ export default { ...@@ -64,12 +58,11 @@ export default {
v-if="!rightPanelCollapsed" v-if="!rightPanelCollapsed"
> >
<div class="svg-content svg-80"> <div class="svg-content svg-80">
<img :src="statusSvg" /> <img :src="noChangesStateSvgPath" />
</div> </div>
<div class="append-right-default prepend-left-default"> <div class="append-right-default prepend-left-default">
<div <div
class="text-content text-center" class="text-content text-center"
v-if="!lastCommitMsg"
> >
<h4> <h4>
{{ __('No changes') }} {{ __('No changes') }}
...@@ -78,15 +71,6 @@ export default { ...@@ -78,15 +71,6 @@ export default {
{{ __('Edit files in the editor and commit changes here') }} {{ __('Edit files in the editor and commit changes here') }}
</p> </p>
</div> </div>
<div
class="text-content text-center"
v-else
>
<h4>
{{ __('All changes are committed') }}
</h4>
<p v-html="lastCommitMsg"></p>
</div>
</div> </div>
</div> </div>
</div> </div>
......
<script>
import { mapState } from 'vuex';
export default {
props: {
committedStateSvgPath: {
type: String,
required: true,
},
},
computed: {
...mapState(['lastCommitMsg']),
},
};
</script>
<template>
<div
class="multi-file-commit-panel-success-message"
aria-live="assertive"
>
<div class="svg-content svg-80">
<img
:src="committedStateSvgPath"
alt=""
/>
</div>
<div class="append-right-default prepend-left-default">
<div
class="text-content text-center"
>
<h4>
{{ __('All changes are committed') }}
</h4>
<p v-html="lastCommitMsg"></p>
</div>
</div>
</div>
</template>
...@@ -7,6 +7,7 @@ import LoadingButton from '~/vue_shared/components/loading_button.vue'; ...@@ -7,6 +7,7 @@ import LoadingButton from '~/vue_shared/components/loading_button.vue';
import CommitFilesList from './commit_sidebar/list.vue'; import CommitFilesList from './commit_sidebar/list.vue';
import EmptyState from './commit_sidebar/empty_state.vue'; import EmptyState from './commit_sidebar/empty_state.vue';
import CommitMessageField from './commit_sidebar/message_field.vue'; import CommitMessageField from './commit_sidebar/message_field.vue';
import SuccessMessage from './commit_sidebar/success_message.vue';
import * as consts from '../stores/modules/commit/constants'; import * as consts from '../stores/modules/commit/constants';
import Actions from './commit_sidebar/actions.vue'; import Actions from './commit_sidebar/actions.vue';
...@@ -16,6 +17,7 @@ export default { ...@@ -16,6 +17,7 @@ export default {
Icon, Icon,
CommitFilesList, CommitFilesList,
EmptyState, EmptyState,
SuccessMessage,
Actions, Actions,
LoadingButton, LoadingButton,
CommitMessageField, CommitMessageField,
...@@ -34,9 +36,15 @@ export default { ...@@ -34,9 +36,15 @@ export default {
}, },
}, },
computed: { computed: {
...mapState(['changedFiles', 'stagedFiles', 'rightPanelCollapsed']), showStageUnstageArea() {
return !!(this.someUncommitedChanges || this.lastCommitMsg || !this.unusedSeal);
},
someUncommitedChanges() {
return !!(this.changedFiles.length || this.stagedFiles.length);
},
...mapState(['changedFiles', 'stagedFiles', 'rightPanelCollapsed', 'lastCommitMsg', 'unusedSeal']),
...mapState('commit', ['commitMessage', 'submitCommitLoading']), ...mapState('commit', ['commitMessage', 'submitCommitLoading']),
...mapGetters('commit', ['commitButtonDisabled', 'discardDraftButtonDisabled', 'branchName']), ...mapGetters('commit', ['commitButtonDisabled', 'discardDraftButtonDisabled']),
}, },
methods: { methods: {
...mapActions('commit', [ ...mapActions('commit', [
...@@ -69,7 +77,7 @@ export default { ...@@ -69,7 +77,7 @@ export default {
</template> </template>
</deprecated-modal> </deprecated-modal>
<template <template
v-if="changedFiles.length || stagedFiles.length" v-if="showStageUnstageArea"
> >
<commit-files-list <commit-files-list
icon-name="unstaged" icon-name="unstaged"
...@@ -89,11 +97,23 @@ export default { ...@@ -89,11 +97,23 @@ export default {
:show-toggle="false" :show-toggle="false"
:staged-list="true" :staged-list="true"
/> />
</template>
<empty-state
v-if="unusedSeal"
:no-changes-state-svg-path="noChangesStateSvgPath"
/>
<div
class="multi-file-commit-panel-bottom"
>
<form <form
class="form-horizontal multi-file-commit-form" class="form-horizontal multi-file-commit-form"
@submit.prevent.stop="commitChanges" @submit.prevent.stop="commitChanges"
v-if="!rightPanelCollapsed" v-if="!rightPanelCollapsed"
> >
<success-message
v-if="lastCommitMsg && !someUncommitedChanges"
:committed-state-svg-path="committedStateSvgPath"
/>
<commit-message-field <commit-message-field
:text="commitMessage" :text="commitMessage"
@input="updateCommitMessage" @input="updateCommitMessage"
...@@ -117,11 +137,6 @@ export default { ...@@ -117,11 +137,6 @@ export default {
</button> </button>
</div> </div>
</form> </form>
</template> </div>
<empty-state
v-else
:no-changes-state-svg-path="noChangesStateSvgPath"
:committed-state-svg-path="committedStateSvgPath"
/>
</div> </div>
</template> </template>
...@@ -149,6 +149,12 @@ export const updateTempFlagForEntry = ({ commit, dispatch, state }, { file, temp ...@@ -149,6 +149,12 @@ export const updateTempFlagForEntry = ({ commit, dispatch, state }, { file, temp
export const toggleFileFinder = ({ commit }, fileFindVisible) => export const toggleFileFinder = ({ commit }, fileFindVisible) =>
commit(types.TOGGLE_FILE_FINDER, fileFindVisible); commit(types.TOGGLE_FILE_FINDER, fileFindVisible);
export const burstUnusedSeal = ({ state, commit }) => {
if (state.unusedSeal) {
commit(types.BURST_UNUSED_SEAL);
}
};
export * from './actions/tree'; export * from './actions/tree';
export * from './actions/file'; export * from './actions/file';
export * from './actions/project'; export * from './actions/project';
......
...@@ -117,7 +117,7 @@ export const getRawFileData = ({ state, commit, dispatch }, { path, baseSha }) = ...@@ -117,7 +117,7 @@ export const getRawFileData = ({ state, commit, dispatch }, { path, baseSha }) =
}); });
}; };
export const changeFileContent = ({ state, commit }, { path, content }) => { export const changeFileContent = ({ commit, dispatch, state }, { path, content }) => {
const file = state.entries[path]; const file = state.entries[path];
commit(types.UPDATE_FILE_CONTENT, { path, content }); commit(types.UPDATE_FILE_CONTENT, { path, content });
...@@ -128,6 +128,8 @@ export const changeFileContent = ({ state, commit }, { path, content }) => { ...@@ -128,6 +128,8 @@ export const changeFileContent = ({ state, commit }, { path, content }) => {
} else if (!file.changed && indexOfChangedFile !== -1) { } else if (!file.changed && indexOfChangedFile !== -1) {
commit(types.REMOVE_FILE_FROM_CHANGED, path); commit(types.REMOVE_FILE_FROM_CHANGED, path);
} }
dispatch('burstUnusedSeal', {}, { root: true });
}; };
export const setFileLanguage = ({ getters, commit }, { fileLanguage }) => { export const setFileLanguage = ({ getters, commit }, { fileLanguage }) => {
......
...@@ -182,6 +182,10 @@ export const commitChanges = ({ commit, state, getters, dispatch, rootState }) = ...@@ -182,6 +182,10 @@ export const commitChanges = ({ commit, state, getters, dispatch, rootState }) =
} }
commit(rootTypes.CLEAR_STAGED_CHANGES, null, { root: true }); commit(rootTypes.CLEAR_STAGED_CHANGES, null, { root: true });
setTimeout(() => {
commit(rootTypes.SET_LAST_COMMIT_MSG, '', { root: true });
}, 5000);
}) })
.then(() => dispatch('updateCommitAction', consts.COMMIT_TO_CURRENT_BRANCH)); .then(() => dispatch('updateCommitAction', consts.COMMIT_TO_CURRENT_BRANCH));
}) })
......
...@@ -61,3 +61,4 @@ export const REMOVE_PENDING_TAB = 'REMOVE_PENDING_TAB'; ...@@ -61,3 +61,4 @@ export const REMOVE_PENDING_TAB = 'REMOVE_PENDING_TAB';
export const UPDATE_TEMP_FLAG = 'UPDATE_TEMP_FLAG'; export const UPDATE_TEMP_FLAG = 'UPDATE_TEMP_FLAG';
export const TOGGLE_FILE_FINDER = 'TOGGLE_FILE_FINDER'; export const TOGGLE_FILE_FINDER = 'TOGGLE_FILE_FINDER';
export const BURST_UNUSED_SEAL = 'BURST_UNUSED_SEAL';
...@@ -128,6 +128,11 @@ export default { ...@@ -128,6 +128,11 @@ export default {
}), }),
}); });
}, },
[types.BURST_UNUSED_SEAL](state) {
Object.assign(state, {
unusedSeal: false,
});
},
...projectMutations, ...projectMutations,
...mergeRequestMutation, ...mergeRequestMutation,
...fileMutations, ...fileMutations,
......
...@@ -18,5 +18,6 @@ export default () => ({ ...@@ -18,5 +18,6 @@ export default () => ({
entries: {}, entries: {},
viewer: 'editor', viewer: 'editor',
delayViewerUpdated: false, delayViewerUpdated: false,
unusedSeal: true,
fileFindVisible: false, fileFindVisible: false,
}); });
...@@ -74,7 +74,11 @@ export function capitalizeFirstCharacter(text) { ...@@ -74,7 +74,11 @@ export function capitalizeFirstCharacter(text) {
* @param {*} replace * @param {*} replace
* @returns {String} * @returns {String}
*/ */
export const stripHtml = (string, replace = '') => string.replace(/<[^>]*>/g, replace); export const stripHtml = (string, replace = '') => {
if (!string) return string;
return string.replace(/<[^>]*>/g, replace);
};
/** /**
* Converts snake_case string to camelCase * Converts snake_case string to camelCase
......
import initCompareAutocomplete from '~/compare_autocomplete'; import initCompareAutocomplete from '~/compare_autocomplete';
document.addEventListener('DOMContentLoaded', initCompareAutocomplete); document.addEventListener('DOMContentLoaded', () => initCompareAutocomplete());
import $ from 'jquery';
import { localTimeAgo } from '~/lib/utils/datetime_utility';
import axios from '~/lib/utils/axios_utils';
import initCompareAutocomplete from '~/compare_autocomplete';
import initTargetProjectDropdown from './target_project_dropdown';
const updateCommitList = (url, $loadingIndicator, $commitList, params) => {
$loadingIndicator.show();
$commitList.empty();
return axios
.get(url, {
params,
})
.then(({ data }) => {
$loadingIndicator.hide();
$commitList.html(data);
localTimeAgo($('.js-timeago', $commitList));
});
};
export default mrNewCompareNode => {
const { sourceBranchUrl, targetBranchUrl } = mrNewCompareNode.dataset;
initTargetProjectDropdown();
const updateSourceBranchCommitList = () =>
updateCommitList(
sourceBranchUrl,
$(mrNewCompareNode).find('.js-source-loading'),
$(mrNewCompareNode).find('.mr_source_commit'),
{
ref: $(mrNewCompareNode)
.find("input[name='merge_request[source_branch]']")
.val(),
},
);
const updateTargetBranchCommitList = () =>
updateCommitList(
targetBranchUrl,
$(mrNewCompareNode).find('.js-target-loading'),
$(mrNewCompareNode).find('.mr_target_commit'),
{
target_project_id: $(mrNewCompareNode)
.find("input[name='merge_request[target_project_id]']")
.val(),
ref: $(mrNewCompareNode)
.find("input[name='merge_request[target_branch]']")
.val(),
},
);
initCompareAutocomplete('branches', $dropdown => {
if ($dropdown.is('.js-target-branch')) {
updateTargetBranchCommitList();
} else if ($dropdown.is('.js-source-branch')) {
updateSourceBranchCommitList();
}
});
updateSourceBranchCommitList();
updateTargetBranchCommitList();
};
import Compare from '~/compare';
import MergeRequest from '~/merge_request'; import MergeRequest from '~/merge_request';
import initPipelines from '~/commit/pipelines/pipelines_bundle'; import initPipelines from '~/commit/pipelines/pipelines_bundle';
import initCompare from './compare';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
const mrNewCompareNode = document.querySelector('.js-merge-request-new-compare'); const mrNewCompareNode = document.querySelector('.js-merge-request-new-compare');
if (mrNewCompareNode) { if (mrNewCompareNode) {
new Compare({ // eslint-disable-line no-new initCompare(mrNewCompareNode);
targetProjectUrl: mrNewCompareNode.dataset.targetProjectUrl,
sourceBranchUrl: mrNewCompareNode.dataset.sourceBranchUrl,
targetBranchUrl: mrNewCompareNode.dataset.targetBranchUrl,
});
} else { } else {
const mrNewSubmitNode = document.querySelector('.js-merge-request-new-submit'); const mrNewSubmitNode = document.querySelector('.js-merge-request-new-submit');
new MergeRequest({ // eslint-disable-line no-new // eslint-disable-next-line no-new
new MergeRequest({
action: mrNewSubmitNode.dataset.mrSubmitAction, action: mrNewSubmitNode.dataset.mrSubmitAction,
}); });
initPipelines(); initPipelines();
......
import $ from 'jquery';
export default () => {
const $targetProjectDropdown = $('.js-target-project');
$targetProjectDropdown.glDropdown({
selectable: true,
fieldName: $targetProjectDropdown.data('fieldName'),
filterable: true,
id(obj, $el) {
return $el.data('id');
},
toggleLabel(obj, $el) {
return $el.text().trim();
},
clicked({ $el }) {
$('.mr_target_commit').empty();
const $targetBranchDropdown = $('.js-target-branch');
$targetBranchDropdown.data('refsUrl', $el.data('refsUrl'));
$targetBranchDropdown.data('glDropdown').clearMenu();
},
});
};
...@@ -70,6 +70,9 @@ ...@@ -70,6 +70,9 @@
toggleMoreParticipants() { toggleMoreParticipants() {
this.isShowingMoreParticipants = !this.isShowingMoreParticipants; this.isShowingMoreParticipants = !this.isShowingMoreParticipants;
}, },
onClickCollapsedIcon() {
this.$emit('toggleSidebar');
},
}, },
}; };
</script> </script>
...@@ -82,6 +85,7 @@ ...@@ -82,6 +85,7 @@
data-container="body" data-container="body"
data-placement="left" data-placement="left"
:title="participantLabel" :title="participantLabel"
@click="onClickCollapsedIcon"
> >
<i <i
class="fa fa-users" class="fa fa-users"
......
<script> <script>
import Store from '../../stores/sidebar_store'; import Store from '../../stores/sidebar_store';
import eventHub from '../../event_hub';
import Flash from '../../../flash'; import Flash from '../../../flash';
import { __ } from '../../../locale'; import { __ } from '../../../locale';
import subscriptions from './subscriptions.vue'; import subscriptions from './subscriptions.vue';
...@@ -20,12 +19,6 @@ export default { ...@@ -20,12 +19,6 @@ export default {
store: new Store(), store: new Store(),
}; };
}, },
created() {
eventHub.$on('toggleSubscription', this.onToggleSubscription);
},
beforeDestroy() {
eventHub.$off('toggleSubscription', this.onToggleSubscription);
},
methods: { methods: {
onToggleSubscription() { onToggleSubscription() {
this.mediator.toggleSubscription() this.mediator.toggleSubscription()
...@@ -42,6 +35,7 @@ export default { ...@@ -42,6 +35,7 @@ export default {
<subscriptions <subscriptions
:loading="store.isFetching.subscriptions" :loading="store.isFetching.subscriptions"
:subscribed="store.subscribed" :subscribed="store.subscribed"
@toggleSubscription="onToggleSubscription"
/> />
</div> </div>
</template> </template>
...@@ -47,8 +47,25 @@ ...@@ -47,8 +47,25 @@
}, },
}, },
methods: { methods: {
/**
* We need to emit this event on both component & eventHub
* for 2 dependencies;
*
* 1. eventHub: This component is used in Issue Boards sidebar
* where component template is part of HAML
* and event listeners are tied to app's eventHub.
* 2. Component: This compone is also used in Epics in EE
* where listeners are tied to component event.
*/
toggleSubscription() { toggleSubscription() {
// App's eventHub event emission.
eventHub.$emit('toggleSubscription', this.id); eventHub.$emit('toggleSubscription', this.id);
// Component event emission.
this.$emit('toggleSubscription', this.id);
},
onClickCollapsedIcon() {
this.$emit('toggleSidebar');
}, },
}, },
}; };
...@@ -56,7 +73,10 @@ ...@@ -56,7 +73,10 @@
<template> <template>
<div> <div>
<div class="sidebar-collapsed-icon"> <div
class="sidebar-collapsed-icon"
@click="onClickCollapsedIcon"
>
<span <span
v-tooltip v-tooltip
:title="notificationTooltip" :title="notificationTooltip"
......
...@@ -44,6 +44,12 @@ ...@@ -44,6 +44,12 @@
} }
} }
.note-text {
table {
font-family: $font-family-sans-serif;
}
}
table { table {
width: 100%; width: 100%;
font-family: $monospace_font; font-family: $monospace_font;
......
...@@ -549,6 +549,7 @@ ...@@ -549,6 +549,7 @@
margin-bottom: 0; margin-bottom: 0;
border-bottom: 1px solid $white-dark; border-bottom: 1px solid $white-dark;
padding: $gl-btn-padding 0; padding: $gl-btn-padding 0;
min-height: 56px;
} }
.multi-file-commit-panel-header-title { .multi-file-commit-panel-header-title {
...@@ -673,6 +674,24 @@ ...@@ -673,6 +674,24 @@
} }
} }
.multi-file-commit-panel-bottom {
position: relative;
.multi-file-commit-panel-success-message {
position: absolute;
top: 1px;
left: 3px;
bottom: 0;
right: 0;
z-index: 10;
background: $gray-light;
overflow: auto;
display: flex;
flex-direction: column;
justify-content: center;
}
}
.dirty-diff { .dirty-diff {
// !important need to override monaco inline style // !important need to override monaco inline style
width: 4px !important; width: 4px !important;
......
...@@ -85,13 +85,6 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap ...@@ -85,13 +85,6 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
render layout: false render layout: false
end end
def update_branches
@target_project = selected_target_project
@target_branches = @target_project ? @target_project.repository.branch_names : []
render layout: false
end
private private
def build_merge_request def build_merge_request
......
class Identity < ActiveRecord::Base class Identity < ActiveRecord::Base
<<<<<<< HEAD
prepend EE::Identity prepend EE::Identity
=======
def self.uniqueness_scope
:provider
end
>>>>>>> upstream/master
include Sortable include Sortable
include CaseSensitivity include CaseSensitivity
...@@ -7,8 +13,8 @@ class Identity < ActiveRecord::Base ...@@ -7,8 +13,8 @@ class Identity < ActiveRecord::Base
belongs_to :user belongs_to :user
validates :provider, presence: true validates :provider, presence: true
validates :extern_uid, allow_blank: true, uniqueness: { scope: :provider, case_sensitive: false } validates :extern_uid, allow_blank: true, uniqueness: { scope: uniqueness_scope, case_sensitive: false }
validates :user_id, uniqueness: { scope: :provider } validates :user_id, uniqueness: { scope: uniqueness_scope }
before_save :ensure_normalized_extern_uid, if: :extern_uid_changed? before_save :ensure_normalized_extern_uid, if: :extern_uid_changed?
after_destroy :clear_user_synced_attributes, if: :user_synced_attributes_metadata_from_provider? after_destroy :clear_user_synced_attributes, if: :user_synced_attributes_metadata_from_provider?
......
...@@ -333,7 +333,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -333,7 +333,7 @@ class MergeRequest < ActiveRecord::Base
# updates `merge_jid` with the MergeWorker#jid. # updates `merge_jid` with the MergeWorker#jid.
# This helps tracking enqueued and ongoing merge jobs. # This helps tracking enqueued and ongoing merge jobs.
def merge_async(user_id, params) def merge_async(user_id, params)
jid = MergeWorker.perform_async(id, user_id, params) jid = MergeWorker.perform_async(id, user_id, params.to_h)
update_column(:merge_jid, jid) update_column(:merge_jid, jid)
end end
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], url: project_new_merge_request_path(@project), method: :get, html: { class: "merge-request-form form-inline js-requires-input" } do |f| = form_for [@project.namespace.becomes(Namespace), @project, @merge_request], url: project_new_merge_request_path(@project), method: :get, html: { class: "merge-request-form form-inline js-requires-input" } do |f|
.hide.alert.alert-danger.mr-compare-errors .hide.alert.alert-danger.mr-compare-errors
.js-merge-request-new-compare.row{ 'data-target-project-url': project_new_merge_request_update_branches_path(@source_project), 'data-source-branch-url': project_new_merge_request_branch_from_path(@source_project), 'data-target-branch-url': project_new_merge_request_branch_to_path(@source_project) } .js-merge-request-new-compare.row{ 'data-source-branch-url': project_new_merge_request_branch_from_path(@source_project), 'data-target-branch-url': project_new_merge_request_branch_to_path(@source_project) }
.col-md-6 .col-md-6
.panel.panel-default.panel-new-merge-request .panel.panel-default.panel-new-merge-request
.panel-heading .panel-heading
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
.panel-body.clearfix .panel-body.clearfix
.merge-request-select.dropdown .merge-request-select.dropdown
= f.hidden_field :source_project_id = f.hidden_field :source_project_id
= dropdown_toggle @merge_request.source_project_path, { toggle: "dropdown", field_name: "#{f.object_name}[source_project_id]", disabled: @merge_request.persisted? }, { toggle_class: "js-compare-dropdown js-source-project" } = dropdown_toggle @merge_request.source_project_path, { toggle: "dropdown", 'field-name': "#{f.object_name}[source_project_id]", disabled: @merge_request.persisted? }, { toggle_class: "js-compare-dropdown js-source-project" }
.dropdown-menu.dropdown-menu-selectable.dropdown-source-project .dropdown-menu.dropdown-menu-selectable.dropdown-source-project
= dropdown_title("Select source project") = dropdown_title("Select source project")
= dropdown_filter("Search projects") = dropdown_filter("Search projects")
...@@ -21,14 +21,12 @@ ...@@ -21,14 +21,12 @@
selected: f.object.source_project_id selected: f.object.source_project_id
.merge-request-select.dropdown .merge-request-select.dropdown
= f.hidden_field :source_branch = f.hidden_field :source_branch
= dropdown_toggle f.object.source_branch || "Select source branch", { toggle: "dropdown", field_name: "#{f.object_name}[source_branch]" }, { toggle_class: "js-compare-dropdown js-source-branch git-revision-dropdown-toggle" } = dropdown_toggle f.object.source_branch || _("Select source branch"), { toggle: "dropdown", 'field-name': "#{f.object_name}[source_branch]", 'refs-url': refs_project_path(@source_project), selected: f.object.source_branch }, { toggle_class: "js-compare-dropdown js-source-branch git-revision-dropdown-toggle" }
.dropdown-menu.dropdown-menu-selectable.dropdown-source-branch.git-revision-dropdown .dropdown-menu.dropdown-menu-selectable.js-source-branch-dropdown.git-revision-dropdown
= dropdown_title("Select source branch") = dropdown_title(_("Select source branch"))
= dropdown_filter("Search branches") = dropdown_filter(_("Search branches"))
= dropdown_content do = dropdown_content
= render 'projects/merge_requests/dropdowns/branch', = dropdown_loading
branches: @merge_request.source_branches,
selected: f.object.source_branch
.panel-footer .panel-footer
.text-center= icon('spinner spin', class: 'js-source-loading') .text-center= icon('spinner spin', class: 'js-source-loading')
%ul.list-unstyled.mr_source_commit %ul.list-unstyled.mr_source_commit
...@@ -41,7 +39,7 @@ ...@@ -41,7 +39,7 @@
- projects = target_projects(@project) - projects = target_projects(@project)
.merge-request-select.dropdown .merge-request-select.dropdown
= f.hidden_field :target_project_id = f.hidden_field :target_project_id
= dropdown_toggle f.object.target_project.full_path, { toggle: "dropdown", field_name: "#{f.object_name}[target_project_id]", disabled: @merge_request.persisted? }, { toggle_class: "js-compare-dropdown js-target-project" } = dropdown_toggle f.object.target_project.full_path, { toggle: "dropdown", 'field-name': "#{f.object_name}[target_project_id]", disabled: @merge_request.persisted? }, { toggle_class: "js-compare-dropdown js-target-project" }
.dropdown-menu.dropdown-menu-selectable.dropdown-target-project .dropdown-menu.dropdown-menu-selectable.dropdown-target-project
= dropdown_title("Select target project") = dropdown_title("Select target project")
= dropdown_filter("Search projects") = dropdown_filter("Search projects")
...@@ -51,14 +49,12 @@ ...@@ -51,14 +49,12 @@
selected: f.object.target_project_id selected: f.object.target_project_id
.merge-request-select.dropdown .merge-request-select.dropdown
= f.hidden_field :target_branch = f.hidden_field :target_branch
= dropdown_toggle f.object.target_branch, { toggle: "dropdown", field_name: "#{f.object_name}[target_branch]" }, { toggle_class: "js-compare-dropdown js-target-branch git-revision-dropdown-toggle" } = dropdown_toggle f.object.target_branch, { toggle: "dropdown", 'field-name': "#{f.object_name}[target_branch]", 'refs-url': refs_project_path(f.object.target_project), selected: f.object.target_branch }, { toggle_class: "js-compare-dropdown js-target-branch git-revision-dropdown-toggle" }
.dropdown-menu.dropdown-menu-selectable.dropdown-target-branch.js-target-branch-dropdown.git-revision-dropdown .dropdown-menu.dropdown-menu-selectable.js-target-branch-dropdown.git-revision-dropdown
= dropdown_title("Select target branch") = dropdown_title(_("Select target branch"))
= dropdown_filter("Search branches") = dropdown_filter(_("Search branches"))
= dropdown_content do = dropdown_content
= render 'projects/merge_requests/dropdowns/branch', = dropdown_loading
branches: @merge_request.target_branches,
selected: f.object.target_branch
.panel-footer .panel-footer
.text-center= icon('spinner spin', class: "js-target-loading") .text-center= icon('spinner spin', class: "js-target-loading")
%ul.list-unstyled.mr_target_commit %ul.list-unstyled.mr_target_commit
......
%ul %ul
- projects.each do |project| - projects.each do |project|
%li %li
%a{ href: "#", class: "#{('is-active' if selected == project.id)}", data: { id: project.id } } %a{ href: "#", class: "#{('is-active' if selected == project.id)}", data: { id: project.id, 'refs-url': refs_project_path(project) } }
= project.full_path = project.full_path
---
title: Change font for tables inside diff discussions
merge_request: 18660
author: George Tsiolis
type: changed
---
title: Add documentation about how to use variables to define deploy policies for
staging/production environments
merge_request: 18675
author:
type: other
---
title: Improve interaction on WebIDE commit panel
merge_request:
author:
type: changed
---
title: Update environment item action buttons icons
merge_request: 18632
author: George Tsiolis
type: changed
---
title: Load branches on new merge request page asynchronously
merge_request: 18315
author:
type: changed
---
title: Gitaly handles repository forks by default
merge_request:
author:
type: other
---
title: Compute Gitlab::Git::Repository#checksum on Gitaly by default
merge_request:
author:
type: performance
...@@ -22,3 +22,16 @@ end.compact ...@@ -22,3 +22,16 @@ end.compact
Rails.application.config.action_dispatch.trusted_proxies = ( Rails.application.config.action_dispatch.trusted_proxies = (
['127.0.0.1', '::1'] + gitlab_trusted_proxies) ['127.0.0.1', '::1'] + gitlab_trusted_proxies)
# A monkey patch to make trusted proxies work with Rails 5.0.
# Inspired by https://github.com/rails/rails/issues/5223#issuecomment-263778719
# Remove this monkey patch when upstream is fixed.
if Gitlab.rails5?
module TrustedProxyMonkeyPatch
def ip
@ip ||= (get_header("action_dispatch.remote_ip") || super).to_s
end
end
ActionDispatch::Request.send(:include, TrustedProxyMonkeyPatch)
end
...@@ -176,7 +176,6 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do ...@@ -176,7 +176,6 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end end
get :diff_for_path get :diff_for_path
get :update_branches
get :branch_from get :branch_from
get :branch_to get :branch_to
end end
......
...@@ -496,8 +496,11 @@ also be customized, and you can easily use a [custom buildpack](#custom-buildpac ...@@ -496,8 +496,11 @@ also be customized, and you can easily use a [custom buildpack](#custom-buildpac
| `POSTGRES_PASSWORD` | The PostgreSQL password; defaults to `testing-password`. Set it to use a custom password. | | `POSTGRES_PASSWORD` | The PostgreSQL password; defaults to `testing-password`. Set it to use a custom password. |
| `POSTGRES_DB` | The PostgreSQL database name; defaults to the value of [`$CI_ENVIRONMENT_SLUG`](../../ci/variables/README.md#predefined-variables-environment-variables). Set it to use a custom database name. | | `POSTGRES_DB` | The PostgreSQL database name; defaults to the value of [`$CI_ENVIRONMENT_SLUG`](../../ci/variables/README.md#predefined-variables-environment-variables). Set it to use a custom database name. |
| `BUILDPACK_URL` | The buildpack's full URL. It can point to either Git repositories or a tarball URL. For Git repositories, it is possible to point to a specific `ref`, for example `https://github.com/heroku/heroku-buildpack-ruby.git#v142` | | `BUILDPACK_URL` | The buildpack's full URL. It can point to either Git repositories or a tarball URL. For Git repositories, it is possible to point to a specific `ref`, for example `https://github.com/heroku/heroku-buildpack-ruby.git#v142` |
<<<<<<< HEAD
| `SAST_CONFIDENCE_LEVEL` | The minimum confidence level of security issues you want to be reported; `1` for Low, `2` for Medium, `3` for High; defaults to `3`.| | `SAST_CONFIDENCE_LEVEL` | The minimum confidence level of security issues you want to be reported; `1` for Low, `2` for Medium, `3` for High; defaults to `3`.|
| `DEP_SCAN_DISABLE_REMOTE_CHECKS` | Whether remote Dependency Scanning checks are disabled; defaults to `"false"`. Set to `"true"` to disable checks that send data to GitLab central servers. [Read more about remote checks](https://gitlab.com/gitlab-org/security-products/dependency-scanning#remote-checks).| | `DEP_SCAN_DISABLE_REMOTE_CHECKS` | Whether remote Dependency Scanning checks are disabled; defaults to `"false"`. Set to `"true"` to disable checks that send data to GitLab central servers. [Read more about remote checks](https://gitlab.com/gitlab-org/security-products/dependency-scanning#remote-checks).|
=======
>>>>>>> upstream/master
| `STAGING_ENABLED` | From GitLab 10.8, this variable can be used to define a [deploy policy for staging and production environments](#deploy-policy-for-staging-and-production-environments). | | `STAGING_ENABLED` | From GitLab 10.8, this variable can be used to define a [deploy policy for staging and production environments](#deploy-policy-for-staging-and-production-environments). |
TIP: **Tip:** TIP: **Tip:**
......
...@@ -53,7 +53,7 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps ...@@ -53,7 +53,7 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
first('.js-source-branch').click first('.js-source-branch').click
wait_for_requests wait_for_requests
first('.dropdown-source-branch .dropdown-content a', text: 'fix').click first('.js-source-branch-dropdown .dropdown-content a', text: 'fix').click
click_button "Compare branches and continue" click_button "Compare branches and continue"
......
...@@ -63,7 +63,8 @@ module Gitlab ...@@ -63,7 +63,8 @@ module Gitlab
end end
def fork_repository(new_shard_name, new_repository_relative_path) def fork_repository(new_shard_name, new_repository_relative_path)
Gitlab::GitalyClient.migrate(:fork_repository) do |is_enabled| Gitlab::GitalyClient.migrate(:fork_repository,
status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled|
if is_enabled if is_enabled
gitaly_fork_repository(new_shard_name, new_repository_relative_path) gitaly_fork_repository(new_shard_name, new_repository_relative_path)
else else
......
...@@ -1583,7 +1583,8 @@ module Gitlab ...@@ -1583,7 +1583,8 @@ module Gitlab
end end
def checksum def checksum
gitaly_migrate(:calculate_checksum) do |is_enabled| gitaly_migrate(:calculate_checksum,
status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled|
if is_enabled if is_enabled
gitaly_repository_client.calculate_checksum gitaly_repository_client.calculate_checksum
else else
......
...@@ -157,34 +157,4 @@ describe Projects::MergeRequests::CreationsController do ...@@ -157,34 +157,4 @@ describe Projects::MergeRequests::CreationsController do
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(200)
end end
end end
describe 'GET #update_branches' do
before do
allow(Ability).to receive(:allowed?).and_call_original
end
it 'lists the branches of another fork if the user has access' do
expect(Ability).to receive(:allowed?).with(user, :read_project, project) { true }
get :update_branches,
namespace_id: fork_project.namespace,
project_id: fork_project,
target_project_id: project.id
expect(assigns(:target_branches)).not_to be_empty
expect(response).to have_gitlab_http_status(200)
end
it 'does not list branches when the user cannot read the project' do
expect(Ability).to receive(:allowed?).with(user, :read_project, project) { false }
get :update_branches,
namespace_id: fork_project.namespace,
project_id: fork_project,
target_project_id: project.id
expect(response).to have_gitlab_http_status(200)
expect(assigns(:target_branches)).to eq([])
end
end
end end
...@@ -19,7 +19,7 @@ describe 'Merge request > User selects branches for new MR', :js do ...@@ -19,7 +19,7 @@ describe 'Merge request > User selects branches for new MR', :js do
expect(page).to have_content('Target branch') expect(page).to have_content('Target branch')
first('.js-source-branch').click first('.js-source-branch').click
find('.dropdown-source-branch .dropdown-content a', match: :first).click find('.js-source-branch-dropdown .dropdown-content a', match: :first).click
expect(page).to have_content "b83d6e3" expect(page).to have_content "b83d6e3"
end end
...@@ -35,22 +35,16 @@ describe 'Merge request > User selects branches for new MR', :js do ...@@ -35,22 +35,16 @@ describe 'Merge request > User selects branches for new MR', :js do
expect(page).to have_content('Target branch') expect(page).to have_content('Target branch')
first('.js-target-branch').click first('.js-target-branch').click
find('.dropdown-target-branch .dropdown-content a', text: 'v1.1.0', match: :first).click find('.js-target-branch-dropdown .dropdown-content a', text: 'v1.1.0', match: :first).click
expect(page).to have_content "b83d6e3" expect(page).to have_content "b83d6e3"
end end
it 'generates a diff for an orphaned branch' do it 'generates a diff for an orphaned branch' do
visit project_merge_requests_path(project) visit project_new_merge_request_path(project)
page.within '.content' do
click_link 'New merge request'
end
expect(page).to have_content('Source branch')
expect(page).to have_content('Target branch')
find('.js-source-branch', match: :first).click find('.js-source-branch', match: :first).click
find('.dropdown-source-branch .dropdown-content a', text: 'orphaned-branch', match: :first).click find('.js-source-branch-dropdown .dropdown-content a', text: 'orphaned-branch', match: :first).click
click_button "Compare branches" click_button "Compare branches"
click_link "Changes" click_link "Changes"
...@@ -71,19 +65,18 @@ describe 'Merge request > User selects branches for new MR', :js do ...@@ -71,19 +65,18 @@ describe 'Merge request > User selects branches for new MR', :js do
first('.js-source-branch').click first('.js-source-branch').click
input = find('.dropdown-source-branch .dropdown-input-field') page.within '.js-source-branch-dropdown' do
input.click input = find('.dropdown-input-field')
input.send_keys('orphaned-branch') input.click
input.send_keys('orphaned-branch')
find('.dropdown-source-branch .dropdown-content li', match: :first) expect(page).to have_css('.dropdown-content li', count: 1)
source_items = all('.dropdown-source-branch .dropdown-content li') end
expect(source_items.count).to eq(1)
first('.js-target-branch').click first('.js-target-branch').click
find('.dropdown-target-branch .dropdown-content li', match: :first) find('.js-target-branch-dropdown .dropdown-content li', match: :first)
target_items = all('.dropdown-target-branch .dropdown-content li') target_items = all('.js-target-branch-dropdown .dropdown-content li')
expect(target_items.count).to be > 1 expect(target_items.count).to be > 1
end end
...@@ -200,7 +193,6 @@ describe 'Merge request > User selects branches for new MR', :js do ...@@ -200,7 +193,6 @@ describe 'Merge request > User selects branches for new MR', :js do
page.within('.merge-request') do page.within('.merge-request') do
click_link 'Pipelines' click_link 'Pipelines'
wait_for_requests
expect(page).to have_content "##{pipeline.id}" expect(page).to have_content "##{pipeline.id}"
end end
......
...@@ -24,42 +24,10 @@ describe('IDE commit panel empty state', () => { ...@@ -24,42 +24,10 @@ describe('IDE commit panel empty state', () => {
resetStore(vm.$store); resetStore(vm.$store);
}); });
describe('statusSvg', () => {
it('uses noChangesStateSvgPath when commit message is empty', () => {
expect(vm.statusSvg).toBe('no-changes');
expect(vm.$el.querySelector('img').getAttribute('src')).toBe(
'no-changes',
);
});
it('uses committedStateSvgPath when commit message exists', done => {
vm.$store.state.lastCommitMsg = 'testing';
Vue.nextTick(() => {
expect(vm.statusSvg).toBe('committed-state');
expect(vm.$el.querySelector('img').getAttribute('src')).toBe(
'committed-state',
);
done();
});
});
});
it('renders no changes text when last commit message is empty', () => { it('renders no changes text when last commit message is empty', () => {
expect(vm.$el.textContent).toContain('No changes'); expect(vm.$el.textContent).toContain('No changes');
}); });
it('renders last commit message when it exists', done => {
vm.$store.state.lastCommitMsg = 'testing commit message';
Vue.nextTick(() => {
expect(vm.$el.textContent).toContain('testing commit message');
done();
});
});
describe('toggle button', () => { describe('toggle button', () => {
it('calls store action', () => { it('calls store action', () => {
spyOn(vm, 'toggleRightPanelCollapsed'); spyOn(vm, 'toggleRightPanelCollapsed');
......
import Vue from 'vue';
import store from '~/ide/stores';
import successMessage from '~/ide/components/commit_sidebar/success_message.vue';
import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper';
import { resetStore } from '../../helpers';
describe('IDE commit panel successful commit state', () => {
let vm;
beforeEach(() => {
const Component = Vue.extend(successMessage);
vm = createComponentWithStore(Component, store, {
committedStateSvgPath: 'committed-state',
});
vm.$mount();
});
afterEach(() => {
vm.$destroy();
resetStore(vm.$store);
});
it('renders last commit message when it exists', done => {
vm.$store.state.lastCommitMsg = 'testing commit message';
Vue.nextTick(() => {
expect(vm.$el.textContent).toContain('testing commit message');
done();
});
});
});
...@@ -398,6 +398,20 @@ describe('IDE store file actions', () => { ...@@ -398,6 +398,20 @@ describe('IDE store file actions', () => {
}) })
.catch(done.fail); .catch(done.fail);
}); });
it('bursts unused seal', done => {
store
.dispatch('changeFileContent', {
path: tmpFile.path,
content: 'content',
})
.then(() => {
expect(store.state.unusedSeal).toBe(false);
done();
})
.catch(done.fail);
});
}); });
describe('discardFileChanges', () => { describe('discardFileChanges', () => {
......
...@@ -116,4 +116,14 @@ describe('Multi-file store mutations', () => { ...@@ -116,4 +116,14 @@ describe('Multi-file store mutations', () => {
expect(localState.fileFindVisible).toBe(true); expect(localState.fileFindVisible).toBe(true);
}); });
}); });
describe('BURST_UNUSED_SEAL', () => {
it('updates unusedSeal', () => {
expect(localState.unusedSeal).toBe(true);
mutations.BURST_UNUSED_SEAL(localState);
expect(localState.unusedSeal).toBe(false);
});
});
}); });
...@@ -73,6 +73,14 @@ describe('text_utility', () => { ...@@ -73,6 +73,14 @@ describe('text_utility', () => {
'This is a text with html .', 'This is a text with html .',
); );
}); });
it('passes through with null string input', () => {
expect(textUtils.stripHtml(null, ' ')).toEqual(null);
});
it('passes through with undefined string input', () => {
expect(textUtils.stripHtml(undefined, ' ')).toEqual(undefined);
});
}); });
describe('convertToCamelCase', () => { describe('convertToCamelCase', () => {
......
...@@ -170,5 +170,19 @@ describe('Participants', function () { ...@@ -170,5 +170,19 @@ describe('Participants', function () {
expect(vm.isShowingMoreParticipants).toBe(true); expect(vm.isShowingMoreParticipants).toBe(true);
}); });
it('clicking on participants icon emits `toggleSidebar` event', () => {
vm = mountComponent(Participants, {
loading: false,
participants: PARTICIPANT_LIST,
numberOfLessParticipants: 2,
});
spyOn(vm, '$emit');
const participantsIconEl = vm.$el.querySelector('.sidebar-collapsed-icon');
participantsIconEl.click();
expect(vm.$emit).toHaveBeenCalledWith('toggleSidebar');
});
}); });
}); });
...@@ -3,7 +3,6 @@ import sidebarSubscriptions from '~/sidebar/components/subscriptions/sidebar_sub ...@@ -3,7 +3,6 @@ import sidebarSubscriptions from '~/sidebar/components/subscriptions/sidebar_sub
import SidebarMediator from '~/sidebar/sidebar_mediator'; import SidebarMediator from '~/sidebar/sidebar_mediator';
import SidebarService from '~/sidebar/services/sidebar_service'; import SidebarService from '~/sidebar/services/sidebar_service';
import SidebarStore from '~/sidebar/stores/sidebar_store'; import SidebarStore from '~/sidebar/stores/sidebar_store';
import eventHub from '~/sidebar/event_hub';
import mountComponent from 'spec/helpers/vue_mount_component_helper'; import mountComponent from 'spec/helpers/vue_mount_component_helper';
import Mock from './mock_data'; import Mock from './mock_data';
...@@ -32,7 +31,7 @@ describe('Sidebar Subscriptions', function () { ...@@ -32,7 +31,7 @@ describe('Sidebar Subscriptions', function () {
mediator, mediator,
}); });
eventHub.$emit('toggleSubscription'); vm.onToggleSubscription();
expect(mediator.toggleSubscription).toHaveBeenCalled(); expect(mediator.toggleSubscription).toHaveBeenCalled();
}); });
......
import Vue from 'vue'; import Vue from 'vue';
import subscriptions from '~/sidebar/components/subscriptions/subscriptions.vue'; import subscriptions from '~/sidebar/components/subscriptions/subscriptions.vue';
import eventHub from '~/sidebar/event_hub';
import mountComponent from 'spec/helpers/vue_mount_component_helper'; import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('Subscriptions', function () { describe('Subscriptions', function () {
...@@ -39,4 +40,22 @@ describe('Subscriptions', function () { ...@@ -39,4 +40,22 @@ describe('Subscriptions', function () {
expect(vm.$refs.toggleButton.$el.querySelector('.project-feature-toggle')).toHaveClass('is-checked'); expect(vm.$refs.toggleButton.$el.querySelector('.project-feature-toggle')).toHaveClass('is-checked');
}); });
it('toggleSubscription method emits `toggleSubscription` event on eventHub and Component', () => {
vm = mountComponent(Subscriptions, { subscribed: true });
spyOn(eventHub, '$emit');
spyOn(vm, '$emit');
vm.toggleSubscription();
expect(eventHub.$emit).toHaveBeenCalledWith('toggleSubscription', jasmine.any(Object));
expect(vm.$emit).toHaveBeenCalledWith('toggleSubscription', jasmine.any(Object));
});
it('onClickCollapsedIcon method emits `toggleSidebar` event on component', () => {
vm = mountComponent(Subscriptions, { subscribed: true });
spyOn(vm, '$emit');
vm.onClickCollapsedIcon();
expect(vm.$emit).toHaveBeenCalledWith('toggleSidebar');
});
}); });
...@@ -1429,7 +1429,7 @@ describe MergeRequest do ...@@ -1429,7 +1429,7 @@ describe MergeRequest do
it 'enqueues MergeWorker job and updates merge_jid' do it 'enqueues MergeWorker job and updates merge_jid' do
merge_request = create(:merge_request) merge_request = create(:merge_request)
user_id = double(:user_id) user_id = double(:user_id)
params = double(:params) params = {}
merge_jid = 'hash-123' merge_jid = 'hash-123'
expect(MergeWorker).to receive(:perform_async).with(merge_request.id, user_id, params) do expect(MergeWorker).to receive(:perform_async).with(merge_request.id, user_id, params) do
......
...@@ -79,7 +79,7 @@ RSpec.shared_examples 'a creatable merge request' do ...@@ -79,7 +79,7 @@ RSpec.shared_examples 'a creatable merge request' do
end end
end end
it 'updates the branches when selecting a new target project' do it 'updates the branches when selecting a new target project', :js do
target_project_member = target_project.owner target_project_member = target_project.owner
CreateBranchService.new(target_project, target_project_member) CreateBranchService.new(target_project, target_project_member)
.execute('a-brand-new-branch-to-test', 'master') .execute('a-brand-new-branch-to-test', 'master')
...@@ -92,7 +92,7 @@ RSpec.shared_examples 'a creatable merge request' do ...@@ -92,7 +92,7 @@ RSpec.shared_examples 'a creatable merge request' do
first('.js-target-branch').click first('.js-target-branch').click
within('.dropdown-target-branch .dropdown-content') do within('.js-target-branch-dropdown .dropdown-content') do
expect(page).to have_content('a-brand-new-branch-to-test') expect(page).to have_content('a-brand-new-branch-to-test')
end end
end end
......
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