summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_plan_component.js2
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_staging_component.js4
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_test_component.js4
-rw-r--r--app/assets/javascripts/droplab/drop_down.js128
-rw-r--r--app/assets/javascripts/droplab/drop_lab.js126
-rw-r--r--app/assets/javascripts/droplab/hook.js29
-rw-r--r--app/assets/javascripts/droplab/hook_button.js59
-rw-r--r--app/assets/javascripts/droplab/hook_input.js68
-rw-r--r--app/assets/javascripts/pipelines/components/pipeline_url.js2
-rw-r--r--app/assets/javascripts/pipelines/components/stage.js104
-rw-r--r--app/assets/javascripts/pipelines/components/status.js60
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.js63
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.js26
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.js2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_locked.js2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.js2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_sha_mismatch.js16
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/dependencies.js1
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/stores/get_state_key.js2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/stores/state_maps.js1
-rw-r--r--app/assets/javascripts/vue_shared/ci_action_icons.js31
-rw-r--r--app/assets/javascripts/vue_shared/ci_status_icons.js12
-rw-r--r--app/assets/javascripts/vue_shared/components/ci_badge_link.vue52
-rw-r--r--app/assets/javascripts/vue_shared/components/ci_icon.vue25
-rw-r--r--app/assets/javascripts/vue_shared/components/commit.js4
-rw-r--r--app/assets/javascripts/vue_shared/components/pipeline_status_icon.js23
-rw-r--r--app/assets/javascripts/vue_shared/components/pipelines_table_row.js15
-rw-r--r--app/assets/javascripts/vue_shared/pipeline_svg_icons.js43
-rw-r--r--app/assets/stylesheets/framework/gfm.scss4
-rw-r--r--app/assets/stylesheets/framework/typography.scss23
-rw-r--r--app/assets/stylesheets/framework/wells.scss6
-rw-r--r--app/assets/stylesheets/highlight/dark.scss5
-rw-r--r--app/assets/stylesheets/highlight/monokai.scss5
-rw-r--r--app/assets/stylesheets/highlight/solarized_dark.scss5
-rw-r--r--app/assets/stylesheets/highlight/solarized_light.scss5
-rw-r--r--app/assets/stylesheets/highlight/white.scss5
-rw-r--r--app/assets/stylesheets/pages/commits.scss10
-rw-r--r--app/assets/stylesheets/pages/cycle_analytics.scss4
-rw-r--r--app/assets/stylesheets/pages/diff.scss5
-rw-r--r--app/assets/stylesheets/pages/environments.scss4
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss64
-rw-r--r--app/assets/stylesheets/pages/notes.scss5
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss8
-rw-r--r--app/assets/stylesheets/pages/projects.scss14
-rw-r--r--app/controllers/projects/deployments_controller.rb6
-rw-r--r--app/helpers/button_helper.rb5
-rw-r--r--app/helpers/commits_helper.rb21
-rw-r--r--app/helpers/diff_helper.rb4
-rw-r--r--app/helpers/events_helper.rb11
-rw-r--r--app/helpers/gitlab_routing_helper.rb4
-rw-r--r--app/helpers/todos_helper.rb11
-rw-r--r--app/models/commit.rb5
-rw-r--r--app/models/concerns/mentionable.rb13
-rw-r--r--app/models/concerns/protected_branch_access.rb22
-rw-r--r--app/models/deployment.rb9
-rw-r--r--app/models/environment.rb2
-rw-r--r--app/models/issue.rb2
-rw-r--r--app/models/issue_assignee.rb7
-rw-r--r--app/models/merge_request.rb8
-rw-r--r--app/models/project_services/chat_message/pipeline_message.rb6
-rw-r--r--app/models/project_services/chat_message/push_message.rb4
-rw-r--r--app/models/project_services/monitoring_service.rb7
-rw-r--r--app/models/project_services/prometheus_service.rb36
-rw-r--r--app/models/protected_branch/merge_access_level.rb10
-rw-r--r--app/models/protected_branch/push_access_level.rb18
-rw-r--r--app/models/user.rb5
-rw-r--r--app/serializers/pipeline_entity.rb5
-rw-r--r--app/serializers/request_aware_entity.rb1
-rw-r--r--app/services/issuable_base_service.rb6
-rw-r--r--app/services/members/authorized_destroy_service.rb3
-rw-r--r--app/services/system_note_service.rb4
-rw-r--r--app/views/discussions/_discussion.html.haml2
-rw-r--r--app/views/events/_commit.html.haml2
-rw-r--r--app/views/layouts/_search.html.haml2
-rw-r--r--app/views/projects/_last_commit.html.haml2
-rw-r--r--app/views/projects/_last_push.html.haml2
-rw-r--r--app/views/projects/blame/show.html.haml2
-rw-r--r--app/views/projects/branches/_branch.html.haml3
-rw-r--r--app/views/projects/branches/_commit.html.haml2
-rw-r--r--app/views/projects/branches/new.html.haml2
-rw-r--r--app/views/projects/builds/_header.html.haml18
-rw-r--r--app/views/projects/ci/builds/_build.html.haml6
-rw-r--r--app/views/projects/commit/_commit_box.html.haml9
-rw-r--r--app/views/projects/commit/_pipeline.html.haml52
-rw-r--r--app/views/projects/commit/branches.html.haml28
-rw-r--r--app/views/projects/commits/_commit.html.haml2
-rw-r--r--app/views/projects/commits/_inline_commit.html.haml2
-rw-r--r--app/views/projects/compare/_form.html.haml4
-rw-r--r--app/views/projects/compare/index.html.haml6
-rw-r--r--app/views/projects/compare/show.html.haml4
-rw-r--r--app/views/projects/deployments/_commit.html.haml4
-rw-r--r--app/views/projects/edit.html.haml4
-rw-r--r--app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml4
-rw-r--r--app/views/projects/issues/_related_branches.html.haml3
-rw-r--r--app/views/projects/merge_requests/_merge_request.html.haml2
-rw-r--r--app/views/projects/merge_requests/_new_compare.html.haml8
-rw-r--r--app/views/projects/merge_requests/_new_submit.html.haml4
-rw-r--r--app/views/projects/merge_requests/show/_versions.html.haml58
-rw-r--r--app/views/projects/new.html.haml2
-rw-r--r--app/views/projects/pipeline_schedules/_form.html.haml2
-rw-r--r--app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml3
-rw-r--r--app/views/projects/pipelines/_info.html.haml6
-rw-r--r--app/views/projects/pipelines/new.html.haml4
-rw-r--r--app/views/projects/protected_branches/_dropdown.html.haml4
-rw-r--r--app/views/projects/protected_branches/_matching_branch.html.haml5
-rw-r--r--app/views/projects/protected_branches/_protected_branch.html.haml5
-rw-r--r--app/views/projects/protected_branches/show.html.haml2
-rw-r--r--app/views/projects/protected_tags/_dropdown.html.haml4
-rw-r--r--app/views/projects/protected_tags/_matching_tag.html.haml5
-rw-r--r--app/views/projects/protected_tags/_protected_tag.html.haml5
-rw-r--r--app/views/projects/protected_tags/show.html.haml2
-rw-r--r--app/views/projects/runners/_runner.html.haml21
-rw-r--r--app/views/projects/tags/_tag.html.haml7
-rw-r--r--app/views/projects/tags/show.html.haml4
-rw-r--r--app/views/shared/_clone_panel.html.haml2
-rw-r--r--app/views/shared/_ref_dropdown.html.haml2
-rw-r--r--app/views/shared/_ref_switcher.html.haml4
-rw-r--r--app/views/shared/empty_states/_issues.html.haml1
-rw-r--r--app/views/shared/issuable/form/_branch_chooser.html.haml4
-rw-r--r--changelogs/unreleased/30286-ci-badge-component.yml4
-rw-r--r--changelogs/unreleased/31384-new-issue-button-on-no-results-page-after-search-doesn-t-go-to-correct-form.yml4
-rw-r--r--changelogs/unreleased/31978-cross-reference-fix.yml4
-rw-r--r--changelogs/unreleased/32178-prevent-merge-on-sha-change.yml4
-rw-r--r--changelogs/unreleased/dm-consistent-commit-sha-style.yml4
-rw-r--r--changelogs/unreleased/dm-copy-mr-source-branch-as-gfm.yml4
-rw-r--r--changelogs/unreleased/dm-dependency-linker-gemfile.yml4
-rw-r--r--changelogs/unreleased/protected-branches-no-one-merge.yml4
-rw-r--r--features/steps/project/builds/artifacts.rb2
-rw-r--r--features/steps/project/forked_merge_requests.rb33
-rw-r--r--lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb2
-rw-r--r--lib/gitlab/dependency_linker.rb18
-rw-r--r--lib/gitlab/dependency_linker/base_linker.rb103
-rw-r--r--lib/gitlab/dependency_linker/gemfile_linker.rb31
-rw-r--r--lib/gitlab/diff/inline_diff_markdown_marker.rb17
-rw-r--r--lib/gitlab/diff/inline_diff_marker.rb130
-rw-r--r--lib/gitlab/file_finder.rb32
-rw-r--r--lib/gitlab/git/diff_collection.rb11
-rw-r--r--lib/gitlab/highlight.rb37
-rw-r--r--lib/gitlab/project_search_results.rb18
-rw-r--r--lib/gitlab/prometheus/queries/base_query.rb26
-rw-r--r--lib/gitlab/prometheus/queries/deployment_query.rb26
-rw-r--r--lib/gitlab/prometheus/queries/environment_query.rb20
-rw-r--r--lib/gitlab/prometheus_client.rb (renamed from lib/gitlab/prometheus.rb)4
-rw-r--r--lib/gitlab/string_range_marker.rb102
-rw-r--r--lib/gitlab/string_regex_marker.rb13
-rw-r--r--spec/controllers/projects/deployments_controller_spec.rb71
-rw-r--r--spec/features/dashboard/issuables_counter_spec.rb4
-rw-r--r--spec/features/dashboard/milestone_filter_spec.rb13
-rw-r--r--spec/features/issues/form_spec.rb18
-rw-r--r--spec/features/issues/notes_on_issues_spec.rb77
-rw-r--r--spec/features/issues_spec.rb2
-rw-r--r--spec/features/projects/pipeline_schedules_spec.rb3
-rw-r--r--spec/features/protected_branches/access_control_ce_spec.rb2
-rw-r--r--spec/helpers/diff_helper_spec.rb4
-rw-r--r--spec/javascripts/droplab/drop_down_spec.js53
-rw-r--r--spec/javascripts/droplab/hook_spec.js8
-rw-r--r--spec/javascripts/lib/utils/poll_spec.js93
-rw-r--r--spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js4
-rw-r--r--spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js13
-rw-r--r--spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js6
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_closed_spec.js4
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_sha_mismatch_spec.js16
-rw-r--r--spec/javascripts/vue_mr_widget/stores/get_state_key_spec.js3
-rw-r--r--spec/javascripts/vue_mr_widget/stores/mr_widget_store_spec.js22
-rw-r--r--spec/javascripts/vue_shared/components/ci_badge_link_spec.js89
-rw-r--r--spec/javascripts/vue_shared/components/ci_icon_spec.js9
-rw-r--r--spec/javascripts/vue_shared/components/commit_spec.js8
-rw-r--r--spec/javascripts/vue_shared/components/pipelines_table_row_spec.js2
-rw-r--r--spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb31
-rw-r--r--spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb60
-rw-r--r--spec/lib/gitlab/dependency_linker_spec.rb13
-rw-r--r--spec/lib/gitlab/diff/highlight_spec.rb4
-rw-r--r--spec/lib/gitlab/diff/inline_diff_markdown_marker_spec.rb14
-rw-r--r--spec/lib/gitlab/diff/inline_diff_marker_spec.rb18
-rw-r--r--spec/lib/gitlab/file_finder_spec.rb21
-rw-r--r--spec/lib/gitlab/highlight_spec.rb11
-rw-r--r--spec/lib/gitlab/project_search_results_spec.rb2
-rw-r--r--spec/lib/gitlab/prometheus/queries/deployment_query_spec.rb36
-rw-r--r--spec/lib/gitlab/prometheus_client_spec.rb (renamed from spec/lib/gitlab/prometheus_spec.rb)2
-rw-r--r--spec/lib/gitlab/string_range_marker_spec.rb36
-rw-r--r--spec/lib/gitlab/string_regex_marker_spec.rb18
-rw-r--r--spec/models/commit_spec.rb21
-rw-r--r--spec/models/deployment_spec.rb9
-rw-r--r--spec/models/environment_spec.rb2
-rw-r--r--spec/models/issue_spec.rb40
-rw-r--r--spec/models/merge_request_spec.rb42
-rw-r--r--spec/models/project_services/chat_message/pipeline_message_spec.rb12
-rw-r--r--spec/models/project_services/chat_message/push_message_spec.rb16
-rw-r--r--spec/models/project_services/prometheus_service_spec.rb32
-rw-r--r--spec/models/protected_branch/merge_access_level_spec.rb5
-rw-r--r--spec/models/protected_branch/push_access_level_spec.rb5
-rw-r--r--spec/services/issues/create_service_spec.rb16
-rw-r--r--spec/services/issues/update_service_spec.rb7
-rw-r--r--spec/services/merge_requests/create_service_spec.rb20
-rw-r--r--spec/services/merge_requests/update_service_spec.rb9
-rw-r--r--spec/support/prometheus_helpers.rb8
-rw-r--r--spec/tasks/gitlab/backup_rake_spec.rb2
200 files changed, 1915 insertions, 1430 deletions
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_plan_component.js b/app/assets/javascripts/cycle_analytics/components/stage_plan_component.js
index 222084deee9..dec1704395e 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_plan_component.js
+++ b/app/assets/javascripts/cycle_analytics/components/stage_plan_component.js
@@ -33,7 +33,7 @@ global.cycleAnalytics.StagePlanComponent = Vue.extend({
<span>
{{ __('FirstPushedBy|First') }}
<span class="commit-icon">${iconCommit}</span>
- <a :href="commit.commitUrl" class="commit-hash-link monospace">{{ commit.shortSha }}</a>
+ <a :href="commit.commitUrl" class="commit-hash-link commit-sha">{{ commit.shortSha }}</a>
{{ __('FirstPushedBy|pushed by') }}
<a :href="commit.author.webUrl" class="commit-author-link">
{{ commit.author.name }}
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_staging_component.js b/app/assets/javascripts/cycle_analytics/components/stage_staging_component.js
index b1e9362434f..1f7c673b1d4 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_staging_component.js
+++ b/app/assets/javascripts/cycle_analytics/components/stage_staging_component.js
@@ -26,9 +26,9 @@ global.cycleAnalytics.StageStagingComponent = Vue.extend({
<h5 class="item-title">
<a :href="build.url" class="pipeline-id">#{{ build.id }}</a>
<i class="fa fa-code-fork"></i>
- <a :href="build.branch.url" class="branch-name monospace">{{ build.branch.name }}</a>
+ <a :href="build.branch.url" class="ref-name">{{ build.branch.name }}</a>
<span class="icon-branch">${iconBranch}</span>
- <a :href="build.commitUrl" class="short-sha monospace">{{ build.shortSha }}</a>
+ <a :href="build.commitUrl" class="commit-sha">{{ build.shortSha }}</a>
</h5>
<span>
<a :href="build.url" class="build-date">{{ build.date }}</a>
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_test_component.js b/app/assets/javascripts/cycle_analytics/components/stage_test_component.js
index e306026429e..78cc97eea0b 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_test_component.js
+++ b/app/assets/javascripts/cycle_analytics/components/stage_test_component.js
@@ -29,9 +29,9 @@ global.cycleAnalytics.StageTestComponent = Vue.extend({
&middot;
<a :href="build.url" class="pipeline-id">#{{ build.id }}</a>
<i class="fa fa-code-fork"></i>
- <a :href="build.branch.url" class="branch-name monospace">{{ build.branch.name }}</a>
+ <a :href="build.branch.url" class="ref-name">{{ build.branch.name }}</a>
<span class="icon-branch">${iconBranch}</span>
- <a :href="build.commitUrl" class="short-sha monospace">{{ build.shortSha }}</a>
+ <a :href="build.commitUrl" class="commit-sha">{{ build.shortSha }}</a>
</h5>
<span>
<a :href="build.url" class="issue-date">
diff --git a/app/assets/javascripts/droplab/drop_down.js b/app/assets/javascripts/droplab/drop_down.js
index de3927d683c..70cd337fb8a 100644
--- a/app/assets/javascripts/droplab/drop_down.js
+++ b/app/assets/javascripts/droplab/drop_down.js
@@ -1,44 +1,42 @@
-/* eslint-disable */
-
import utils from './utils';
import { SELECTED_CLASS, IGNORE_CLASS } from './constants';
-var DropDown = function(list) {
- this.currentIndex = 0;
- this.hidden = true;
- this.list = typeof list === 'string' ? document.querySelector(list) : list;
- this.items = [];
+class DropDown {
+ constructor(list) {
+ this.currentIndex = 0;
+ this.hidden = true;
+ this.list = typeof list === 'string' ? document.querySelector(list) : list;
+ this.items = [];
- this.eventWrapper = {};
+ this.eventWrapper = {};
- this.getItems();
- this.initTemplateString();
- this.addEvents();
+ this.getItems();
+ this.initTemplateString();
+ this.addEvents();
- this.initialState = list.innerHTML;
-};
+ this.initialState = list.innerHTML;
+ }
-Object.assign(DropDown.prototype, {
- getItems: function() {
+ getItems() {
this.items = [].slice.call(this.list.querySelectorAll('li'));
return this.items;
- },
+ }
- initTemplateString: function() {
- var items = this.items || this.getItems();
+ initTemplateString() {
+ const items = this.items || this.getItems();
- var templateString = '';
+ let templateString = '';
if (items.length > 0) templateString = items[items.length - 1].outerHTML;
this.templateString = templateString;
return this.templateString;
- },
+ }
- clickEvent: function(e) {
+ clickEvent(e) {
if (e.target.tagName === 'UL') return;
if (e.target.classList.contains(IGNORE_CLASS)) return;
- var selected = utils.closest(e.target, 'LI');
+ const selected = utils.closest(e.target, 'LI');
if (!selected) return;
this.addSelectedClass(selected);
@@ -46,95 +44,95 @@ Object.assign(DropDown.prototype, {
e.preventDefault();
this.hide();
- var listEvent = new CustomEvent('click.dl', {
+ const listEvent = new CustomEvent('click.dl', {
detail: {
list: this,
- selected: selected,
+ selected,
data: e.target.dataset,
},
});
this.list.dispatchEvent(listEvent);
- },
+ }
- addSelectedClass: function (selected) {
+ addSelectedClass(selected) {
this.removeSelectedClasses();
selected.classList.add(SELECTED_CLASS);
- },
+ }
- removeSelectedClasses: function () {
+ removeSelectedClasses() {
const items = this.items || this.getItems();
items.forEach(item => item.classList.remove(SELECTED_CLASS));
- },
+ }
- addEvents: function() {
- this.eventWrapper.clickEvent = this.clickEvent.bind(this)
+ addEvents() {
+ this.eventWrapper.clickEvent = this.clickEvent.bind(this);
this.list.addEventListener('click', this.eventWrapper.clickEvent);
- },
-
- toggle: function() {
- this.hidden ? this.show() : this.hide();
- },
+ }
- setData: function(data) {
+ setData(data) {
this.data = data;
this.render(data);
- },
+ }
- addData: function(data) {
+ addData(data) {
this.data = (this.data || []).concat(data);
this.render(this.data);
- },
+ }
- render: function(data) {
+ render(data) {
const children = data ? data.map(this.renderChildren.bind(this)) : [];
const renderableList = this.list.querySelector('ul[data-dynamic]') || this.list;
renderableList.innerHTML = children.join('');
- },
+ }
- renderChildren: function(data) {
- var html = utils.template(this.templateString, data);
- var template = document.createElement('div');
+ renderChildren(data) {
+ const html = utils.template(this.templateString, data);
+ const template = document.createElement('div');
template.innerHTML = html;
- this.setImagesSrc(template);
+ DropDown.setImagesSrc(template);
template.firstChild.style.display = data.droplab_hidden ? 'none' : 'block';
return template.firstChild.outerHTML;
- },
-
- setImagesSrc: function(template) {
- const images = [].slice.call(template.querySelectorAll('img[data-src]'));
-
- images.forEach((image) => {
- image.src = image.getAttribute('data-src');
- image.removeAttribute('data-src');
- });
- },
+ }
- show: function() {
+ show() {
if (!this.hidden) return;
this.list.style.display = 'block';
this.currentIndex = 0;
this.hidden = false;
- },
+ }
- hide: function() {
+ hide() {
if (this.hidden) return;
this.list.style.display = 'none';
this.currentIndex = 0;
this.hidden = true;
- },
+ }
- toggle: function () {
- this.hidden ? this.show() : this.hide();
- },
+ toggle() {
+ if (this.hidden) return this.show();
- destroy: function() {
+ return this.hide();
+ }
+
+ destroy() {
this.hide();
this.list.removeEventListener('click', this.eventWrapper.clickEvent);
}
-});
+
+ static setImagesSrc(template) {
+ const images = [...template.querySelectorAll('img[data-src]')];
+
+ images.forEach((image) => {
+ const img = image;
+
+ img.src = img.getAttribute('data-src');
+ img.removeAttribute('data-src');
+ });
+ }
+}
export default DropDown;
diff --git a/app/assets/javascripts/droplab/drop_lab.js b/app/assets/javascripts/droplab/drop_lab.js
index 6eb9f314af7..2a02ede72bf 100644
--- a/app/assets/javascripts/droplab/drop_lab.js
+++ b/app/assets/javascripts/droplab/drop_lab.js
@@ -1,99 +1,99 @@
-/* eslint-disable */
-
import HookButton from './hook_button';
import HookInput from './hook_input';
import utils from './utils';
import Keyboard from './keyboard';
import { DATA_TRIGGER } from './constants';
-var DropLab = function() {
- this.ready = false;
- this.hooks = [];
- this.queuedData = [];
- this.config = {};
+class DropLab {
+ constructor() {
+ this.ready = false;
+ this.hooks = [];
+ this.queuedData = [];
+ this.config = {};
- this.eventWrapper = {};
-};
+ this.eventWrapper = {};
+ }
-Object.assign(DropLab.prototype, {
- loadStatic: function(){
- var dropdownTriggers = [].slice.apply(document.querySelectorAll(`[${DATA_TRIGGER}]`));
+ loadStatic() {
+ const dropdownTriggers = [].slice.apply(document.querySelectorAll(`[${DATA_TRIGGER}]`));
this.addHooks(dropdownTriggers);
- },
+ }
- addData: function () {
- var args = [].slice.apply(arguments);
- this.applyArgs(args, '_addData');
- },
+ addData(...args) {
+ this.applyArgs(args, 'processAddData');
+ }
- setData: function() {
- var args = [].slice.apply(arguments);
- this.applyArgs(args, '_setData');
- },
+ setData(...args) {
+ this.applyArgs(args, 'processSetData');
+ }
- destroy: function() {
+ destroy() {
this.hooks.forEach(hook => hook.destroy());
this.hooks = [];
this.removeEvents();
- },
+ }
- applyArgs: function(args, methodName) {
- if (this.ready) return this[methodName].apply(this, args);
+ applyArgs(args, methodName) {
+ if (this.ready) return this[methodName](...args);
this.queuedData = this.queuedData || [];
this.queuedData.push(args);
- },
- _addData: function(trigger, data) {
- this._processData(trigger, data, 'addData');
- },
+ return this.ready;
+ }
+
+ processAddData(trigger, data) {
+ this.processData(trigger, data, 'addData');
+ }
- _setData: function(trigger, data) {
- this._processData(trigger, data, 'setData');
- },
+ processSetData(trigger, data) {
+ this.processData(trigger, data, 'setData');
+ }
- _processData: function(trigger, data, methodName) {
+ processData(trigger, data, methodName) {
this.hooks.forEach((hook) => {
if (Array.isArray(trigger)) hook.list[methodName](trigger);
if (hook.trigger.id === trigger) hook.list[methodName](data);
});
- },
+ }
- addEvents: function() {
- this.eventWrapper.documentClicked = this.documentClicked.bind(this)
+ addEvents() {
+ this.eventWrapper.documentClicked = this.documentClicked.bind(this);
document.addEventListener('click', this.eventWrapper.documentClicked);
- },
+ }
- documentClicked: function(e) {
+ documentClicked(e) {
let thisTag = e.target;
if (thisTag.tagName !== 'UL') thisTag = utils.closest(thisTag, 'UL');
- if (utils.isDropDownParts(thisTag, this.hooks) || utils.isDropDownParts(e.target, this.hooks)) return;
+ if (utils.isDropDownParts(thisTag, this.hooks)) return;
+ if (utils.isDropDownParts(e.target, this.hooks)) return;
this.hooks.forEach(hook => hook.list.hide());
- },
+ }
- removeEvents: function(){
+ removeEvents() {
document.removeEventListener('click', this.eventWrapper.documentClicked);
- },
-
- changeHookList: function(trigger, list, plugins, config) {
- const availableTrigger = typeof trigger === 'string' ? document.getElementById(trigger) : trigger;
+ }
+ changeHookList(trigger, list, plugins, config) {
+ const availableTrigger = typeof trigger === 'string' ? document.getElementById(trigger) : trigger;
this.hooks.forEach((hook, i) => {
- hook.list.list.dataset.dropdownActive = false;
+ const aHook = hook;
+
+ aHook.list.list.dataset.dropdownActive = false;
- if (hook.trigger !== availableTrigger) return;
+ if (aHook.trigger !== availableTrigger) return;
- hook.destroy();
+ aHook.destroy();
this.hooks.splice(i, 1);
this.addHook(availableTrigger, list, plugins, config);
});
- },
+ }
- addHook: function(hook, list, plugins, config) {
+ addHook(hook, list, plugins, config) {
const availableHook = typeof hook === 'string' ? document.querySelector(hook) : hook;
let availableList;
@@ -111,18 +111,18 @@ Object.assign(DropLab.prototype, {
this.hooks.push(new HookObject(availableHook, availableList, plugins, config));
return this;
- },
+ }
- addHooks: function(hooks, plugins, config) {
+ addHooks(hooks, plugins, config) {
hooks.forEach(hook => this.addHook(hook, null, plugins, config));
return this;
- },
+ }
- setConfig: function(obj){
+ setConfig(obj) {
this.config = obj;
- },
+ }
- fireReady: function() {
+ fireReady() {
const readyEvent = new CustomEvent('ready.dl', {
detail: {
dropdown: this,
@@ -131,10 +131,14 @@ Object.assign(DropLab.prototype, {
document.dispatchEvent(readyEvent);
this.ready = true;
- },
+ }
- init: function (hook, list, plugins, config) {
- hook ? this.addHook(hook, list, plugins, config) : this.loadStatic();
+ init(hook, list, plugins, config) {
+ if (hook) {
+ this.addHook(hook, list, plugins, config);
+ } else {
+ this.loadStatic();
+ }
this.addEvents();
@@ -146,7 +150,7 @@ Object.assign(DropLab.prototype, {
this.queuedData = [];
return this;
- },
-});
+ }
+}
export default DropLab;
diff --git a/app/assets/javascripts/droplab/hook.js b/app/assets/javascripts/droplab/hook.js
index 2f840083571..cf78165b0d8 100644
--- a/app/assets/javascripts/droplab/hook.js
+++ b/app/assets/javascripts/droplab/hook.js
@@ -1,22 +1,15 @@
-/* eslint-disable */
-
import DropDown from './drop_down';
-var Hook = function(trigger, list, plugins, config){
- this.trigger = trigger;
- this.list = new DropDown(list);
- this.type = 'Hook';
- this.event = 'click';
- this.plugins = plugins || [];
- this.config = config || {};
- this.id = trigger.id;
-};
-
-Object.assign(Hook.prototype, {
-
- addEvents: function(){},
-
- constructor: Hook,
-});
+class Hook {
+ constructor(trigger, list, plugins, config) {
+ this.trigger = trigger;
+ this.list = new DropDown(list);
+ this.type = 'Hook';
+ this.event = 'click';
+ this.plugins = plugins || [];
+ this.config = config || {};
+ this.id = trigger.id;
+ }
+}
export default Hook;
diff --git a/app/assets/javascripts/droplab/hook_button.js b/app/assets/javascripts/droplab/hook_button.js
index be8aead1303..af45eba74e7 100644
--- a/app/assets/javascripts/droplab/hook_button.js
+++ b/app/assets/javascripts/droplab/hook_button.js
@@ -1,65 +1,58 @@
-/* eslint-disable */
-
import Hook from './hook';
-var HookButton = function(trigger, list, plugins, config) {
- Hook.call(this, trigger, list, plugins, config);
-
- this.type = 'button';
- this.event = 'click';
+class HookButton extends Hook {
+ constructor(trigger, list, plugins, config) {
+ super(trigger, list, plugins, config);
- this.eventWrapper = {};
+ this.type = 'button';
+ this.event = 'click';
- this.addEvents();
- this.addPlugins();
-};
+ this.eventWrapper = {};
-HookButton.prototype = Object.create(Hook.prototype);
+ this.addEvents();
+ this.addPlugins();
+ }
-Object.assign(HookButton.prototype, {
- addPlugins: function() {
+ addPlugins() {
this.plugins.forEach(plugin => plugin.init(this));
- },
+ }
- clicked: function(e){
- var buttonEvent = new CustomEvent('click.dl', {
+ clicked(e) {
+ const buttonEvent = new CustomEvent('click.dl', {
detail: {
hook: this,
},
bubbles: true,
- cancelable: true
+ cancelable: true,
});
e.target.dispatchEvent(buttonEvent);
this.list.toggle();
- },
+ }
- addEvents: function(){
+ addEvents() {
this.eventWrapper.clicked = this.clicked.bind(this);
this.trigger.addEventListener('click', this.eventWrapper.clicked);
- },
+ }
- removeEvents: function(){
+ removeEvents() {
this.trigger.removeEventListener('click', this.eventWrapper.clicked);
- },
+ }
- restoreInitialState: function() {
+ restoreInitialState() {
this.list.list.innerHTML = this.list.initialState;
- },
+ }
- removePlugins: function() {
+ removePlugins() {
this.plugins.forEach(plugin => plugin.destroy());
- },
+ }
- destroy: function() {
+ destroy() {
this.restoreInitialState();
this.removeEvents();
this.removePlugins();
- },
-
- constructor: HookButton,
-});
-
+ }
+}
export default HookButton;
diff --git a/app/assets/javascripts/droplab/hook_input.js b/app/assets/javascripts/droplab/hook_input.js
index 05082334045..19131a64f2c 100644
--- a/app/assets/javascripts/droplab/hook_input.js
+++ b/app/assets/javascripts/droplab/hook_input.js
@@ -1,25 +1,23 @@
-/* eslint-disable */
-
import Hook from './hook';
-var HookInput = function(trigger, list, plugins, config) {
- Hook.call(this, trigger, list, plugins, config);
+class HookInput extends Hook {
+ constructor(trigger, list, plugins, config) {
+ super(trigger, list, plugins, config);
- this.type = 'input';
- this.event = 'input';
+ this.type = 'input';
+ this.event = 'input';
- this.eventWrapper = {};
+ this.eventWrapper = {};
- this.addEvents();
- this.addPlugins();
-};
+ this.addEvents();
+ this.addPlugins();
+ }
-Object.assign(HookInput.prototype, {
- addPlugins: function() {
+ addPlugins() {
this.plugins.forEach(plugin => plugin.init(this));
- },
+ }
- addEvents: function(){
+ addEvents() {
this.eventWrapper.mousedown = this.mousedown.bind(this);
this.eventWrapper.input = this.input.bind(this);
this.eventWrapper.keyup = this.keyup.bind(this);
@@ -29,19 +27,19 @@ Object.assign(HookInput.prototype, {
this.trigger.addEventListener('input', this.eventWrapper.input);
this.trigger.addEventListener('keyup', this.eventWrapper.keyup);
this.trigger.addEventListener('keydown', this.eventWrapper.keydown);
- },
+ }
- removeEvents: function() {
+ removeEvents() {
this.hasRemovedEvents = true;
this.trigger.removeEventListener('mousedown', this.eventWrapper.mousedown);
this.trigger.removeEventListener('input', this.eventWrapper.input);
this.trigger.removeEventListener('keyup', this.eventWrapper.keyup);
this.trigger.removeEventListener('keydown', this.eventWrapper.keydown);
- },
+ }
- input: function(e) {
- if(this.hasRemovedEvents) return;
+ input(e) {
+ if (this.hasRemovedEvents) return;
this.list.show();
@@ -51,12 +49,12 @@ Object.assign(HookInput.prototype, {
text: e.target.value,
},
bubbles: true,
- cancelable: true
+ cancelable: true,
});
e.target.dispatchEvent(inputEvent);
- },
+ }
- mousedown: function(e) {
+ mousedown(e) {
if (this.hasRemovedEvents) return;
const mouseEvent = new CustomEvent('mousedown.dl', {
@@ -68,21 +66,21 @@ Object.assign(HookInput.prototype, {
cancelable: true,
});
e.target.dispatchEvent(mouseEvent);
- },
+ }
- keyup: function(e) {
+ keyup(e) {
if (this.hasRemovedEvents) return;
this.keyEvent(e, 'keyup.dl');
- },
+ }
- keydown: function(e) {
+ keydown(e) {
if (this.hasRemovedEvents) return;
this.keyEvent(e, 'keydown.dl');
- },
+ }
- keyEvent: function(e, eventName) {
+ keyEvent(e, eventName) {
this.list.show();
const keyEvent = new CustomEvent(eventName, {
@@ -96,17 +94,17 @@ Object.assign(HookInput.prototype, {
cancelable: true,
});
e.target.dispatchEvent(keyEvent);
- },
+ }
- restoreInitialState: function() {
+ restoreInitialState() {
this.list.list.innerHTML = this.list.initialState;
- },
+ }
- removePlugins: function() {
+ removePlugins() {
this.plugins.forEach(plugin => plugin.destroy());
- },
+ }
- destroy: function() {
+ destroy() {
this.restoreInitialState();
this.removeEvents();
@@ -114,6 +112,6 @@ Object.assign(HookInput.prototype, {
this.list.destroy();
}
-});
+}
export default HookInput;
diff --git a/app/assets/javascripts/pipelines/components/pipeline_url.js b/app/assets/javascripts/pipelines/components/pipeline_url.js
index 4e183d5c8ec..ea8aaca6c9c 100644
--- a/app/assets/javascripts/pipelines/components/pipeline_url.js
+++ b/app/assets/javascripts/pipelines/components/pipeline_url.js
@@ -29,7 +29,7 @@ export default {
</a>
<span
v-if="!user"
- class="js-pipeline-url-api api monospace">
+ class="js-pipeline-url-api api">
API
</span>
<span
diff --git a/app/assets/javascripts/pipelines/components/stage.js b/app/assets/javascripts/pipelines/components/stage.js
deleted file mode 100644
index 034e8d3280e..00000000000
--- a/app/assets/javascripts/pipelines/components/stage.js
+++ /dev/null
@@ -1,104 +0,0 @@
-/* global Flash */
-import { borderlessStatusIconEntityMap } from '../../vue_shared/ci_status_icons';
-
-export default {
- data() {
- return {
- builds: '',
- spinner: '<span class="fa fa-spinner fa-spin"></span>',
- };
- },
-
- props: {
- stage: {
- type: Object,
- required: true,
- },
- },
-
- updated() {
- if (this.builds) {
- this.stopDropdownClickPropagation();
- }
- },
-
- methods: {
- fetchBuilds(e) {
- const ariaExpanded = e.currentTarget.attributes['aria-expanded'];
-
- if (ariaExpanded && (ariaExpanded.textContent === 'true')) return null;
-
- return this.$http.get(this.stage.dropdown_path)
- .then((response) => {
- this.builds = JSON.parse(response.body).html;
- }, () => {
- const flash = new Flash('Something went wrong on our end.');
- return flash;
- });
- },
-
- /**
- * When the user right clicks or cmd/ctrl + click in the job name
- * the dropdown should not be closed and the link should open in another tab,
- * so we stop propagation of the click event inside the dropdown.
- *
- * Since this component is rendered multiple times per page we need to guarantee we only
- * target the click event of this component.
- */
- stopDropdownClickPropagation() {
- $(this.$el.querySelectorAll('.js-builds-dropdown-list a.mini-pipeline-graph-dropdown-item')).on('click', (e) => {
- e.stopPropagation();
- });
- },
- },
- computed: {
- buildsOrSpinner() {
- return this.builds ? this.builds : this.spinner;
- },
- dropdownClass() {
- if (this.builds) return 'js-builds-dropdown-container';
- return 'js-builds-dropdown-loading builds-dropdown-loading';
- },
- buildStatus() {
- return `Build: ${this.stage.status.label}`;
- },
- tooltip() {
- return `has-tooltip ci-status-icon ci-status-icon-${this.stage.status.group}`;
- },
- triggerButtonClass() {
- return `mini-pipeline-graph-dropdown-toggle has-tooltip js-builds-dropdown-button ci-status-icon-${this.stage.status.group}`;
- },
- svgHTML() {
- return borderlessStatusIconEntityMap[this.stage.status.icon];
- },
- },
- watch: {
- 'stage.title': function stageTitle() {
- $(this.$refs.button).tooltip('destroy').tooltip();
- },
- },
- template: `
- <div>
- <button
- @click="fetchBuilds($event)"
- :class="triggerButtonClass"
- :title="stage.title"
- data-placement="top"
- data-toggle="dropdown"
- type="button"
- ref="button"
- :aria-label="stage.title">
- <span v-html="svgHTML" aria-hidden="true"></span>
- <i class="fa fa-caret-down" aria-hidden="true"></i>
- </button>
- <ul class="dropdown-menu mini-pipeline-graph-dropdown-menu js-builds-dropdown-container">
- <div class="arrow-up" aria-hidden="true"></div>
- <div
- :class="dropdownClass"
- class="js-builds-dropdown-list scrollable-menu"
- v-html="buildsOrSpinner">
- </div>
- </ul>
- </div>
- `,
-};
diff --git a/app/assets/javascripts/pipelines/components/status.js b/app/assets/javascripts/pipelines/components/status.js
deleted file mode 100644
index 21a281af438..00000000000
--- a/app/assets/javascripts/pipelines/components/status.js
+++ /dev/null
@@ -1,60 +0,0 @@
-import canceledSvg from 'icons/_icon_status_canceled.svg';
-import createdSvg from 'icons/_icon_status_created.svg';
-import failedSvg from 'icons/_icon_status_failed.svg';
-import manualSvg from 'icons/_icon_status_manual.svg';
-import pendingSvg from 'icons/_icon_status_pending.svg';
-import runningSvg from 'icons/_icon_status_running.svg';
-import skippedSvg from 'icons/_icon_status_skipped.svg';
-import successSvg from 'icons/_icon_status_success.svg';
-import warningSvg from 'icons/_icon_status_warning.svg';
-
-export default {
- props: {
- pipeline: {
- type: Object,
- required: true,
- },
- },
-
- data() {
- const svgsDictionary = {
- icon_status_canceled: canceledSvg,
- icon_status_created: createdSvg,
- icon_status_failed: failedSvg,
- icon_status_manual: manualSvg,
- icon_status_pending: pendingSvg,
- icon_status_running: runningSvg,
- icon_status_skipped: skippedSvg,
- icon_status_success: successSvg,
- icon_status_warning: warningSvg,
- };
-
- return {
- svg: svgsDictionary[this.pipeline.details.status.icon],
- };
- },
-
- computed: {
- cssClasses() {
- return `ci-status ci-${this.pipeline.details.status.group}`;
- },
-
- detailsPath() {
- const { status } = this.pipeline.details;
- return status.has_details ? status.details_path : false;
- },
-
- content() {
- return `${this.svg} ${this.pipeline.details.status.text}`;
- },
- },
- template: `
- <td class="commit-link">
- <a
- :class="cssClasses"
- :href="detailsPath"
- v-html="content">
- </a>
- </td>
- `,
-};
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js
index 3c23b8e472b..8b59e018836 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js
@@ -1,7 +1,7 @@
/* global Flash */
import '~/lib/utils/datetime_utility';
-import { statusClassToSvgMap } from '../../vue_shared/pipeline_svg_icons';
+import { statusIconEntityMap } from '../../vue_shared/ci_status_icons';
import MemoryUsage from './mr_widget_memory_usage';
import MRWidgetService from '../services/mr_widget_service';
@@ -16,7 +16,7 @@ export default {
},
computed: {
svg() {
- return statusClassToSvgMap.icon_status_success;
+ return statusIconEntityMap.icon_status_success;
},
},
methods: {
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.js
index 4a1fd881169..9e7299fcdeb 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.js
@@ -12,6 +12,15 @@ export default {
commitsText() {
return gl.text.pluralize('commit', this.mr.divergedCommitsCount);
},
+ branchNameClipboardData() {
+ // This supports code in app/assets/javascripts/copy_to_clipboard.js that
+ // works around ClipboardJS limitations to allow the context-specific
+ // copy/pasting of plain text or GFM.
+ return JSON.stringify({
+ text: this.mr.sourceBranch,
+ gfm: `\`${this.mr.sourceBranch}\``,
+ });
+ },
},
methods: {
isBranchTitleLong(branchTitle) {
@@ -61,32 +70,34 @@ export default {
</span>
</div>
<div class="normal">
- <b>Request to merge</b>
- <span
- class="label-branch"
- :class="{'label-truncated has-tooltip': isBranchTitleLong(mr.sourceBranch)}"
- :title="isBranchTitleLong(mr.sourceBranch) ? mr.sourceBranch : ''"
- data-placement="bottom"
- v-html="mr.sourceBranchLink"></span>
- <button
- class="btn btn-transparent btn-clipboard has-tooltip"
- data-title="Copy branch name to clipboard"
- :data-clipboard-text="mr.sourceBranch">
- <i
- aria-hidden="true"
- class="fa fa-clipboard"></i>
- </button>
- <b>into</b>
- <span
- class="label-branch"
- :class="{'label-truncated has-tooltip': isBranchTitleLong(mr.targetBranch)}"
- :title="isBranchTitleLong(mr.targetBranch) ? mr.targetBranch : ''"
- data-placement="bottom">
- <a
- :href="mr.targetBranchCommitsPath">
- {{mr.targetBranch}}
- </a>
- </span>
+ <strong>
+ Request to merge
+ <span
+ class="label-branch"
+ :class="{'label-truncated has-tooltip': isBranchTitleLong(mr.sourceBranch)}"
+ :title="isBranchTitleLong(mr.sourceBranch) ? mr.sourceBranch : ''"
+ data-placement="bottom"
+ v-html="mr.sourceBranchLink"></span>
+ <button
+ class="btn btn-transparent btn-clipboard has-tooltip"
+ data-title="Copy branch name to clipboard"
+ :data-clipboard-text="branchNameClipboardData">
+ <i
+ aria-hidden="true"
+ class="fa fa-clipboard"></i>
+ </button>
+ into
+ <span
+ class="label-branch"
+ :class="{'label-truncated has-tooltip': isBranchTitleLong(mr.targetBranch)}"
+ :title="isBranchTitleLong(mr.targetBranch) ? mr.targetBranch : ''"
+ data-placement="bottom">
+ <a
+ :href="mr.targetBranchPath">
+ {{mr.targetBranch}}
+ </a>
+ </span>
+ </strong>
<span
v-if="shouldShowCommitsBehindText"
class="diverged-commits-count">
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.js
index 801b9fb1ba1..517838f92ac 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.js
@@ -1,6 +1,6 @@
-import PipelineStage from '../../pipelines/components/stage';
-import pipelineStatusIcon from '../../vue_shared/components/pipeline_status_icon';
-import { statusClassToSvgMap } from '../../vue_shared/pipeline_svg_icons';
+import PipelineStage from '../../pipelines/components/stage.vue';
+import ciIcon from '../../vue_shared/components/ci_icon.vue';
+import { statusIconEntityMap } from '../../vue_shared/ci_status_icons';
export default {
name: 'MRWidgetPipeline',
@@ -9,7 +9,7 @@ export default {
},
components: {
'pipeline-stage': PipelineStage,
- 'pipeline-status-icon': pipelineStatusIcon,
+ ciIcon,
},
computed: {
hasCIError() {
@@ -18,11 +18,14 @@ export default {
return hasCI && !ciStatus;
},
svg() {
- return statusClassToSvgMap.icon_status_failed;
+ return statusIconEntityMap.icon_status_failed;
},
stageText() {
return this.mr.pipeline.details.stages.length > 1 ? 'stages' : 'stage';
},
+ status() {
+ return this.mr.pipeline.details.status || {};
+ },
},
template: `
<div class="mr-widget-heading">
@@ -38,13 +41,22 @@ export default {
<span>Could not connect to the CI server. Please check your settings and try again.</span>
</template>
<template v-else>
- <pipeline-status-icon :pipelineStatus="mr.pipelineDetailedStatus" />
+ <div>
+ <a
+ class="icon-link"
+ :href="this.status.details_path">
+ <ci-icon :status="status" />
+ </a>
+ </div>
<span>
Pipeline
<a
:href="mr.pipeline.path"
class="pipeline-id">#{{mr.pipeline.id}}</a>
{{mr.pipeline.details.status.label}}
+ </span>
+ <span
+ v-if="mr.pipeline.details.stages.length > 0">
with {{stageText}}
</span>
<div class="mr-widget-pipeline-graph">
@@ -61,7 +73,7 @@ export default {
for
<a
:href="mr.pipeline.commit.commit_path"
- class="monospace js-commit-link">
+ class="commit-sha js-commit-link">
{{mr.pipeline.commit.short_id}}</a>.
</span>
<span
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.js
index 7e66441e5ff..fc2e42c6821 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.js
@@ -20,7 +20,7 @@ export default {
<p>
The changes were not merged into
<a
- :href="mr.targetBranchCommitsPath"
+ :href="mr.targetBranchPath"
class="label-branch">
{{mr.targetBranch}}</a>.
</p>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_locked.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_locked.js
index e3c27dfb76d..0bd31731a0b 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_locked.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_locked.js
@@ -16,7 +16,7 @@ export default {
The changes will be merged into
<span class="label-branch">
<a :href="mr.targetBranchPath">{{mr.targetBranch}}</a>
- </span>
+ </span>.
</p>
</section>
</div>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.js
index bcdbedcd46b..419d174f3ff 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.js
@@ -87,7 +87,7 @@ export default {
:href="mr.targetBranchPath"
class="label-branch">
{{mr.targetBranch}}
- </a>
+ </a>.
</p>
<p v-if="mr.shouldRemoveSourceBranch">
The source branch will be removed.
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_sha_mismatch.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_sha_mismatch.js
new file mode 100644
index 00000000000..79f8ef408e6
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_sha_mismatch.js
@@ -0,0 +1,16 @@
+export default {
+ name: 'MRWidgetSHAMismatch',
+ template: `
+ <div class="mr-widget-body">
+ <button
+ type="button"
+ class="btn btn-success btn-small"
+ disabled="true">
+ Merge
+ </button>
+ <span class="bold">
+ The source branch HEAD has recently changed. Please reload the page and review the changes before merging.
+ </span>
+ </div>
+ `,
+};
diff --git a/app/assets/javascripts/vue_merge_request_widget/dependencies.js b/app/assets/javascripts/vue_merge_request_widget/dependencies.js
index b2eb32ead5f..bfe30ee4c08 100644
--- a/app/assets/javascripts/vue_merge_request_widget/dependencies.js
+++ b/app/assets/javascripts/vue_merge_request_widget/dependencies.js
@@ -27,6 +27,7 @@ export { default as NothingToMergeState } from './components/states/mr_widget_no
export { default as MissingBranchState } from './components/states/mr_widget_missing_branch';
export { default as NotAllowedState } from './components/states/mr_widget_not_allowed';
export { default as ReadyToMergeState } from './components/states/mr_widget_ready_to_merge';
+export { default as SHAMismatchState } from './components/states/mr_widget_sha_mismatch';
export { default as UnresolvedDiscussionsState } from './components/states/mr_widget_unresolved_discussions';
export { default as PipelineBlockedState } from './components/states/mr_widget_pipeline_blocked';
export { default as PipelineFailedState } from './components/states/mr_widget_pipeline_failed';
diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js
index 7c6c2d21714..5452e19bd8e 100644
--- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js
+++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js
@@ -16,6 +16,7 @@ import {
MissingBranchState,
NotAllowedState,
ReadyToMergeState,
+ SHAMismatchState,
UnresolvedDiscussionsState,
PipelineBlockedState,
PipelineFailedState,
@@ -203,6 +204,7 @@ export default {
'mr-widget-not-allowed': NotAllowedState,
'mr-widget-missing-branch': MissingBranchState,
'mr-widget-ready-to-merge': ReadyToMergeState,
+ 'mr-widget-sha-mismatch': SHAMismatchState,
'mr-widget-squash-before-merge': SquashBeforeMerge,
'mr-widget-checking': CheckingState,
'mr-widget-unresolved-discussions': UnresolvedDiscussionsState,
diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/get_state_key.js b/app/assets/javascripts/vue_merge_request_widget/stores/get_state_key.js
index fee4113f3c8..fb78ea92da1 100644
--- a/app/assets/javascripts/vue_merge_request_widget/stores/get_state_key.js
+++ b/app/assets/javascripts/vue_merge_request_widget/stores/get_state_key.js
@@ -21,6 +21,8 @@ export default function deviseState(data) {
return 'unresolvedDiscussions';
} else if (this.isPipelineBlocked) {
return 'pipelineBlocked';
+ } else if (this.hasSHAChanged) {
+ return 'shaMismatch';
} else if (this.canBeMerged) {
return 'readyToMerge';
}
diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
index faafeae5c5b..05e67706983 100644
--- a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
+++ b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
@@ -4,6 +4,7 @@ import { getStateKey } from '../dependencies';
export default class MergeRequestStore {
constructor(data) {
+ this.startingSha = data.diff_head_sha;
this.setData(data);
}
@@ -67,6 +68,7 @@ export default class MergeRequestStore {
this.canMerge = !!data.merge_path;
this.canCreateIssue = currentUser.can_create_issue || false;
this.canCancelAutomaticMerge = !!data.cancel_merge_when_pipeline_succeeds_path;
+ this.hasSHAChanged = this.sha !== this.startingSha;
this.canBeMerged = data.can_be_merged || false;
// Cherry-pick and Revert actions related
diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/state_maps.js b/app/assets/javascripts/vue_merge_request_widget/stores/state_maps.js
index 625d7a01c65..605dd3a1ff4 100644
--- a/app/assets/javascripts/vue_merge_request_widget/stores/state_maps.js
+++ b/app/assets/javascripts/vue_merge_request_widget/stores/state_maps.js
@@ -16,6 +16,7 @@ const stateToComponentMap = {
mergeWhenPipelineSucceeds: 'mr-widget-merge-when-pipeline-succeeds',
failedToMerge: 'mr-widget-failed-to-merge',
autoMergeFailed: 'mr-widget-auto-merge-failed',
+ shaMismatch: 'mr-widget-sha-mismatch',
};
const statesToShowHelpWidget = [
diff --git a/app/assets/javascripts/vue_shared/ci_action_icons.js b/app/assets/javascripts/vue_shared/ci_action_icons.js
index ee41dc95beb..b21f0ab49fd 100644
--- a/app/assets/javascripts/vue_shared/ci_action_icons.js
+++ b/app/assets/javascripts/vue_shared/ci_action_icons.js
@@ -3,24 +3,19 @@ import retrySVG from 'icons/_icon_action_retry.svg';
import playSVG from 'icons/_icon_action_play.svg';
import stopSVG from 'icons/_icon_action_stop.svg';
+/**
+ * For the provided action returns the respective SVG
+ *
+ * @param {String} action
+ * @return {SVG|String}
+ */
export default function getActionIcon(action) {
- let icon;
- switch (action) {
- case 'icon_action_cancel':
- icon = cancelSVG;
- break;
- case 'icon_action_retry':
- icon = retrySVG;
- break;
- case 'icon_action_play':
- icon = playSVG;
- break;
- case 'icon_action_stop':
- icon = stopSVG;
- break;
- default:
- icon = '';
- }
+ const icons = {
+ icon_action_cancel: cancelSVG,
+ icon_action_play: playSVG,
+ icon_action_retry: retrySVG,
+ icon_action_stop: stopSVG,
+ };
- return icon;
+ return icons[action] || '';
}
diff --git a/app/assets/javascripts/vue_shared/ci_status_icons.js b/app/assets/javascripts/vue_shared/ci_status_icons.js
index 48ad9214ac8..d9d0cad38e4 100644
--- a/app/assets/javascripts/vue_shared/ci_status_icons.js
+++ b/app/assets/javascripts/vue_shared/ci_status_icons.js
@@ -41,15 +41,3 @@ export const statusIconEntityMap = {
icon_status_success: SUCCESS_SVG,
icon_status_warning: WARNING_SVG,
};
-
-export const statusCssClasses = {
- icon_status_canceled: 'canceled',
- icon_status_created: 'created',
- icon_status_failed: 'failed',
- icon_status_manual: 'manual',
- icon_status_pending: 'pending',
- icon_status_running: 'running',
- icon_status_skipped: 'skipped',
- icon_status_success: 'success',
- icon_status_warning: 'warning',
-};
diff --git a/app/assets/javascripts/vue_shared/components/ci_badge_link.vue b/app/assets/javascripts/vue_shared/components/ci_badge_link.vue
new file mode 100644
index 00000000000..caa28bff6db
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/ci_badge_link.vue
@@ -0,0 +1,52 @@
+<script>
+import ciIcon from './ci_icon.vue';
+/**
+ * Renders CI Badge link with CI icon and status text based on
+ * API response shared between all places where it is used.
+ *
+ * Receives status object containing:
+ * status: {
+ * details_path: "/gitlab-org/gitlab-ce/pipelines/8150156" // url
+ * group:"running" // used for CSS class
+ * icon: "icon_status_running" // used to render the icon
+ * label:"running" // used for potential tooltip
+ * text:"running" // text rendered
+ * }
+ *
+ * Used in:
+ * - Pipelines table - first column
+ * - Jobs table - first column
+ * - Pipeline show view - header
+ * - Job show view - header
+ * - MR widget
+ */
+
+export default {
+ props: {
+ status: {
+ type: Object,
+ required: true,
+ },
+ },
+
+ components: {
+ ciIcon,
+ },
+
+ computed: {
+ cssClass() {
+ const className = this.status.group;
+
+ return className ? `ci-status ci-${this.status.group}` : 'ci-status';
+ },
+ },
+};
+</script>
+<template>
+ <a
+ :href="status.details_path"
+ :class="cssClass">
+ <ci-icon :status="status" />
+ {{status.text}}
+ </a>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/ci_icon.vue b/app/assets/javascripts/vue_shared/components/ci_icon.vue
index 4d44baaa3c4..ec88119e16c 100644
--- a/app/assets/javascripts/vue_shared/components/ci_icon.vue
+++ b/app/assets/javascripts/vue_shared/components/ci_icon.vue
@@ -1,6 +1,27 @@
<script>
- import { statusIconEntityMap, statusCssClasses } from '../../vue_shared/ci_status_icons';
+ import { statusIconEntityMap } from '../ci_status_icons';
+ /**
+ * Renders CI icon based on API response shared between all places where it is used.
+ *
+ * Receives status object containing:
+ * status: {
+ * details_path: "/gitlab-org/gitlab-ce/pipelines/8150156" // url
+ * group:"running" // used for CSS class
+ * icon: "icon_status_running" // used to render the icon
+ * label:"running" // used for potential tooltip
+ * text:"running" // text rendered
+ * }
+ *
+ * Used in:
+ * - Pipelines table Badge
+ * - Pipelines table mini graph
+ * - Pipeline graph
+ * - Pipeline show view badge
+ * - Jobs table
+ * - Jobs show view header
+ * - Jobs show view sidebar
+ */
export default {
props: {
status: {
@@ -15,7 +36,7 @@
},
cssClass() {
- const status = statusCssClasses[this.status.icon];
+ const status = this.status.group;
return `ci-status-icon ci-status-icon-${status} js-ci-status-icon-${status}`;
},
},
diff --git a/app/assets/javascripts/vue_shared/components/commit.js b/app/assets/javascripts/vue_shared/components/commit.js
index fb68abd95a2..9b060a0a35f 100644
--- a/app/assets/javascripts/vue_shared/components/commit.js
+++ b/app/assets/javascripts/vue_shared/components/commit.js
@@ -119,14 +119,14 @@ export default {
</div>
<a v-if="hasCommitRef"
- class="monospace branch-name"
+ class="ref-name"
:href="commitRef.ref_url">
{{commitRef.name}}
</a>
<div v-html="commitIconSvg" class="commit-icon js-commit-icon"></div>
- <a class="commit-id monospace"
+ <a class="commit-sha"
:href="commitUrl">
{{shortSha}}
</a>
diff --git a/app/assets/javascripts/vue_shared/components/pipeline_status_icon.js b/app/assets/javascripts/vue_shared/components/pipeline_status_icon.js
deleted file mode 100644
index ae246ada01b..00000000000
--- a/app/assets/javascripts/vue_shared/components/pipeline_status_icon.js
+++ /dev/null
@@ -1,23 +0,0 @@
-import { statusClassToSvgMap } from '../pipeline_svg_icons';
-
-export default {
- name: 'PipelineStatusIcon',
- props: {
- pipelineStatus: { type: Object, required: true, default: () => ({}) },
- },
- computed: {
- svg() {
- return statusClassToSvgMap[this.pipelineStatus.icon];
- },
- statusClass() {
- return `ci-status-icon ci-status-icon-${this.pipelineStatus.group}`;
- },
- },
- template: `
- <div :class="statusClass">
- <a class="icon-link" :href="pipelineStatus.details_path">
- <span v-html="svg" aria-hidden="true"></span>
- </a>
- </div>
- `,
-};
diff --git a/app/assets/javascripts/vue_shared/components/pipelines_table_row.js b/app/assets/javascripts/vue_shared/components/pipelines_table_row.js
index 7ac7ceaa4e5..30d16e4ed3e 100644
--- a/app/assets/javascripts/vue_shared/components/pipelines_table_row.js
+++ b/app/assets/javascripts/vue_shared/components/pipelines_table_row.js
@@ -2,7 +2,7 @@
import AsyncButtonComponent from '../../pipelines/components/async_button.vue';
import PipelinesActionsComponent from '../../pipelines/components/pipelines_actions';
import PipelinesArtifactsComponent from '../../pipelines/components/pipelines_artifacts';
-import PipelinesStatusComponent from '../../pipelines/components/status';
+import ciBadge from './ci_badge_link.vue';
import PipelinesStageComponent from '../../pipelines/components/stage.vue';
import PipelinesUrlComponent from '../../pipelines/components/pipeline_url';
import PipelinesTimeagoComponent from '../../pipelines/components/time_ago';
@@ -39,7 +39,7 @@ export default {
'commit-component': CommitComponent,
'dropdown-stage': PipelinesStageComponent,
'pipeline-url': PipelinesUrlComponent,
- 'status-scope': PipelinesStatusComponent,
+ ciBadge,
'time-ago': PipelinesTimeagoComponent,
},
@@ -196,11 +196,20 @@ export default {
return '';
},
+
+ pipelineStatus() {
+ if (this.pipeline.details && this.pipeline.details.status) {
+ return this.pipeline.details.status;
+ }
+ return {};
+ },
},
template: `
<tr class="commit">
- <status-scope :pipeline="pipeline"/>
+ <td class="commit-link">
+ <ci-badge :status="pipelineStatus"/>
+ </td>
<pipeline-url :pipeline="pipeline"></pipeline-url>
diff --git a/app/assets/javascripts/vue_shared/pipeline_svg_icons.js b/app/assets/javascripts/vue_shared/pipeline_svg_icons.js
deleted file mode 100644
index 5af30ae74f0..00000000000
--- a/app/assets/javascripts/vue_shared/pipeline_svg_icons.js
+++ /dev/null
@@ -1,43 +0,0 @@
-import canceledSvg from 'icons/_icon_status_canceled.svg';
-import createdSvg from 'icons/_icon_status_created.svg';
-import failedSvg from 'icons/_icon_status_failed.svg';
-import manualSvg from 'icons/_icon_status_manual.svg';
-import pendingSvg from 'icons/_icon_status_pending.svg';
-import runningSvg from 'icons/_icon_status_running.svg';
-import skippedSvg from 'icons/_icon_status_skipped.svg';
-import successSvg from 'icons/_icon_status_success.svg';
-import warningSvg from 'icons/_icon_status_warning.svg';
-
-import canceledBorderlessSvg from 'icons/_icon_status_canceled_borderless.svg';
-import createdBorderlessSvg from 'icons/_icon_status_created_borderless.svg';
-import failedBorderlessSvg from 'icons/_icon_status_failed_borderless.svg';
-import manualBorderlessSvg from 'icons/_icon_status_manual_borderless.svg';
-import pendingBorderlessSvg from 'icons/_icon_status_pending_borderless.svg';
-import runningBorderlessSvg from 'icons/_icon_status_running_borderless.svg';
-import skippedBorderlessSvg from 'icons/_icon_status_skipped_borderless.svg';
-import successBorderlessSvg from 'icons/_icon_status_success_borderless.svg';
-import warningBorderlessSvg from 'icons/_icon_status_warning_borderless.svg';
-
-export const statusClassToSvgMap = {
- icon_status_canceled: canceledSvg,
- icon_status_created: createdSvg,
- icon_status_failed: failedSvg,
- icon_status_manual: manualSvg,
- icon_status_pending: pendingSvg,
- icon_status_running: runningSvg,
- icon_status_skipped: skippedSvg,
- icon_status_success: successSvg,
- icon_status_warning: warningSvg,
-};
-
-export const statusClassToBorderlessSvgMap = {
- icon_status_canceled: canceledBorderlessSvg,
- icon_status_created: createdBorderlessSvg,
- icon_status_failed: failedBorderlessSvg,
- icon_status_manual: manualBorderlessSvg,
- icon_status_pending: pendingBorderlessSvg,
- icon_status_running: runningBorderlessSvg,
- icon_status_skipped: skippedBorderlessSvg,
- icon_status_success: successBorderlessSvg,
- icon_status_warning: warningBorderlessSvg,
-};
diff --git a/app/assets/stylesheets/framework/gfm.scss b/app/assets/stylesheets/framework/gfm.scss
index c0de09f3968..dbdd5a4464b 100644
--- a/app/assets/stylesheets/framework/gfm.scss
+++ b/app/assets/stylesheets/framework/gfm.scss
@@ -2,7 +2,7 @@
* Styles that apply to all GFM related forms.
*/
+.gfm-commit,
.gfm-commit_range {
- font-family: $monospace_font;
- font-size: 90%;
+ @extend .commit-sha;
}
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index 96d8a812723..a7c6cbaae21 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -289,11 +289,6 @@ pre {
}
}
-.monospace {
- font-family: $monospace_font;
- font-size: 90%;
-}
-
code {
&.key-fingerprint {
background: $body-bg;
@@ -305,6 +300,24 @@ a > code {
color: $link-color;
}
+.monospace {
+ font-family: $monospace_font;
+}
+
+.commit-sha,
+.ref-name {
+ @extend .monospace;
+ font-size: 95%;
+}
+
+.git-revision-dropdown-toggle {
+ @extend .monospace;
+}
+
+.git-revision-dropdown .dropdown-content ul li a {
+ @extend .ref-name;
+}
+
/**
* Apply Markdown typography
*
diff --git a/app/assets/stylesheets/framework/wells.scss b/app/assets/stylesheets/framework/wells.scss
index 32eb750180f..1c1392f8f67 100644
--- a/app/assets/stylesheets/framework/wells.scss
+++ b/app/assets/stylesheets/framework/wells.scss
@@ -12,10 +12,14 @@
}
&.branch-info {
- .monospace,
+ .commit-sha,
.commit-info {
margin-left: 4px;
}
+
+ .ref-name {
+ font-size: 12px;
+ }
}
}
diff --git a/app/assets/stylesheets/highlight/dark.scss b/app/assets/stylesheets/highlight/dark.scss
index 09951fe3d3e..6e3829d994f 100644
--- a/app/assets/stylesheets/highlight/dark.scss
+++ b/app/assets/stylesheets/highlight/dark.scss
@@ -185,6 +185,11 @@ $dark-il: #de935f;
color: $dark-highlight-color !important;
}
+ // Links to URLs, emails, or dependencies
+ .line a {
+ color: $dark-na;
+ }
+
.hll { background-color: $dark-hll-bg; }
.c { color: $dark-c; } /* Comment */
.err { color: $dark-err; } /* Error */
diff --git a/app/assets/stylesheets/highlight/monokai.scss b/app/assets/stylesheets/highlight/monokai.scss
index b6a6d298adf..68eb0c7720f 100644
--- a/app/assets/stylesheets/highlight/monokai.scss
+++ b/app/assets/stylesheets/highlight/monokai.scss
@@ -185,6 +185,11 @@ $monokai-gi: #a6e22e;
color: $black !important;
}
+ // Links to URLs, emails, or dependencies
+ .line a {
+ color: $monokai-k;
+ }
+
.hll { background-color: $monokai-hll; }
.c { color: $monokai-c; } /* Comment */
.err { color: $monokai-err-color; background-color: $monokai-err-bg; } /* Error */
diff --git a/app/assets/stylesheets/highlight/solarized_dark.scss b/app/assets/stylesheets/highlight/solarized_dark.scss
index 4f7a50dcb4f..2cc968c32f2 100644
--- a/app/assets/stylesheets/highlight/solarized_dark.scss
+++ b/app/assets/stylesheets/highlight/solarized_dark.scss
@@ -188,6 +188,11 @@ $solarized-dark-il: #2aa198;
background-color: $solarized-dark-highlight !important;
}
+ // Links to URLs, emails, or dependencies
+ .line a {
+ color: $solarized-dark-kd;
+ }
+
/* Solarized Dark
For use with Jekyll and Pygments
diff --git a/app/assets/stylesheets/highlight/solarized_light.scss b/app/assets/stylesheets/highlight/solarized_light.scss
index 6463fe96c1b..b61b85a2cd1 100644
--- a/app/assets/stylesheets/highlight/solarized_light.scss
+++ b/app/assets/stylesheets/highlight/solarized_light.scss
@@ -196,6 +196,11 @@ $solarized-light-il: #2aa198;
background-color: $solarized-light-highlight !important;
}
+ // Links to URLs, emails, or dependencies
+ .line a {
+ color: $solarized-light-kd;
+ }
+
/* Solarized Light
For use with Jekyll and Pygments
diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss
index ab2018bfbca..1daa10aef24 100644
--- a/app/assets/stylesheets/highlight/white.scss
+++ b/app/assets/stylesheets/highlight/white.scss
@@ -203,6 +203,11 @@ $white-gc-bg: #eaf2f5;
background-color: $white-highlight !important;
}
+ // Links to URLs, emails, or dependencies
+ .line a {
+ color: $white-nb;
+ }
+
.hll { background-color: $white-hll-bg; }
.c { color: $white-c; font-style: italic; }
.err { color: $white-err; background-color: $white-err-bg; }
diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss
index c8996753809..bb72f453d1b 100644
--- a/app/assets/stylesheets/pages/commits.scss
+++ b/app/assets/stylesheets/pages/commits.scss
@@ -206,11 +206,11 @@
margin-left: $gl-padding;
}
}
-}
-.commit-short-id {
- font-family: $monospace_font;
- font-weight: 600;
+ .commit-sha {
+ font-size: 14px;
+ font-weight: 600;
+ }
}
.commit,
@@ -271,7 +271,7 @@
}
}
- .commit-id {
+ .commit-sha {
color: $gl-link-color;
}
diff --git a/app/assets/stylesheets/pages/cycle_analytics.scss b/app/assets/stylesheets/pages/cycle_analytics.scss
index d29944207c5..7bec4bd5f56 100644
--- a/app/assets/stylesheets/pages/cycle_analytics.scss
+++ b/app/assets/stylesheets/pages/cycle_analytics.scss
@@ -387,7 +387,7 @@
padding: 0 3px 0 0;
}
- .branch-name {
+ .ref-name {
color: $black;
display: inline-block;
max-width: 180px;
@@ -398,7 +398,7 @@
vertical-align: top;
}
- .short-sha {
+ .commit-sha {
color: $gl-link-color;
line-height: 1.3;
vertical-align: top;
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 0d5c4aed971..cfb1df4df84 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -2,11 +2,6 @@
.diff-file {
margin-bottom: $gl-padding;
- .commit-short-id {
- font-family: $regular_font;
- font-weight: 400;
- }
-
.file-title,
.file-title-flex-parent {
cursor: pointer;
diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss
index af991a128f3..a42ae7e55a5 100644
--- a/app/assets/stylesheets/pages/environments.scss
+++ b/app/assets/stylesheets/pages/environments.scss
@@ -90,7 +90,7 @@
}
.build-link,
- .branch-name {
+ .ref-name {
color: $gl-text-color;
}
@@ -135,7 +135,7 @@
}
.branch-commit {
- .commit-id {
+ .commit-sha {
margin-right: 0;
}
}
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 87592926930..8f5db1b3b9c 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -90,11 +90,6 @@
align-items: center;
padding: $gl-padding-top $gl-padding 0;
- i,
- svg {
- margin-right: 8px;
- }
-
svg {
position: relative;
top: 1px;
@@ -109,9 +104,10 @@
flex-wrap: wrap;
}
- .ci-status-icon > .icon-link svg {
+ .icon-link > .ci-status-icon > svg {
width: 22px;
height: 22px;
+ margin-right: 8px;
}
}
@@ -160,6 +156,34 @@
text-transform: capitalize;
}
+ .label-branch {
+ @extend .ref-name;
+
+ color: $gl-text-color;
+ font-weight: bold;
+ overflow: hidden;
+ margin: 0 3px;
+ word-break: break-all;
+
+ &.label-truncated {
+ position: relative;
+ display: inline-block;
+ width: 250px;
+ margin-bottom: -3px;
+ white-space: nowrap;
+ text-overflow: clip;
+ line-height: 14px;
+
+ &::after {
+ position: absolute;
+ content: '...';
+ right: 0;
+ font-family: $regular_font;
+ background-color: $gray-light;
+ }
+ }
+ }
+
.js-deployment-link {
display: inline-block;
}
@@ -359,34 +383,6 @@
}
}
-.label-branch {
- color: $gl-text-color;
- font-family: $monospace_font;
- font-weight: bold;
- overflow: hidden;
- font-size: 90%;
- margin: 0 3px;
- word-break: break-all;
-
- &.label-truncated {
- position: relative;
- display: inline-block;
- width: 250px;
- margin-bottom: -3px;
- white-space: nowrap;
- text-overflow: clip;
- line-height: 14px;
-
- &::after {
- position: absolute;
- content: '...';
- right: 0;
- font-family: $regular_font;
- background-color: $gray-light;
- }
- }
-}
-
.commits-empty {
text-align: center;
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 6d7b7031c30..0600bb1cb1a 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -238,11 +238,6 @@ ul.notes {
ul {
margin: 3px 0 3px 16px !important;
-
- .gfm-commit {
- font-family: $monospace_font;
- font-size: 12px;
- }
}
p:first-child {
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index 685b9775fe1..e4f5ab26b4d 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -158,9 +158,13 @@
float: none;
}
+ .api {
+ @extend .monospace;
+ }
+
.branch-commit {
- .branch-name {
+ .ref-name {
font-weight: bold;
max-width: 120px;
overflow: hidden;
@@ -182,7 +186,7 @@
color: $gl-text-color;
}
- .commit-id {
+ .commit-sha {
color: $gl-link-color;
}
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index c119f0c9b22..ed4a5474034 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -657,9 +657,8 @@ pre.light-well {
color: $gl-text-color;
}
- .commit_short_id {
+ .commit-sha {
margin-right: 5px;
- color: $gl-link-color;
font-weight: 600;
}
@@ -825,7 +824,8 @@ pre.light-well {
}
.compare-form-group {
- .dropdown-menu {
+ .dropdown-menu,
+ .inline-input-group {
width: 100%;
@media (min-width: $screen-sm-min) {
@@ -844,14 +844,6 @@ pre.light-well {
width: auto;
}
}
-
- .inline-input-group {
- width: 100%;
-
- @media (min-width: $screen-sm-min) {
- width: 250px;
- }
- }
}
.clearable-input {
diff --git a/app/controllers/projects/deployments_controller.rb b/app/controllers/projects/deployments_controller.rb
index f06a4d943f3..6644deb49c9 100644
--- a/app/controllers/projects/deployments_controller.rb
+++ b/app/controllers/projects/deployments_controller.rb
@@ -11,13 +11,15 @@ class Projects::DeploymentsController < Projects::ApplicationController
end
def metrics
- @metrics = deployment.metrics(1.hour)
-
+ return render_404 unless deployment.has_metrics?
+ @metrics = deployment.metrics
if @metrics&.any?
render json: @metrics, status: :ok
else
head :no_content
end
+ rescue NotImplementedError
+ render_404
end
private
diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb
index c85e96cf78d..206d0753f08 100644
--- a/app/helpers/button_helper.rb
+++ b/app/helpers/button_helper.rb
@@ -42,7 +42,10 @@ module ButtonHelper
class: "btn #{css_class}",
data: data,
type: :button,
- title: title
+ title: title,
+ aria: {
+ label: title
+ }
end
def http_clone_button(project, placement = 'right', append_link: true)
diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb
index 97b497a0fed..6d6f1361bf9 100644
--- a/app/helpers/commits_helper.rb
+++ b/app/helpers/commits_helper.rb
@@ -74,12 +74,8 @@ module CommitsHelper
# Returns the sorted alphabetically links to branches, separated by a comma
def commit_branches_links(project, branches)
branches.sort.map do |branch|
- link_to(
- namespace_project_tree_path(project.namespace, project, branch)
- ) do
- content_tag :span, class: 'label label-gray' do
- icon('code-fork') + ' ' + branch
- end
+ link_to(project_ref_path(project, branch), class: "label label-gray ref-name") do
+ icon('code-fork') + " #{branch}"
end
end.join(" ").html_safe
end
@@ -88,13 +84,8 @@ module CommitsHelper
def commit_tags_links(project, tags)
sorted = VersionSorter.rsort(tags)
sorted.map do |tag|
- link_to(
- namespace_project_commits_path(project.namespace, project,
- project.repository.find_tag(tag).name)
- ) do
- content_tag :span, class: 'label label-gray' do
- icon('tag') + ' ' + tag
- end
+ link_to(project_ref_path(project, tag), class: "label label-gray ref-name") do
+ icon('tag') + " #{tag}"
end
end.join(" ").html_safe
end
@@ -198,8 +189,8 @@ module CommitsHelper
tree_join(commit_sha, diff_new_path)),
class: 'btn view-file js-view-file'
) do
- raw('View file @') + content_tag(:span, commit_sha[0..6],
- class: 'commit-short-id')
+ raw('View file @ ') + content_tag(:span, Commit.truncate_sha(commit_sha),
+ class: 'commit-sha')
end
end
diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb
index 63bc3b11b56..4a06ee653ee 100644
--- a/app/helpers/diff_helper.rb
+++ b/app/helpers/diff_helper.rb
@@ -63,7 +63,7 @@ module DiffHelper
def parallel_diff_discussions(left, right, diff_file)
return unless @grouped_diff_discussions
-
+
discussions_left = discussions_right = nil
if left && (left.unchanged? || left.removed?)
@@ -98,7 +98,7 @@ module DiffHelper
[
content_tag(:span, link_to(truncate(blob.name, length: 40), tree)),
'@',
- content_tag(:span, commit_id, class: 'monospace')
+ content_tag(:span, commit_id, class: 'commit-sha')
].join(' ').html_safe
end
diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb
index c14438da281..751d61955b7 100644
--- a/app/helpers/events_helper.rb
+++ b/app/helpers/events_helper.rb
@@ -164,9 +164,14 @@ module EventsHelper
def event_note_title_html(event)
if event.note_target
- link_to(event_note_target_path(event), title: event.target_title, class: 'has-tooltip') do
- "#{event.note_target_type} #{event.note_target_reference}"
- end
+ text = raw("#{event.note_target_type} ") +
+ if event.commit_note?
+ content_tag(:span, event.note_target_reference, class: 'commit-sha')
+ else
+ event.note_target_reference
+ end
+
+ link_to(text, event_note_target_path(event), title: event.target_title, class: 'has-tooltip')
else
content_tag(:strong, '(deleted)')
end
diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb
index bcf71bc347b..fc308b3960e 100644
--- a/app/helpers/gitlab_routing_helper.rb
+++ b/app/helpers/gitlab_routing_helper.rb
@@ -54,6 +54,10 @@ module GitlabRoutingHelper
namespace_project_builds_path(project.namespace, project, *args)
end
+ def project_ref_path(project, ref_name, *args)
+ namespace_project_commits_path(project.namespace, project, ref_name, *args)
+ end
+
def project_container_registry_path(project, *args)
namespace_project_container_registry_index_path(project.namespace, project, *args)
end
diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb
index 0ff2d5ce4cd..19286fadb19 100644
--- a/app/helpers/todos_helper.rb
+++ b/app/helpers/todos_helper.rb
@@ -24,10 +24,13 @@ module TodosHelper
end
def todo_target_link(todo)
- target = todo.target_type.titleize.downcase
- link_to "#{target} #{todo.target_reference}", todo_target_path(todo),
- class: 'has-tooltip',
- title: todo.target.title
+ text = raw("#{todo.target_type.titleize.downcase} ") +
+ if todo.for_commit?
+ content_tag(:span, todo.target_reference, class: 'commit-sha')
+ else
+ todo.target_reference
+ end
+ link_to text, todo_target_path(todo), class: 'has-tooltip', title: todo.target.title
end
def todo_target_path(todo)
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 3a143a5a1f6..8d54ce6eb25 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -326,10 +326,7 @@ class Commit
end
def raw_diffs(*args)
- use_gitaly = Gitlab::GitalyClient.feature_enabled?(:commit_raw_diffs)
- deltas_only = args.last.is_a?(Hash) && args.last[:deltas_only]
-
- if use_gitaly && !deltas_only
+ if Gitlab::GitalyClient.feature_enabled?(:commit_raw_diffs)
Gitlab::GitalyClient::Commit.diff_from_parent(self, *args)
else
raw.diffs(*args)
diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb
index 6eddeab515e..c034bf9cbc0 100644
--- a/app/models/concerns/mentionable.rb
+++ b/app/models/concerns/mentionable.rb
@@ -44,14 +44,15 @@ module Mentionable
end
def all_references(current_user = nil, extractor: nil)
+ @extractors ||= {}
+
# Use custom extractor if it's passed in the function parameters.
if extractor
- @extractor = extractor
+ @extractors[current_user] = extractor
else
- @extractor ||= Gitlab::ReferenceExtractor.
- new(project, current_user)
+ extractor = @extractors[current_user] ||= Gitlab::ReferenceExtractor.new(project, current_user)
- @extractor.reset_memoized_values
+ extractor.reset_memoized_values
end
self.class.mentionable_attrs.each do |attr, options|
@@ -62,10 +63,10 @@ module Mentionable
skip_project_check: skip_project_check?
)
- @extractor.analyze(text, options)
+ extractor.analyze(text, options)
end
- @extractor
+ extractor
end
def mentioned_users(current_user = nil)
diff --git a/app/models/concerns/protected_branch_access.rb b/app/models/concerns/protected_branch_access.rb
index c41b807df8a..a40148a4394 100644
--- a/app/models/concerns/protected_branch_access.rb
+++ b/app/models/concerns/protected_branch_access.rb
@@ -7,5 +7,27 @@ module ProtectedBranchAccess
belongs_to :protected_branch
delegate :project, to: :protected_branch
+
+ validates :access_level, presence: true, inclusion: {
+ in: [
+ Gitlab::Access::MASTER,
+ Gitlab::Access::DEVELOPER,
+ Gitlab::Access::NO_ACCESS
+ ]
+ }
+
+ def self.human_access_levels
+ {
+ Gitlab::Access::MASTER => "Masters",
+ Gitlab::Access::DEVELOPER => "Developers + Masters",
+ Gitlab::Access::NO_ACCESS => "No one"
+ }.with_indifferent_access
+ end
+
+ def check_access(user)
+ return false if access_level == Gitlab::Access::NO_ACCESS
+
+ super
+ end
end
end
diff --git a/app/models/deployment.rb b/app/models/deployment.rb
index f83d9e8edee..216cec751e3 100644
--- a/app/models/deployment.rb
+++ b/app/models/deployment.rb
@@ -103,15 +103,10 @@ class Deployment < ActiveRecord::Base
project.monitoring_service.present?
end
- def metrics(timeframe)
+ def metrics
return {} unless has_metrics?
- half_timeframe = timeframe / 2
- timeframe_start = created_at - half_timeframe
- timeframe_end = created_at + half_timeframe
-
- metrics = project.monitoring_service.metrics(environment, timeframe_start: timeframe_start, timeframe_end: timeframe_end)
- metrics&.merge(deployment_time: created_at.to_i) || {}
+ project.monitoring_service.deployment_metrics(self)
end
private
diff --git a/app/models/environment.rb b/app/models/environment.rb
index 61efc1b2d17..61572d8d69a 100644
--- a/app/models/environment.rb
+++ b/app/models/environment.rb
@@ -150,7 +150,7 @@ class Environment < ActiveRecord::Base
end
def metrics
- project.monitoring_service.metrics(self) if has_metrics?
+ project.monitoring_service.environment_metrics(self) if has_metrics?
end
# An environment name is not necessarily suitable for use in URLs, DNS
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 27e3ed9bc7f..ecfc33ec1a1 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -174,7 +174,7 @@ class Issue < ActiveRecord::Base
# Returns boolean if a related branch exists for the current issue
# ignores merge requests branchs
- def has_related_branch?
+ def has_related_branch?
project.repository.branch_names.any? do |branch|
/\A#{iid}-(?!\d+-stable)/i =~ branch
end
diff --git a/app/models/issue_assignee.rb b/app/models/issue_assignee.rb
index 0663d3aaef8..06d760b6a89 100644
--- a/app/models/issue_assignee.rb
+++ b/app/models/issue_assignee.rb
@@ -3,11 +3,4 @@ class IssueAssignee < ActiveRecord::Base
belongs_to :issue
belongs_to :assignee, class_name: "User", foreign_key: :user_id
-
- after_create :update_assignee_cache_counts
- after_destroy :update_assignee_cache_counts
-
- def update_assignee_cache_counts
- assignee&.update_cache_counts
- end
end
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 59736f70f24..9e5db53b15e 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -125,7 +125,6 @@ class MergeRequest < ActiveRecord::Base
participant :assignee
after_save :keep_around_commit
- after_save :update_assignee_cache_counts, if: :assignee_id_changed?
def self.reference_prefix
'!'
@@ -187,13 +186,6 @@ class MergeRequest < ActiveRecord::Base
work_in_progress?(title) ? title : "WIP: #{title}"
end
- def update_assignee_cache_counts
- # make sure we flush the cache for both the old *and* new assignees(if they exist)
- previous_assignee = User.find_by_id(assignee_id_was) if assignee_id_was
- previous_assignee&.update_cache_counts
- assignee&.update_cache_counts
- end
-
# Returns a Hash of attributes to be used for Twitter card metadata
def card_attributes
{
diff --git a/app/models/project_services/chat_message/pipeline_message.rb b/app/models/project_services/chat_message/pipeline_message.rb
index 47b68f00cff..3edc395033c 100644
--- a/app/models/project_services/chat_message/pipeline_message.rb
+++ b/app/models/project_services/chat_message/pipeline_message.rb
@@ -35,7 +35,7 @@ module ChatMessage
def activity
{
- title: "Pipeline #{pipeline_link} of #{branch_link} #{ref_type} by #{user_name} #{humanized_status}",
+ title: "Pipeline #{pipeline_link} of #{ref_type} #{branch_link} by #{user_name} #{humanized_status}",
subtitle: "in #{project_link}",
text: "in #{pretty_duration(duration)}",
image: user_avatar || ''
@@ -45,7 +45,7 @@ module ChatMessage
private
def message
- "#{project_link}: Pipeline #{pipeline_link} of #{branch_link} #{ref_type} by #{user_name} #{humanized_status} in #{pretty_duration(duration)}"
+ "#{project_link}: Pipeline #{pipeline_link} of #{ref_type} #{branch_link} by #{user_name} #{humanized_status} in #{pretty_duration(duration)}"
end
def humanized_status
@@ -70,7 +70,7 @@ module ChatMessage
end
def branch_link
- "[#{ref}](#{branch_url})"
+ "`[#{ref}](#{branch_url})`"
end
def project_link
diff --git a/app/models/project_services/chat_message/push_message.rb b/app/models/project_services/chat_message/push_message.rb
index c52dd6ef8ef..04a59d559ca 100644
--- a/app/models/project_services/chat_message/push_message.rb
+++ b/app/models/project_services/chat_message/push_message.rb
@@ -61,7 +61,7 @@ module ChatMessage
end
def removed_branch_message
- "#{user_name} removed #{ref_type} #{ref} from #{project_link}"
+ "#{user_name} removed #{ref_type} `#{ref}` from #{project_link}"
end
def push_message
@@ -102,7 +102,7 @@ module ChatMessage
end
def branch_link
- "[#{ref}](#{branch_url})"
+ "`[#{ref}](#{branch_url})`"
end
def project_link
diff --git a/app/models/project_services/monitoring_service.rb b/app/models/project_services/monitoring_service.rb
index 59776552540..ee9cd78327a 100644
--- a/app/models/project_services/monitoring_service.rb
+++ b/app/models/project_services/monitoring_service.rb
@@ -9,8 +9,11 @@ class MonitoringService < Service
%w()
end
- # Environments have a number of metrics
- def metrics(environment, timeframe_start: nil, timeframe_end: nil)
+ def environment_metrics(environment)
+ raise NotImplementedError
+ end
+
+ def deployment_metrics(deployment)
raise NotImplementedError
end
end
diff --git a/app/models/project_services/prometheus_service.rb b/app/models/project_services/prometheus_service.rb
index 6a4479c4dbc..ec72cb6856d 100644
--- a/app/models/project_services/prometheus_service.rb
+++ b/app/models/project_services/prometheus_service.rb
@@ -63,45 +63,31 @@ class PrometheusService < MonitoringService
{ success: false, result: err }
end
- def metrics(environment, timeframe_start: nil, timeframe_end: nil)
- with_reactive_cache(environment.slug, timeframe_start, timeframe_end) do |data|
- data
- end
+ def environment_metrics(environment)
+ with_reactive_cache(Gitlab::Prometheus::Queries::EnvironmentQuery.name, environment.id, &:itself)
+ end
+
+ def deployment_metrics(deployment)
+ metrics = with_reactive_cache(Gitlab::Prometheus::Queries::DeploymentQuery.name, deployment.id, &:itself)
+ metrics&.merge(deployment_time: created_at.to_i) || {}
end
# Cache metrics for specific environment
- def calculate_reactive_cache(environment_slug, timeframe_start, timeframe_end)
+ def calculate_reactive_cache(query_class_name, *args)
return unless active? && project && !project.pending_delete?
- timeframe_start = Time.parse(timeframe_start) if timeframe_start
- timeframe_end = Time.parse(timeframe_end) if timeframe_end
-
- timeframe_start ||= 8.hours.ago
- timeframe_end ||= Time.now
-
- memory_query = %{(sum(container_memory_usage_bytes{container_name!="POD",environment="#{environment_slug}"}) / count(container_memory_usage_bytes{container_name!="POD",environment="#{environment_slug}"})) /1024/1024}
- cpu_query = %{sum(rate(container_cpu_usage_seconds_total{container_name!="POD",environment="#{environment_slug}"}[2m])) / count(container_cpu_usage_seconds_total{container_name!="POD",environment="#{environment_slug}"}) * 100}
+ metrics = Kernel.const_get(query_class_name).new(client).query(*args)
{
success: true,
- metrics: {
- # Average Memory used in MB
- memory_values: client.query_range(memory_query, start: timeframe_start, stop: timeframe_end),
- memory_current: client.query(memory_query, time: timeframe_end),
- memory_previous: client.query(memory_query, time: timeframe_start),
- # Average CPU Utilization
- cpu_values: client.query_range(cpu_query, start: timeframe_start, stop: timeframe_end),
- cpu_current: client.query(cpu_query, time: timeframe_end),
- cpu_previous: client.query(cpu_query, time: timeframe_start)
- },
+ metrics: metrics,
last_update: Time.now.utc
}
-
rescue Gitlab::PrometheusError => err
{ success: false, result: err.message }
end
def client
- @prometheus ||= Gitlab::Prometheus.new(api_url: api_url)
+ @prometheus ||= Gitlab::PrometheusClient.new(api_url: api_url)
end
end
diff --git a/app/models/protected_branch/merge_access_level.rb b/app/models/protected_branch/merge_access_level.rb
index 771e3376613..e8d35ac326f 100644
--- a/app/models/protected_branch/merge_access_level.rb
+++ b/app/models/protected_branch/merge_access_level.rb
@@ -1,13 +1,3 @@
class ProtectedBranch::MergeAccessLevel < ActiveRecord::Base
include ProtectedBranchAccess
-
- validates :access_level, presence: true, inclusion: { in: [Gitlab::Access::MASTER,
- Gitlab::Access::DEVELOPER] }
-
- def self.human_access_levels
- {
- Gitlab::Access::MASTER => "Masters",
- Gitlab::Access::DEVELOPER => "Developers + Masters"
- }.with_indifferent_access
- end
end
diff --git a/app/models/protected_branch/push_access_level.rb b/app/models/protected_branch/push_access_level.rb
index 14610cb42b7..7a2e9e5ec5d 100644
--- a/app/models/protected_branch/push_access_level.rb
+++ b/app/models/protected_branch/push_access_level.rb
@@ -1,21 +1,3 @@
class ProtectedBranch::PushAccessLevel < ActiveRecord::Base
include ProtectedBranchAccess
-
- validates :access_level, presence: true, inclusion: { in: [Gitlab::Access::MASTER,
- Gitlab::Access::DEVELOPER,
- Gitlab::Access::NO_ACCESS] }
-
- def self.human_access_levels
- {
- Gitlab::Access::MASTER => "Masters",
- Gitlab::Access::DEVELOPER => "Developers + Masters",
- Gitlab::Access::NO_ACCESS => "No one"
- }.with_indifferent_access
- end
-
- def check_access(user)
- return false if access_level == Gitlab::Access::NO_ACCESS
-
- super
- end
end
diff --git a/app/models/user.rb b/app/models/user.rb
index f713a20233c..c7160a6af14 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -929,6 +929,11 @@ class User < ActiveRecord::Base
assigned_open_issues_count(force: true)
end
+ def invalidate_cache_counts
+ Rails.cache.delete(['users', id, 'assigned_open_merge_requests_count'])
+ Rails.cache.delete(['users', id, 'assigned_open_issues_count'])
+ end
+
def todos_done_count(force: false)
Rails.cache.fetch(['users', id, 'todos_done_count'], force: force) do
TodosFinder.new(self, state: :done).execute.count
diff --git a/app/serializers/pipeline_entity.rb b/app/serializers/pipeline_entity.rb
index 51ad0a3f8ba..ea57cc97a7e 100644
--- a/app/serializers/pipeline_entity.rb
+++ b/app/serializers/pipeline_entity.rb
@@ -38,10 +38,7 @@ class PipelineEntity < Grape::Entity
expose :path do |pipeline|
if pipeline.ref
- namespace_project_tree_path(
- pipeline.project.namespace,
- pipeline.project,
- id: pipeline.ref)
+ project_ref_path(pipeline.project, pipeline.ref)
end
end
diff --git a/app/serializers/request_aware_entity.rb b/app/serializers/request_aware_entity.rb
index 3039014aaaa..d53fcfb8c1b 100644
--- a/app/serializers/request_aware_entity.rb
+++ b/app/serializers/request_aware_entity.rb
@@ -3,6 +3,7 @@ module RequestAwareEntity
included do
include Gitlab::Routing
+ include GitlabRoutingHelper
include Gitlab::Allowable
end
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index c1e532b504a..dc2ab99b982 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -178,6 +178,7 @@ class IssuableBaseService < BaseService
after_create(issuable)
issuable.create_cross_references!(current_user)
execute_hooks(issuable)
+ issuable.assignees.each(&:invalidate_cache_counts)
end
issuable
@@ -234,6 +235,11 @@ class IssuableBaseService < BaseService
old_assignees: old_assignees
)
+ if old_assignees != issuable.assignees
+ assignees = old_assignees + issuable.assignees.to_a
+ assignees.compact.each(&:invalidate_cache_counts)
+ end
+
after_update(issuable)
issuable.create_new_cross_references!(current_user)
execute_hooks(issuable, 'update')
diff --git a/app/services/members/authorized_destroy_service.rb b/app/services/members/authorized_destroy_service.rb
index a85b9465c84..7912cac65d3 100644
--- a/app/services/members/authorized_destroy_service.rb
+++ b/app/services/members/authorized_destroy_service.rb
@@ -43,8 +43,9 @@ module Members
)
project.merge_requests.opened.assigned_to(member.user).update_all(assignee_id: nil)
- member.user.update_cache_counts
end
+
+ member.user.invalidate_cache_counts
end
end
end
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index 0766df50ed2..93bf1fb1615 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -291,8 +291,8 @@ module SystemNoteService
old_diffs, new_diffs = Gitlab::Diff::InlineDiff.new(old_title, new_title).inline_diffs
- marked_old_title = Gitlab::Diff::InlineDiffMarker.new(old_title).mark(old_diffs, mode: :deletion, markdown: true)
- marked_new_title = Gitlab::Diff::InlineDiffMarker.new(new_title).mark(new_diffs, mode: :addition, markdown: true)
+ marked_old_title = Gitlab::Diff::InlineDiffMarkdownMarker.new(old_title).mark(old_diffs, mode: :deletion)
+ marked_new_title = Gitlab::Diff::InlineDiffMarkdownMarker.new(new_title).mark(new_diffs, mode: :addition)
body = "changed title from **#{marked_old_title}** to **#{marked_new_title}**"
diff --git a/app/views/discussions/_discussion.html.haml b/app/views/discussions/_discussion.html.haml
index 38e85168f40..74992e439f3 100644
--- a/app/views/discussions/_discussion.html.haml
+++ b/app/views/discussions/_discussion.html.haml
@@ -26,7 +26,7 @@
- commit = discussion.noteable
- if commit
commit
- = link_to commit.short_id, url, class: 'monospace'
+ = link_to commit.short_id, url, class: 'commit-sha'
- else
a deleted commit
- elsif discussion.diff_discussion?
diff --git a/app/views/events/_commit.html.haml b/app/views/events/_commit.html.haml
index 1bc9f604438..3c64f1be5ff 100644
--- a/app/views/events/_commit.html.haml
+++ b/app/views/events/_commit.html.haml
@@ -1,5 +1,5 @@
%li.commit
.commit-row-title
- = link_to truncate_sha(commit[:id]), namespace_project_commit_path(project.namespace, project, commit[:id]), class: "commit_short_id", alt: '', title: truncate_sha(commit[:id])
+ = link_to truncate_sha(commit[:id]), namespace_project_commit_path(project.namespace, project, commit[:id]), class: "commit-sha", alt: '', title: truncate_sha(commit[:id])
&middot;
= markdown event_commit_title(commit[:message]), project: project, pipeline: :single_line, author: event.author
diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml
index 0e64ebd71b8..b689991bb6d 100644
--- a/app/views/layouts/_search.html.haml
+++ b/app/views/layouts/_search.html.haml
@@ -13,7 +13,7 @@
.location-badge= label
.search-input-wrap
.dropdown{ data: { url: search_autocomplete_path } }
- = search_field_tag 'search', nil, placeholder: 'Search', class: 'search-input dropdown-menu-toggle no-outline js-search-dashboard-options', spellcheck: false, tabindex: '1', autocomplete: 'off', data: { toggle: 'dropdown', issues_path: issues_dashboard_url, mr_path: merge_requests_dashboard_url }
+ = search_field_tag 'search', nil, placeholder: 'Search', class: 'search-input dropdown-menu-toggle no-outline js-search-dashboard-options', spellcheck: false, tabindex: '1', autocomplete: 'off', data: { toggle: 'dropdown', issues_path: issues_dashboard_url, mr_path: merge_requests_dashboard_url }, aria: { label: 'Search' }
.dropdown-menu.dropdown-select
= dropdown_content do
%ul
diff --git a/app/views/projects/_last_commit.html.haml b/app/views/projects/_last_commit.html.haml
index df3b1c75508..d104cc7c1a3 100644
--- a/app/views/projects/_last_commit.html.haml
+++ b/app/views/projects/_last_commit.html.haml
@@ -5,7 +5,7 @@
= ci_icon_for_status(status)
= ci_text_for_status(status)
-= link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id"
+= link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit-sha"
= link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit), class: "commit-row-message"
&middot;
#{time_ago_with_tooltip(commit.committed_date)} by
diff --git a/app/views/projects/_last_push.html.haml b/app/views/projects/_last_push.html.haml
index 768bc1fb323..f8a6e98d280 100644
--- a/app/views/projects/_last_push.html.haml
+++ b/app/views/projects/_last_push.html.haml
@@ -5,7 +5,7 @@
.event-last-push
.event-last-push-text
%span You pushed to
- = link_to namespace_project_commits_path(event.project.namespace, event.project, event.ref_name) do
+ = link_to namespace_project_commits_path(event.project.namespace, event.project, event.ref_name, class: 'commit-sha') do
%strong= event.ref_name
- if @project && event.project != @project
%span at
diff --git a/app/views/projects/blame/show.html.haml b/app/views/projects/blame/show.html.haml
index ae4658d8ca1..a2ec3d44185 100644
--- a/app/views/projects/blame/show.html.haml
+++ b/app/views/projects/blame/show.html.haml
@@ -22,7 +22,7 @@
%strong
= link_to_gfm truncate(commit.title, length: 35), namespace_project_commit_path(@project.namespace, @project, commit.id), class: "cdark"
.pull-right
- = link_to commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit), class: "monospace"
+ = link_to commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit), class: "commit-sha"
&nbsp;
.light
= commit_author_link(commit, avatar: false)
diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml
index 8b35a037c55..304c512e1b5 100644
--- a/app/views/projects/branches/_branch.html.haml
+++ b/app/views/projects/branches/_branch.html.haml
@@ -6,7 +6,8 @@
- merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project))
%li{ class: "js-branch-#{branch.name}" }
%div
- = link_to namespace_project_tree_path(@project.namespace, @project, branch.name), class: 'item-title str-truncated' do
+ = link_to namespace_project_tree_path(@project.namespace, @project, branch.name), class: 'item-title str-truncated ref-name' do
+ = icon('code-fork')
= branch.name
&nbsp;
- if branch.name == @repository.root_ref
diff --git a/app/views/projects/branches/_commit.html.haml b/app/views/projects/branches/_commit.html.haml
index de607772df6..ad8f9da0621 100644
--- a/app/views/projects/branches/_commit.html.haml
+++ b/app/views/projects/branches/_commit.html.haml
@@ -1,7 +1,7 @@
.branch-commit
.icon-container.commit-icon
= custom_icon("icon_commit")
- = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-id monospace"
+ = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-sha"
&middot;
%span.str-truncated
= link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message"
diff --git a/app/views/projects/branches/new.html.haml b/app/views/projects/branches/new.html.haml
index 55575c5e412..5a0eba3551f 100644
--- a/app/views/projects/branches/new.html.haml
+++ b/app/views/projects/branches/new.html.haml
@@ -20,7 +20,7 @@
.col-sm-10.create-from
.dropdown
= hidden_field_tag :ref, default_ref
- = button_tag type: 'button', title: default_ref, class: 'dropdown-menu-toggle wide form-control js-branch-select', required: true, data: { toggle: 'dropdown', selected: default_ref, field_name: 'ref' } do
+ = button_tag type: 'button', title: default_ref, class: 'dropdown-menu-toggle wide form-control js-branch-select git-revision-dropdown-toggle', required: true, data: { toggle: 'dropdown', selected: default_ref, field_name: 'ref' } do
.text-left.dropdown-toggle-text= default_ref
= icon('chevron-down')
= render 'shared/ref_dropdown', dropdown_class: 'wide'
diff --git a/app/views/projects/builds/_header.html.haml b/app/views/projects/builds/_header.html.haml
index a0f8f105d9a..d4cdb709b97 100644
--- a/app/views/projects/builds/_header.html.haml
+++ b/app/views/projects/builds/_header.html.haml
@@ -6,18 +6,16 @@
= render 'ci/status/badge', status: @build.detailed_status(current_user), link: false, title: @build.status_title
%strong
Job
- = link_to namespace_project_build_path(@project.namespace, @project, @build), class: 'js-build-id' do
- \##{@build.id}
+ = link_to "##{@build.id}", namespace_project_build_path(@project.namespace, @project, @build), class: 'js-build-id'
in pipeline
- = link_to pipeline_path(pipeline) do
- %strong ##{pipeline.id}
- for commit
- = link_to namespace_project_commit_path(@project.namespace, @project, pipeline.sha) do
- %strong= pipeline.short_sha
+ %strong
+ = link_to "##{pipeline.id}", pipeline_path(pipeline)
+ for
+ %strong
+ = link_to pipeline.short_sha, namespace_project_commit_path(@project.namespace, @project, pipeline.sha), class: 'commit-sha'
from
- = link_to namespace_project_commits_path(@project.namespace, @project, @build.ref) do
- %code
- = @build.ref
+ %strong
+ = link_to @build.ref, project_ref_path(@project, @build.ref), class: 'ref-name'
= render "projects/builds/user" if @build.user
diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml
index c0019996176..e796920ac82 100644
--- a/app/views/projects/ci/builds/_build.html.haml
+++ b/app/views/projects/ci/builds/_build.html.haml
@@ -23,14 +23,14 @@
- if job.ref
.icon-container
= job.tag? ? icon('tag') : icon('code-fork')
- = link_to job.ref, namespace_project_commits_path(job.project.namespace, job.project, job.ref), class: "monospace branch-name"
+ = link_to job.ref, project_ref_path(job.project, job.ref), class: "ref-name"
- else
.light none
.icon-container.commit-icon
= custom_icon("icon_commit")
- if commit_sha
- = link_to job.short_sha, namespace_project_commit_path(job.project.namespace, job.project, job.sha), class: "commit-id monospace"
+ = link_to job.short_sha, namespace_project_commit_path(job.project.namespace, job.project, job.sha), class: "commit-sha"
- if job.stuck?
= icon('warning', class: 'text-warning has-tooltip', title: 'Job is stuck. Check runners.')
@@ -58,7 +58,7 @@
- if pipeline.user
= user_avatar(user: pipeline.user, size: 20)
- else
- %span.monospace API
+ %span.api API
- if admin
%td
diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml
index 64adb70cb81..0aef5822f81 100644
--- a/app/views/projects/commit/_commit_box.html.haml
+++ b/app/views/projects/commit/_commit_box.html.haml
@@ -1,6 +1,8 @@
.page-content-header
.header-main-content
- %strong Commit #{@commit.short_id}
+ %strong
+ Commit
+ %span.commit-sha= @commit.short_id
= clipboard_button(text: @commit.id, title: "Copy commit SHA to clipboard")
%span.hidden-xs authored
#{time_ago_with_tooltip(@commit.authored_date)}
@@ -57,7 +59,7 @@
= custom_icon("icon_commit")
%span.cgray= pluralize(@commit.parents.count, "parent")
- @commit.parents.each do |parent|
- = link_to parent.short_id, namespace_project_commit_path(@project.namespace, @project, parent), class: "monospace"
+ = link_to parent.short_id, namespace_project_commit_path(@project.namespace, @project, parent), class: "commit-sha"
%span.commit-info.branches
%i.fa.fa-spinner.fa-spin
@@ -68,9 +70,10 @@
= link_to namespace_project_pipeline_path(@project.namespace, @project, last_pipeline.id) do
= ci_icon_for_status(last_pipeline.status)
Pipeline
- = link_to "##{last_pipeline.id}", namespace_project_pipeline_path(@project.namespace, @project, last_pipeline.id), class: "monospace"
+ = link_to "##{last_pipeline.id}", namespace_project_pipeline_path(@project.namespace, @project, last_pipeline.id)
= ci_label_for_status(last_pipeline.status)
- if last_pipeline.stages.any?
+ with #{"stage".pluralize(last_pipeline.stages.count)}
.mr-widget-pipeline-graph
= render 'shared/mini_pipeline_graph', pipeline: last_pipeline, klass: 'js-commit-pipeline-graph'
in
diff --git a/app/views/projects/commit/_pipeline.html.haml b/app/views/projects/commit/_pipeline.html.haml
deleted file mode 100644
index 3ee85723ebe..00000000000
--- a/app/views/projects/commit/_pipeline.html.haml
+++ /dev/null
@@ -1,52 +0,0 @@
-.pipeline-graph-container
- .row-content-block.build-content.middle-block.pipeline-actions
- .pull-right
- - if can?(current_user, :update_pipeline, pipeline.project)
- - if pipeline.builds.latest.failed.any?(&:retryable?)
- = link_to "Retry", retry_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: 'js-retry-button btn btn-grouped btn-primary', method: :post
-
- - if pipeline.builds.running_or_pending.any?
- = link_to "Cancel running", cancel_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), data: { confirm: 'Are you sure?' }, class: 'btn btn-grouped btn-danger', method: :post
-
- .oneline.clearfix
- - if defined?(pipeline_details) && pipeline_details
- Pipeline
- = link_to "##{pipeline.id}", namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: "monospace"
- with
- = pluralize pipeline.statuses.count(:id), "job"
- - if pipeline.ref
- for
- = link_to pipeline.ref, namespace_project_commits_path(pipeline.project.namespace, pipeline.project, pipeline.ref), class: "monospace"
- - if defined?(link_to_commit) && link_to_commit
- for commit
- = link_to pipeline.short_sha, namespace_project_commit_path(pipeline.project.namespace, pipeline.project, pipeline.sha), class: "monospace"
- - if pipeline.duration
- in
- = time_interval_in_words pipeline.duration
-
- .row-content-block.build-content.middle-block.js-pipeline-graph.hidden
- = render "projects/pipelines/graph", pipeline: pipeline
-
-- if pipeline.yaml_errors.present?
- .bs-callout.bs-callout-danger
- %h4 Found errors in your .gitlab-ci.yml:
- %ul
- - pipeline.yaml_errors.split(",").each do |error|
- %li= error
- You can also test your .gitlab-ci.yml in the #{link_to "Lint", ci_lint_path}
-
-- if pipeline.project.builds_enabled? && !pipeline.ci_yaml_file
- .bs-callout.bs-callout-warning
- \.gitlab-ci.yml not found in this commit
-
-.table-holder.pipeline-holder
- %table.table.ci-table.pipeline
- %thead
- %tr
- %th Status
- %th Job ID
- %th Name
- %th
- %th Coverage
- %th
- = render partial: "projects/stage/stage", collection: pipeline.stages, as: :stage
diff --git a/app/views/projects/commit/branches.html.haml b/app/views/projects/commit/branches.html.haml
index 2b0c9a4b4de..911c9ddce06 100644
--- a/app/views/projects/commit/branches.html.haml
+++ b/app/views/projects/commit/branches.html.haml
@@ -1,15 +1,15 @@
-- if @branches.any?
- %span
- - branch = commit_default_branch(@project, @branches)
- = link_to(namespace_project_tree_path(@project.namespace, @project, branch)) do
- %span.label.label-gray
- = branch
- - if @branches.any? || @tags.any?
- = link_to("#", class: "js-details-expand") do
- %span.label.label-gray
- \...
+- if @branches.any? || @tags.any?
+ - branch = commit_default_branch(@project, @branches)
+ = link_to(project_ref_path(@project, branch), class: "label label-gray ref-name") do
+ = icon('code-fork')
+ = branch
+
+ -# `commit_default_branch` deletes the default branch from `@branches`,
+ -# so only render this if we have more branches left
+ - if @branches.any? || @tags.any?
+ %span
+ = link_to "…", "#", class: "js-details-expand label label-gray"
+
%span.js-details-content.hide
- - if @branches.any?
- = commit_branches_links(@project, @branches)
- - if @tags.any?
- = commit_tags_links(@project, @tags)
+ = commit_branches_links(@project, @branches) if @branches.any?
+ = commit_tags_links(@project, @tags) if @tags.any?
diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
index 24153b8b59a..3350a0ec152 100644
--- a/app/views/projects/commits/_commit.html.haml
+++ b/app/views/projects/commits/_commit.html.haml
@@ -37,6 +37,6 @@
.commit-actions.flex-row.hidden-xs
- if commit.status(ref)
= render_commit_status(commit, ref: ref)
- = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit-short-id btn btn-transparent"
+ = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit-sha btn btn-transparent"
= clipboard_button(text: commit.id, title: "Copy commit SHA to clipboard")
= link_to_browse_code(project, commit)
diff --git a/app/views/projects/commits/_inline_commit.html.haml b/app/views/projects/commits/_inline_commit.html.haml
index c03bc3f9df9..5fb89935467 100644
--- a/app/views/projects/commits/_inline_commit.html.haml
+++ b/app/views/projects/commits/_inline_commit.html.haml
@@ -1,6 +1,6 @@
%li.commit.inline-commit
.commit-row-title
- = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id"
+ = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit-sha"
&nbsp;
%span.str-truncated
= link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message"
diff --git a/app/views/projects/compare/_form.html.haml b/app/views/projects/compare/_form.html.haml
index 1f4c9fac54c..adb724c1b8d 100644
--- a/app/views/projects/compare/_form.html.haml
+++ b/app/views/projects/compare/_form.html.haml
@@ -7,7 +7,7 @@
.input-group.inline-input-group
%span.input-group-addon from
= hidden_field_tag :from, params[:from]
- = button_tag type: 'button', title: params[:from], class: "form-control compare-dropdown-toggle js-compare-dropdown has-tooltip", required: true, data: { refs_url: refs_namespace_project_path(@project.namespace, @project), toggle: "dropdown", target: ".js-compare-from-dropdown", selected: params[:from], field_name: :from } do
+ = button_tag type: 'button', title: params[:from], class: "form-control compare-dropdown-toggle js-compare-dropdown has-tooltip git-revision-dropdown-toggle", required: true, data: { refs_url: refs_namespace_project_path(@project.namespace, @project), toggle: "dropdown", target: ".js-compare-from-dropdown", selected: params[:from], field_name: :from } do
.dropdown-toggle-text.str-truncated= params[:from] || 'Select branch/tag'
= render 'shared/ref_dropdown'
.compare-ellipsis.inline ...
@@ -15,7 +15,7 @@
.input-group.inline-input-group
%span.input-group-addon to
= hidden_field_tag :to, params[:to]
- = button_tag type: 'button', title: params[:to], class: "form-control compare-dropdown-toggle js-compare-dropdown has-tooltip", required: true, data: { refs_url: refs_namespace_project_path(@project.namespace, @project), toggle: "dropdown", target: ".js-compare-to-dropdown", selected: params[:to], field_name: :to } do
+ = button_tag type: 'button', title: params[:to], class: "form-control compare-dropdown-toggle js-compare-dropdown has-tooltip git-revision-dropdown-toggle", required: true, data: { refs_url: refs_namespace_project_path(@project.namespace, @project), toggle: "dropdown", target: ".js-compare-to-dropdown", selected: params[:to], field_name: :to } do
.dropdown-toggle-text.str-truncated= params[:to] || 'Select branch/tag'
= render 'shared/ref_dropdown'
&nbsp;
diff --git a/app/views/projects/compare/index.html.haml b/app/views/projects/compare/index.html.haml
index 45be6581cfc..2cf14859f30 100644
--- a/app/views/projects/compare/index.html.haml
+++ b/app/views/projects/compare/index.html.haml
@@ -6,10 +6,10 @@
.sub-header-block
Compare Git revisions.
%br
- Fill input field with commit id like
- %code.label-branch 4eedf23
+ Fill input field with commit SHA like
+ %code.ref-name 4eedf23
or branch/tag name like
- %code.label-branch master
+ %code.ref-name master
and press compare button for the commits list and a code diff.
%br
Changes are shown <b>from</b> the version in the first field <b>to</b> the version in the second field.
diff --git a/app/views/projects/compare/show.html.haml b/app/views/projects/compare/show.html.haml
index 0dfc9fe20ed..a1bca2cf83a 100644
--- a/app/views/projects/compare/show.html.haml
+++ b/app/views/projects/compare/show.html.haml
@@ -16,9 +16,9 @@
There isn't anything to compare.
%p.slead
- if params[:to] == params[:from]
- %span.label-branch= params[:from]
+ %span.ref-name= params[:from]
and
- %span.label-branch= params[:to]
+ %span.ref-name= params[:to]
are the same.
- else
You'll need to use different branch names to get a valid comparison.
diff --git a/app/views/projects/deployments/_commit.html.haml b/app/views/projects/deployments/_commit.html.haml
index 170d786ecbf..31fd982c522 100644
--- a/app/views/projects/deployments/_commit.html.haml
+++ b/app/views/projects/deployments/_commit.html.haml
@@ -2,10 +2,10 @@
- if deployment.ref
.icon-container
= deployment.tag? ? icon('tag') : icon('code-fork')
- = link_to deployment.ref, namespace_project_commits_path(@project.namespace, @project, deployment.ref), class: "monospace branch-name"
+ = link_to deployment.ref, project_ref_path(@project, deployment.ref), class: "ref-name"
.icon-container.commit-icon
= custom_icon("icon_commit")
- = link_to deployment.short_sha, namespace_project_commit_path(@project.namespace, @project, deployment.sha), class: "commit-id monospace"
+ = link_to deployment.short_sha, namespace_project_commit_path(@project.namespace, @project, deployment.sha), class: "commit-sha"
%p.commit-title
%span
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index 160345cfaa5..d9643dc7957 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -40,8 +40,8 @@
.form_group.prepend-top-20.sharing-and-permissions
.row.js-visibility-select
.col-md-9
- %label.label-light
- = label_tag :project_visibility, 'Project Visibility', class: 'label-light'
+ .label-light
+ = label_tag :project_visibility, 'Project Visibility', class: 'label-light', for: :project_visibility_level
= link_to "(?)", help_page_path("public_access/public_access")
%span.help-block
.col-md-3.visibility-select-container
diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
index f458646522c..b23bbadbdb4 100644
--- a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
+++ b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
@@ -27,7 +27,7 @@
= custom_icon("icon_commit")
- if commit_sha
- = link_to generic_commit_status.short_sha, namespace_project_commit_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.sha), class: "commit-id monospace"
+ = link_to generic_commit_status.short_sha, namespace_project_commit_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.sha), class: "commit-sha"
- if retried
= icon('warning', class: 'text-warning has-tooltip', title: 'Status was retried.')
@@ -48,7 +48,7 @@
- if generic_commit_status.pipeline.user
= user_avatar(user: generic_commit_status.pipeline.user, size: 20)
- else
- %span.monospace API
+ %span.api API
- if admin
%td
diff --git a/app/views/projects/issues/_related_branches.html.haml b/app/views/projects/issues/_related_branches.html.haml
index 1892ebb512f..8c9f6f3b4df 100644
--- a/app/views/projects/issues/_related_branches.html.haml
+++ b/app/views/projects/issues/_related_branches.html.haml
@@ -11,5 +11,4 @@
= render_pipeline_status(pipeline)
%span.related-branch-info
%strong
- = link_to namespace_project_compare_path(@project.namespace, @project, from: @project.default_branch, to: branch), class: "label-branch" do
- = branch
+ = link_to branch, namespace_project_compare_path(@project.namespace, @project, from: @project.default_branch, to: branch), class: "ref-name"
diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml
index 11b7aaec704..94b9577e9eb 100644
--- a/app/views/projects/merge_requests/_merge_request.html.haml
+++ b/app/views/projects/merge_requests/_merge_request.html.haml
@@ -37,7 +37,7 @@
by #{link_to_member(@project, merge_request.author, avatar: false)}
- if merge_request.target_project.default_branch != merge_request.target_branch
&nbsp;
- = link_to namespace_project_commits_path(merge_request.project.namespace, merge_request.project, merge_request.target_branch) do
+ = link_to project_ref_path(merge_request.project, merge_request.target_branch), class: 'ref-name' do
= icon('code-fork')
= merge_request.target_branch
diff --git a/app/views/projects/merge_requests/_new_compare.html.haml b/app/views/projects/merge_requests/_new_compare.html.haml
index 9cf24e10842..0f37abb579c 100644
--- a/app/views/projects/merge_requests/_new_compare.html.haml
+++ b/app/views/projects/merge_requests/_new_compare.html.haml
@@ -21,8 +21,8 @@
selected: f.object.source_project_id
.merge-request-select.dropdown
= 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" }
- .dropdown-menu.dropdown-menu-selectable.dropdown-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-menu.dropdown-menu-selectable.dropdown-source-branch.git-revision-dropdown
= dropdown_title("Select source branch")
= dropdown_filter("Search branches")
= dropdown_content do
@@ -51,8 +51,8 @@
selected: f.object.target_project_id
.merge-request-select.dropdown
= 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" }
- .dropdown-menu.dropdown-menu-selectable.dropdown-target-branch.js-target-branch-dropdown
+ = 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-menu.dropdown-menu-selectable.dropdown-target-branch.js-target-branch-dropdown.git-revision-dropdown
= dropdown_title("Select target branch")
= dropdown_filter("Search branches")
= dropdown_content do
diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml
index da79ca2ee75..e3ecbee5490 100644
--- a/app/views/projects/merge_requests/_new_submit.html.haml
+++ b/app/views/projects/merge_requests/_new_submit.html.haml
@@ -3,9 +3,9 @@
%p.slead
- source_title, target_title = format_mr_branch_names(@merge_request)
From
- %strong.label-branch= source_title
+ %strong.ref-name= source_title
%span into
- %strong.label-branch= target_title
+ %strong.ref-name= target_title
%span.pull-right
= link_to 'Change branches', mr_change_branches_path(@merge_request)
diff --git a/app/views/projects/merge_requests/show/_versions.html.haml b/app/views/projects/merge_requests/show/_versions.html.haml
index 11b0c55be0b..37117bc64a3 100644
--- a/app/views/projects/merge_requests/show/_versions.html.haml
+++ b/app/views/projects/merge_requests/show/_versions.html.haml
@@ -20,25 +20,27 @@
- @merge_request_diffs.each do |merge_request_diff|
%li
= link_to merge_request_version_path(@project, @merge_request, merge_request_diff, @start_sha), class: ('is-active' if merge_request_diff == @merge_request_diff) do
- %strong
- - if merge_request_diff.latest?
- latest version
- - else
- version #{version_index(merge_request_diff)}
- .monospace= short_sha(merge_request_diff.head_commit_sha)
- %small
- #{number_with_delimiter(merge_request_diff.commits_count)} #{'commit'.pluralize(merge_request_diff.commits_count)},
- = time_ago_with_tooltip(merge_request_diff.created_at)
+ %div
+ %strong
+ - if merge_request_diff.latest?
+ latest version
+ - else
+ version #{version_index(merge_request_diff)}
+ %div
+ %small.commit-sha= short_sha(merge_request_diff.head_commit_sha)
+ %div
+ %small
+ #{number_with_delimiter(merge_request_diff.commits_count)} #{'commit'.pluralize(merge_request_diff.commits_count)},
+ = time_ago_with_tooltip(merge_request_diff.created_at)
- if @merge_request_diff.base_commit_sha
and
%span.dropdown.inline.mr-version-compare-dropdown
%a.btn.btn-default.dropdown-toggle{ data: {toggle: :dropdown} }
- %span
- - if @start_version
- version #{version_index(@start_version)}
- - else
- #{@merge_request.target_branch}
+ - if @start_version
+ version #{version_index(@start_version)}
+ - else
+ %span.ref-name= @merge_request.target_branch
= icon('caret-down')
.dropdown-menu.dropdown-select.dropdown-menu-selectable
.dropdown-title
@@ -50,19 +52,25 @@
- @comparable_diffs.each do |merge_request_diff|
%li
= link_to merge_request_version_path(@project, @merge_request, @merge_request_diff, merge_request_diff.head_commit_sha), class: ('is-active' if merge_request_diff == @start_version) do
- %strong
- - if merge_request_diff.latest?
- latest version
- - else
- version #{version_index(merge_request_diff)}
- .monospace= short_sha(merge_request_diff.head_commit_sha)
- %small
- = time_ago_with_tooltip(merge_request_diff.created_at)
+ %div
+ %strong
+ - if merge_request_diff.latest?
+ latest version
+ - else
+ version #{version_index(merge_request_diff)}
+ %div
+ %small.commit-sha= short_sha(merge_request_diff.head_commit_sha)
+ %div
+ %small
+ = time_ago_with_tooltip(merge_request_diff.created_at)
%li
= link_to merge_request_version_path(@project, @merge_request, @merge_request_diff), class: ('is-active' unless @start_version) do
- %strong
- #{@merge_request.target_branch} (base)
- .monospace= short_sha(@merge_request_diff.base_commit_sha)
+ %div
+ %strong
+ %span.ref-name= @merge_request.target_branch
+ (base)
+ %div
+ %strong.commit-sha= short_sha(@merge_request_diff.base_commit_sha)
- if different_base?(@start_version, @merge_request_diff)
.content-block
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index 9e292729425..e180cb8bad1 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -30,7 +30,7 @@
#{root_url}#{current_user.username}/
= f.hidden_field :namespace_id, value: current_user.namespace_id
.form-group.col-xs-12.col-sm-6.project-path
- = f.label :namespace_id, class: 'label-light' do
+ = f.label :path, class: 'label-light' do
%span
Project name
= f.text_field :path, placeholder: "my-awesome-project", class: "form-control", tabindex: 2, autofocus: true, required: true
diff --git a/app/views/projects/pipeline_schedules/_form.html.haml b/app/views/projects/pipeline_schedules/_form.html.haml
index 4a21cce024e..2d272a93b98 100644
--- a/app/views/projects/pipeline_schedules/_form.html.haml
+++ b/app/views/projects/pipeline_schedules/_form.html.haml
@@ -19,7 +19,7 @@
.form-group
.col-md-6
= f.label :ref, 'Target Branch', class: 'label-light'
- = dropdown_tag("Select target branch", options: { toggle_class: 'btn js-target-branch-dropdown', title: "Select target branch", filter: true, placeholder: "Filter", data: { data: @project.repository.branch_names } } )
+ = dropdown_tag("Select target branch", options: { toggle_class: 'btn js-target-branch-dropdown git-revision-dropdown-toggle', dropdown_class: 'git-revision-dropdown', title: "Select target branch", filter: true, placeholder: "Filter", data: { data: @project.repository.branch_names } } )
= f.text_field :ref, value: @schedule.ref, id: 'schedule_ref', class: 'hidden', name: 'schedule[ref]', required: true
.form-group
.col-md-6
diff --git a/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml b/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml
index 1406868488f..2cd82e1b661 100644
--- a/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml
+++ b/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml
@@ -4,12 +4,13 @@
= pipeline_schedule.description
%td.branch-name-cell
= icon('code-fork')
- = link_to pipeline_schedule.ref, namespace_project_commits_path(@project.namespace, @project, pipeline_schedule.ref), class: "branch-name"
+ = link_to pipeline_schedule.ref, project_ref_path(@project, pipeline_schedule.ref), class: "ref-name"
%td
- if pipeline_schedule.last_pipeline
.status-icon-container{ class: "ci-status-icon-#{pipeline_schedule.last_pipeline.status}" }
= link_to namespace_project_pipeline_path(@project.namespace, @project, pipeline_schedule.last_pipeline.id) do
= ci_icon_for_status(pipeline_schedule.last_pipeline.status)
+ %span ##{pipeline_schedule.last_pipeline.id}
- else
None
%td.next-run-cell
diff --git a/app/views/projects/pipelines/_info.html.haml b/app/views/projects/pipelines/_info.html.haml
index ab6baaf35b6..8607da8fcdd 100644
--- a/app/views/projects/pipelines/_info.html.haml
+++ b/app/views/projects/pipelines/_info.html.haml
@@ -30,7 +30,7 @@
= pluralize @pipeline.statuses.count(:id), "job"
- if @pipeline.ref
from
- = link_to @pipeline.ref, namespace_project_commits_path(@project.namespace, @project, @pipeline.ref), class: "monospace"
+ = link_to @pipeline.ref, project_ref_path(@project, @pipeline.ref), class: "ref-name"
- if @pipeline.duration
in
= time_interval_in_words(@pipeline.duration)
@@ -40,10 +40,10 @@
.well-segment.branch-info
.icon-container.commit-icon
= custom_icon("icon_commit")
- = link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @pipeline.sha), class: "monospace js-details-short"
+ = link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @pipeline.sha), class: "commit-sha js-details-short"
= link_to("#", class: "js-details-expand hidden-xs hidden-sm") do
%span.text-expander
\...
%span.js-details-content.hide
- = link_to @pipeline.sha, namespace_project_commit_path(@project.namespace, @project, @pipeline.sha), class: "monospace commit-hash-full"
+ = link_to @pipeline.sha, namespace_project_commit_path(@project.namespace, @project, @pipeline.sha), class: "commit-sha commit-hash-full"
= clipboard_button(text: @pipeline.sha, title: "Copy commit SHA to clipboard")
diff --git a/app/views/projects/pipelines/new.html.haml b/app/views/projects/pipelines/new.html.haml
index 14a270a3039..71a8e490c3e 100644
--- a/app/views/projects/pipelines/new.html.haml
+++ b/app/views/projects/pipelines/new.html.haml
@@ -11,8 +11,8 @@
.col-sm-10
= hidden_field_tag 'pipeline[ref]', params[:ref] || @project.default_branch
= dropdown_tag(params[:ref] || @project.default_branch,
- options: { toggle_class: 'js-branch-select wide',
- filter: true, dropdown_class: "dropdown-menu-selectable", placeholder: "Search branches",
+ options: { toggle_class: 'js-branch-select wide git-revision-dropdown-toggle',
+ filter: true, dropdown_class: "dropdown-menu-selectable git-revision-dropdown", placeholder: "Search branches",
data: { selected: params[:ref] || @project.default_branch, field_name: 'pipeline[ref]' } })
.help-block Existing branch name, tag
.form-actions
diff --git a/app/views/projects/protected_branches/_dropdown.html.haml b/app/views/projects/protected_branches/_dropdown.html.haml
index 5af0cc7a2f3..6e9c473494e 100644
--- a/app/views/projects/protected_branches/_dropdown.html.haml
+++ b/app/views/projects/protected_branches/_dropdown.html.haml
@@ -1,8 +1,8 @@
= f.hidden_field(:name)
= dropdown_tag('Select branch or create wildcard',
- options: { toggle_class: 'js-protected-branch-select js-filter-submit wide',
- filter: true, dropdown_class: "dropdown-menu-selectable", placeholder: "Search protected branches",
+ options: { toggle_class: 'js-protected-branch-select js-filter-submit wide git-revision-dropdown-toggle',
+ filter: true, dropdown_class: "dropdown-menu-selectable git-revision-dropdown", placeholder: "Search protected branches",
footer_content: true,
data: { show_no: true, show_any: true, show_upcoming: true,
selected: params[:protected_branch_name],
diff --git a/app/views/projects/protected_branches/_matching_branch.html.haml b/app/views/projects/protected_branches/_matching_branch.html.haml
index 8a5332ca5bb..27896272733 100644
--- a/app/views/projects/protected_branches/_matching_branch.html.haml
+++ b/app/views/projects/protected_branches/_matching_branch.html.haml
@@ -1,9 +1,10 @@
%tr
%td
- = link_to matching_branch.name, namespace_project_tree_path(@project.namespace, @project, matching_branch.name)
+ = link_to matching_branch.name, project_ref_path(@project, matching_branch.name), class: 'ref-name'
+
- if @project.root_ref?(matching_branch.name)
%span.label.label-info.prepend-left-5 default
%td
- commit = @project.commit(matching_branch.name)
- = link_to(commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit.id), class: 'commit_short_id')
+ = link_to(commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit.id), class: 'commit-sha')
= time_ago_with_tooltip(commit.committed_date)
diff --git a/app/views/projects/protected_branches/_protected_branch.html.haml b/app/views/projects/protected_branches/_protected_branch.html.haml
index b2a6b8469a3..0f80de94392 100644
--- a/app/views/projects/protected_branches/_protected_branch.html.haml
+++ b/app/views/projects/protected_branches/_protected_branch.html.haml
@@ -1,6 +1,7 @@
%tr.js-protected-branch-edit-form{ data: { url: namespace_project_protected_branch_path(@project.namespace, @project, protected_branch) } }
%td
- = protected_branch.name
+ %span.ref-name= protected_branch.name
+
- if @project.root_ref?(protected_branch.name)
%span.label.label-info.prepend-left-5 default
%td
@@ -9,7 +10,7 @@
= link_to pluralize(matching_branches.count, "matching branch"), namespace_project_protected_branch_path(@project.namespace, @project, protected_branch)
- else
- if commit = protected_branch.commit
- = link_to(commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit.id), class: 'commit_short_id')
+ = link_to(commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit.id), class: 'commit-sha')
= time_ago_with_tooltip(commit.committed_date)
- else
(branch was removed from repository)
diff --git a/app/views/projects/protected_branches/show.html.haml b/app/views/projects/protected_branches/show.html.haml
index f8cfe5e4b11..a806a0756ec 100644
--- a/app/views/projects/protected_branches/show.html.haml
+++ b/app/views/projects/protected_branches/show.html.haml
@@ -2,7 +2,7 @@
.row.prepend-top-default.append-bottom-default
.col-lg-3
- %h4.prepend-top-0
+ %h4.prepend-top-0.ref-name
= @protected_ref.name
.col-lg-9
diff --git a/app/views/projects/protected_tags/_dropdown.html.haml b/app/views/projects/protected_tags/_dropdown.html.haml
index c50515cfe06..c8531f96f97 100644
--- a/app/views/projects/protected_tags/_dropdown.html.haml
+++ b/app/views/projects/protected_tags/_dropdown.html.haml
@@ -1,8 +1,8 @@
= f.hidden_field(:name)
= dropdown_tag('Select tag or create wildcard',
- options: { toggle_class: 'js-protected-tag-select js-filter-submit wide',
- filter: true, dropdown_class: "dropdown-menu-selectable capitalize-header", placeholder: "Search protected tag",
+ options: { toggle_class: 'js-protected-tag-select js-filter-submit wide git-revision-dropdown-toggle',
+ filter: true, dropdown_class: "dropdown-menu-selectable capitalize-header git-revision-dropdown", placeholder: "Search protected tag",
footer_content: true,
data: { show_no: true, show_any: true, show_upcoming: true,
selected: params[:protected_tag_name],
diff --git a/app/views/projects/protected_tags/_matching_tag.html.haml b/app/views/projects/protected_tags/_matching_tag.html.haml
index 97e5cd6f9d2..f17353df122 100644
--- a/app/views/projects/protected_tags/_matching_tag.html.haml
+++ b/app/views/projects/protected_tags/_matching_tag.html.haml
@@ -1,9 +1,10 @@
%tr
%td
- = link_to matching_tag.name, namespace_project_tree_path(@project.namespace, @project, matching_tag.name)
+ = link_to matching_tag.name, project_ref_path(@project, matching_tag.name), class: 'ref-name'
+
- if @project.root_ref?(matching_tag.name)
%span.label.label-info.prepend-left-5 default
%td
- commit = @project.commit(matching_tag.name)
- = link_to(commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit.id), class: 'commit_short_id')
+ = link_to(commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit.id), class: 'commit-sha')
= time_ago_with_tooltip(commit.committed_date)
diff --git a/app/views/projects/protected_tags/_protected_tag.html.haml b/app/views/projects/protected_tags/_protected_tag.html.haml
index 26bd3a1f5ed..54249ec0db1 100644
--- a/app/views/projects/protected_tags/_protected_tag.html.haml
+++ b/app/views/projects/protected_tags/_protected_tag.html.haml
@@ -1,6 +1,7 @@
%tr.js-protected-tag-edit-form{ data: { url: namespace_project_protected_tag_path(@project.namespace, @project, protected_tag) } }
%td
- = protected_tag.name
+ %span.ref-name= protected_tag.name
+
- if @project.root_ref?(protected_tag.name)
%span.label.label-info.prepend-left-5 default
%td
@@ -9,7 +10,7 @@
= link_to pluralize(matching_tags.count, "matching tag"), namespace_project_protected_tag_path(@project.namespace, @project, protected_tag)
- else
- if commit = protected_tag.commit
- = link_to(commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit.id), class: 'commit_short_id')
+ = link_to(commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit.id), class: 'commit-sha')
= time_ago_with_tooltip(commit.committed_date)
- else
(tag was removed from repository)
diff --git a/app/views/projects/protected_tags/show.html.haml b/app/views/projects/protected_tags/show.html.haml
index 63743f28b3c..94c3612a449 100644
--- a/app/views/projects/protected_tags/show.html.haml
+++ b/app/views/projects/protected_tags/show.html.haml
@@ -2,7 +2,7 @@
.row.prepend-top-default.append-bottom-default
.col-lg-3
- %h4.prepend-top-0
+ %h4.prepend-top-0.ref-name
= @protected_ref.name
.col-lg-9
diff --git a/app/views/projects/runners/_runner.html.haml b/app/views/projects/runners/_runner.html.haml
index deeadb609f6..674f87e8220 100644
--- a/app/views/projects/runners/_runner.html.haml
+++ b/app/views/projects/runners/_runner.html.haml
@@ -1,15 +1,18 @@
%li.runner{ id: dom_id(runner) }
%h4
= runner_status_icon(runner)
- %span.monospace
- - if @project_runners.include?(runner)
- = link_to runner.short_sha, runner_path(runner)
- - if runner.locked?
- = icon('lock', class: 'has-tooltip', title: 'Locked to current projects')
- %small
- = link_to edit_namespace_project_runner_path(@project.namespace, @project, runner) do
- %i.fa.fa-edit.btn
- - else
+
+ - if @project_runners.include?(runner)
+ = link_to runner.short_sha, runner_path(runner), class: 'commit-sha'
+
+ - if runner.locked?
+ = icon('lock', class: 'has-tooltip', title: 'Locked to current projects')
+
+ %small
+ = link_to edit_namespace_project_runner_path(@project.namespace, @project, runner) do
+ %i.fa.fa-edit.btn
+ - else
+ %span.commit-sha
= runner.short_sha
.pull-right
diff --git a/app/views/projects/tags/_tag.html.haml b/app/views/projects/tags/_tag.html.haml
index 4c4f3655b97..44cb734d7b9 100644
--- a/app/views/projects/tags/_tag.html.haml
+++ b/app/views/projects/tags/_tag.html.haml
@@ -2,10 +2,9 @@
- release = @releases.find { |release| release.tag == tag.name }
%li.flex-row
.row-main-content.str-truncated
- = link_to namespace_project_tag_path(@project.namespace, @project, tag.name) do
- %span.item-title
- = icon('tag')
- = tag.name
+ = link_to namespace_project_tag_path(@project.namespace, @project, tag.name), class: 'item-title ref-name' do
+ = icon('tag')
+ = tag.name
- if protected_tag?(@project, tag)
%span.label.label-success
diff --git a/app/views/projects/tags/show.html.haml b/app/views/projects/tags/show.html.haml
index e996ae3e4fc..2b81ce4b9fa 100644
--- a/app/views/projects/tags/show.html.haml
+++ b/app/views/projects/tags/show.html.haml
@@ -6,7 +6,9 @@
.top-area.multi-line
.nav-text
.title
- %span.item-title= @tag.name
+ %span.item-title.ref-name
+ = icon('tag')
+ = @tag.name
- if protected_tag?(@project, @tag)
%span.label.label-success
protected
diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml
index 34a4d7398bc..0992a65f7cd 100644
--- a/app/views/shared/_clone_panel.html.haml
+++ b/app/views/shared/_clone_panel.html.haml
@@ -17,7 +17,7 @@
%li
= http_clone_button(project)
- = text_field_tag :project_clone, default_url_to_repo(project), class: "js-select-on-focus form-control", readonly: true
+ = text_field_tag :project_clone, default_url_to_repo(project), class: "js-select-on-focus form-control", readonly: true, aria: { label: 'Project clone URL' }
.input-group-btn
= clipboard_button(target: '#project_clone', title: "Copy URL to clipboard")
diff --git a/app/views/shared/_ref_dropdown.html.haml b/app/views/shared/_ref_dropdown.html.haml
index 96f68c80c48..8b2a3bee407 100644
--- a/app/views/shared/_ref_dropdown.html.haml
+++ b/app/views/shared/_ref_dropdown.html.haml
@@ -1,6 +1,6 @@
- dropdown_class = local_assigns.fetch(:dropdown_class, '')
-.dropdown-menu.dropdown-menu-selectable{ class: dropdown_class }
+.dropdown-menu.dropdown-menu-selectable.git-revision-dropdown{ class: dropdown_class }
= dropdown_title "Select Git revision"
= dropdown_filter "Filter by Git revision"
= dropdown_content
diff --git a/app/views/shared/_ref_switcher.html.haml b/app/views/shared/_ref_switcher.html.haml
index 9a8252ab087..2029eb5824a 100644
--- a/app/views/shared/_ref_switcher.html.haml
+++ b/app/views/shared/_ref_switcher.html.haml
@@ -6,8 +6,8 @@
- @options && @options.each do |key, value|
= hidden_field_tag key, value, id: nil
.dropdown
- = dropdown_toggle dropdown_toggle_text, { toggle: "dropdown", selected: dropdown_toggle_text, ref: @ref, refs_url: refs_namespace_project_path(@project.namespace, @project), field_name: 'ref', submit_form_on_click: true }, { toggle_class: "js-project-refs-dropdown" }
- .dropdown-menu.dropdown-menu-selectable{ class: ("dropdown-menu-align-right" if local_assigns[:align_right]) }
+ = dropdown_toggle dropdown_toggle_text, { toggle: "dropdown", selected: dropdown_toggle_text, ref: @ref, refs_url: refs_namespace_project_path(@project.namespace, @project), field_name: 'ref', submit_form_on_click: true }, { toggle_class: "js-project-refs-dropdown git-revision-dropdown-toggle" }
+ .dropdown-menu.dropdown-menu-selectable.git-revision-dropdown{ class: ("dropdown-menu-align-right" if local_assigns[:align_right]) }
= dropdown_title "Switch branch/tag"
= dropdown_filter "Search branches and tags"
= dropdown_content
diff --git a/app/views/shared/empty_states/_issues.html.haml b/app/views/shared/empty_states/_issues.html.haml
index 12d99c3ab4b..046b127f73c 100644
--- a/app/views/shared/empty_states/_issues.html.haml
+++ b/app/views/shared/empty_states/_issues.html.haml
@@ -20,4 +20,3 @@
- else
.text-center
%h4 There are no issues to show.
- = link_to 'New issue', button_path, class: 'btn btn-new', title: 'New issue', id: 'new_issue_link'
diff --git a/app/views/shared/issuable/form/_branch_chooser.html.haml b/app/views/shared/issuable/form/_branch_chooser.html.haml
index f57b4d899ce..203d2adc8db 100644
--- a/app/views/shared/issuable/form/_branch_chooser.html.haml
+++ b/app/views/shared/issuable/form/_branch_chooser.html.haml
@@ -10,14 +10,14 @@
= form.label :source_branch, class: 'control-label'
.col-sm-10
.issuable-form-select-holder
- = form.select(:source_branch, [issuable.source_branch], {}, { class: 'source_branch select2', disabled: true })
+ = form.select(:source_branch, [issuable.source_branch], {}, { class: 'source_branch select2 ref-name', disabled: true })
.form-group
= form.label :target_branch, class: 'control-label'
.col-sm-10.target-branch-select-dropdown-container
.issuable-form-select-holder
= form.select(:target_branch, issuable.target_branches,
{ include_blank: true },
- { class: 'target_branch js-target-branch-select',
+ { class: 'target_branch js-target-branch-select ref-name',
disabled: issuable.new_record?,
data: { placeholder: "Select branch" }})
- if issuable.new_record?
diff --git a/changelogs/unreleased/30286-ci-badge-component.yml b/changelogs/unreleased/30286-ci-badge-component.yml
new file mode 100644
index 00000000000..13c2a4598c8
--- /dev/null
+++ b/changelogs/unreleased/30286-ci-badge-component.yml
@@ -0,0 +1,4 @@
+---
+title: Refactor all CI vue badges to use the same vue component
+merge_request:
+author:
diff --git a/changelogs/unreleased/31384-new-issue-button-on-no-results-page-after-search-doesn-t-go-to-correct-form.yml b/changelogs/unreleased/31384-new-issue-button-on-no-results-page-after-search-doesn-t-go-to-correct-form.yml
new file mode 100644
index 00000000000..8d586616e07
--- /dev/null
+++ b/changelogs/unreleased/31384-new-issue-button-on-no-results-page-after-search-doesn-t-go-to-correct-form.yml
@@ -0,0 +1,4 @@
+---
+title: Remove 'New issue' button when issues search returns no results.
+merge_request: !11263
+author:
diff --git a/changelogs/unreleased/31978-cross-reference-fix.yml b/changelogs/unreleased/31978-cross-reference-fix.yml
new file mode 100644
index 00000000000..fbcb3d5d482
--- /dev/null
+++ b/changelogs/unreleased/31978-cross-reference-fix.yml
@@ -0,0 +1,4 @@
+---
+title: Fix cross referencing for private and internal projects
+merge_request: 11243
+author:
diff --git a/changelogs/unreleased/32178-prevent-merge-on-sha-change.yml b/changelogs/unreleased/32178-prevent-merge-on-sha-change.yml
new file mode 100644
index 00000000000..d3208973de6
--- /dev/null
+++ b/changelogs/unreleased/32178-prevent-merge-on-sha-change.yml
@@ -0,0 +1,4 @@
+---
+title: Add state to MR widget that prevent merges when branch changes after page load
+merge_request: 11316
+author:
diff --git a/changelogs/unreleased/dm-consistent-commit-sha-style.yml b/changelogs/unreleased/dm-consistent-commit-sha-style.yml
new file mode 100644
index 00000000000..b6dace34d9b
--- /dev/null
+++ b/changelogs/unreleased/dm-consistent-commit-sha-style.yml
@@ -0,0 +1,4 @@
+---
+title: Consistently use monospace font for commit SHAs and branch and tag names
+merge_request:
+author:
diff --git a/changelogs/unreleased/dm-copy-mr-source-branch-as-gfm.yml b/changelogs/unreleased/dm-copy-mr-source-branch-as-gfm.yml
new file mode 100644
index 00000000000..708c82604ad
--- /dev/null
+++ b/changelogs/unreleased/dm-copy-mr-source-branch-as-gfm.yml
@@ -0,0 +1,4 @@
+---
+title: Paste a copied MR source branch name as code when pasted into a GFM form
+merge_request:
+author:
diff --git a/changelogs/unreleased/dm-dependency-linker-gemfile.yml b/changelogs/unreleased/dm-dependency-linker-gemfile.yml
new file mode 100644
index 00000000000..2d4167a1be5
--- /dev/null
+++ b/changelogs/unreleased/dm-dependency-linker-gemfile.yml
@@ -0,0 +1,4 @@
+---
+title: Autolink package names in Gemfile
+merge_request:
+author:
diff --git a/changelogs/unreleased/protected-branches-no-one-merge.yml b/changelogs/unreleased/protected-branches-no-one-merge.yml
new file mode 100644
index 00000000000..52d93793f3d
--- /dev/null
+++ b/changelogs/unreleased/protected-branches-no-one-merge.yml
@@ -0,0 +1,4 @@
+---
+title: Allow 'no one' as an option for allowed to merge on a procted branch
+merge_request:
+author:
diff --git a/features/steps/project/builds/artifacts.rb b/features/steps/project/builds/artifacts.rb
index eec375b0532..89132ff068f 100644
--- a/features/steps/project/builds/artifacts.rb
+++ b/features/steps/project/builds/artifacts.rb
@@ -25,7 +25,7 @@ class Spinach::Features::ProjectBuildsArtifacts < Spinach::FeatureSteps
step 'I should see the build header' do
page.within('.build-header') do
- expect(page).to have_content "Job ##{@build.id} in pipeline ##{@pipeline.id} for commit #{@pipeline.short_sha}"
+ expect(page).to have_content "Job ##{@build.id} in pipeline ##{@pipeline.id} for #{@pipeline.short_sha}"
end
end
diff --git a/features/steps/project/forked_merge_requests.rb b/features/steps/project/forked_merge_requests.rb
index 310db6e6dad..29055373a57 100644
--- a/features/steps/project/forked_merge_requests.rb
+++ b/features/steps/project/forked_merge_requests.rb
@@ -5,6 +5,7 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
include SharedPaths
include Select2Helper
include WaitForVueResource
+ include WaitForAjax
step 'I am a member of project "Shop"' do
@project = ::Project.find_by(name: "Shop")
@@ -47,6 +48,7 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
first('.dropdown-target-project a', text: @project.path_with_namespace)
first('.js-source-branch').click
+ wait_for_ajax
first('.dropdown-source-branch .dropdown-content a', text: 'fix').click
click_button "Compare branches and continue"
@@ -62,31 +64,6 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
click_button "Submit merge request"
end
- step 'I follow the target commit link' do
- commit = @project.repository.commit
- click_link commit.short_id(8)
- end
-
- step 'I should see the commit under the forked from project' do
- commit = @project.repository.commit
- expect(page).to have_content(commit.message)
- end
-
- step 'I click "Create Merge Request on fork" link' do
- click_link "Create Merge Request on fork"
- end
-
- step 'I see prefilled new Merge Request page for the forked project' do
- expect(current_path).to eq new_namespace_project_merge_request_path(@forked_project.namespace, @forked_project)
- expect(find("#merge_request_source_project_id").value).to eq @forked_project.id.to_s
- expect(find("#merge_request_target_project_id").value).to eq @project.id.to_s
- expect(find("#merge_request_source_branch").value).to have_content "new_design"
- expect(find("#merge_request_target_branch").value).to have_content "master"
- expect(find("#merge_request_title").value).to eq "New Design"
- verify_commit_link(".mr_target_commit", @project)
- verify_commit_link(".mr_source_commit", @forked_project)
- end
-
step 'I update the merge request title' do
fill_in "merge_request_title", with: "An Edited Forked Merge Request"
end
@@ -155,10 +132,4 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
expect(page).to have_content @project.users.first.name
end
end
-
- # Verify a link is generated against the correct project
- def verify_commit_link(container_div, container_project)
- # This should force a wait for the javascript to execute
- expect(find(:div, container_div).find(".commit_short_id")['href']).to have_content "#{container_project.path_with_namespace}/commit"
- end
end
diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb
index de4e6e7c404..5397877b5d5 100644
--- a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb
+++ b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb
@@ -15,7 +15,7 @@ module Gitlab
end
def path_patterns
- @path_patterns ||= paths.map { |path| "%#{path}" }
+ @path_patterns ||= paths.flat_map { |path| ["%/#{path}", path] }
end
def rename_path_for_routable(routable)
diff --git a/lib/gitlab/dependency_linker.rb b/lib/gitlab/dependency_linker.rb
new file mode 100644
index 00000000000..5e884c4bea1
--- /dev/null
+++ b/lib/gitlab/dependency_linker.rb
@@ -0,0 +1,18 @@
+module Gitlab
+ module DependencyLinker
+ LINKERS = [
+ GemfileLinker,
+ ].freeze
+
+ def self.linker(blob_name)
+ LINKERS.find { |linker| linker.support?(blob_name) }
+ end
+
+ def self.link(blob_name, plain_text, highlighted_text)
+ linker = linker(blob_name)
+ return highlighted_text unless linker
+
+ linker.link(plain_text, highlighted_text)
+ end
+ end
+end
diff --git a/lib/gitlab/dependency_linker/base_linker.rb b/lib/gitlab/dependency_linker/base_linker.rb
new file mode 100644
index 00000000000..40a4ad11372
--- /dev/null
+++ b/lib/gitlab/dependency_linker/base_linker.rb
@@ -0,0 +1,103 @@
+module Gitlab
+ module DependencyLinker
+ class BaseLinker
+ def self.link(plain_text, highlighted_text)
+ new(plain_text, highlighted_text).link
+ end
+
+ attr_accessor :plain_text, :highlighted_text
+
+ def initialize(plain_text, highlighted_text)
+ @plain_text = plain_text
+ @highlighted_text = highlighted_text
+ end
+
+ def link
+ link_dependencies
+
+ highlighted_lines.join.html_safe
+ end
+
+ private
+
+ def package_url(name)
+ raise NotImplementedError
+ end
+
+ def link_dependencies
+ raise NotImplementedError
+ end
+
+ def package_link(name, url = package_url(name))
+ return name unless url
+
+ %{<a href="#{ERB::Util.html_escape_once(url)}" rel="noopener noreferrer" target="_blank">#{ERB::Util.html_escape_once(name)}</a>}
+ end
+
+ # Links package names in a method call or assignment string argument.
+ #
+ # Example:
+ # link_method_call("gem")
+ # # Will link `package` in `gem "package"`, `gem("package")` and `gem = "package"`
+ #
+ # link_method_call("gem", "specific_package")
+ # # Will link `specific_package` in `gem "specific_package"`
+ #
+ # link_method_call("github", /[^\/]+\/[^\/]+/)
+ # # Will link `user/repo` in `github "user/repo"`, but not `github "package"`
+ #
+ # link_method_call(%w[add_dependency add_development_dependency])
+ # # Will link `spec.add_dependency "package"` and `spec.add_development_dependency "package"`
+ #
+ # link_method_call("name")
+ # # Will link `package` in `self.name = "package"`
+ def link_method_call(method_names, value = nil, &url_proc)
+ value =
+ case value
+ when String
+ Regexp.escape(value)
+ when nil
+ /[^'"]+/
+ else
+ value
+ end
+
+ method_names = Array(method_names).map { |name| Regexp.escape(name) }
+
+ regex = %r{
+ #{Regexp.union(method_names)} # Method name
+ \s* # Whitespace
+ [(=]? # Opening brace or equals sign
+ \s* # Whitespace
+ ['"](?<name>#{value})['"] # Package name in quotes
+ }x
+
+ link_regex(regex, &url_proc)
+ end
+
+ # Links package names based on regex.
+ #
+ # Example:
+ # link_regex(/(github:|:github =>)\s*['"](?<name>[^'"]+)['"]/)
+ # # Will link `user/repo` in `github: "user/repo"` or `:github => "user/repo"`
+ def link_regex(regex)
+ highlighted_lines.map!.with_index do |rich_line, i|
+ marker = StringRegexMarker.new(plain_lines[i], rich_line.html_safe)
+
+ marker.mark(regex, group: :name) do |text, left:, right:|
+ url = block_given? ? yield(text) : package_url(text)
+ package_link(text, url)
+ end
+ end
+ end
+
+ def plain_lines
+ @plain_lines ||= plain_text.lines
+ end
+
+ def highlighted_lines
+ @highlighted_lines ||= highlighted_text.lines
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/dependency_linker/gemfile_linker.rb b/lib/gitlab/dependency_linker/gemfile_linker.rb
new file mode 100644
index 00000000000..45be760d89e
--- /dev/null
+++ b/lib/gitlab/dependency_linker/gemfile_linker.rb
@@ -0,0 +1,31 @@
+module Gitlab
+ module DependencyLinker
+ class GemfileLinker < BaseLinker
+ def self.support?(blob_name)
+ blob_name == 'Gemfile' || blob_name == 'gems.rb'
+ end
+
+ private
+
+ def link_dependencies
+ # Link `gem "package_name"` to https://rubygems.org/gems/package_name
+ link_method_call("gem")
+
+ # Link `github: "user/repo"` to https://github.com/user/repo
+ link_regex(/(github:|:github\s*=>)\s*['"](?<name>[^'"]+)['"]/) do |name|
+ "https://github.com/#{name}"
+ end
+
+ # Link `git: "https://gitlab.example.com/user/repo"` to https://gitlab.example.com/user/repo
+ link_regex(%r{(git:|:git\s*=>)\s*['"](?<name>https?://[^'"]+)['"]}) { |url| url }
+
+ # Link `source "https://rubygems.org"` to https://rubygems.org
+ link_method_call("source", %r{https?://[^'"]+}) { |url| url }
+ end
+
+ def package_url(name)
+ "https://rubygems.org/gems/#{name}"
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/diff/inline_diff_markdown_marker.rb b/lib/gitlab/diff/inline_diff_markdown_marker.rb
new file mode 100644
index 00000000000..c2a2eb15931
--- /dev/null
+++ b/lib/gitlab/diff/inline_diff_markdown_marker.rb
@@ -0,0 +1,17 @@
+module Gitlab
+ module Diff
+ class InlineDiffMarkdownMarker < Gitlab::StringRangeMarker
+ MARKDOWN_SYMBOLS = {
+ addition: "+",
+ deletion: "-"
+ }.freeze
+
+ def mark(line_inline_diffs, mode: nil)
+ super(line_inline_diffs) do |text, left:, right:|
+ symbol = MARKDOWN_SYMBOLS[mode]
+ "{#{symbol}#{text}#{symbol}}"
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/diff/inline_diff_marker.rb b/lib/gitlab/diff/inline_diff_marker.rb
index 736933b1c4b..919965100ae 100644
--- a/lib/gitlab/diff/inline_diff_marker.rb
+++ b/lib/gitlab/diff/inline_diff_marker.rb
@@ -1,137 +1,21 @@
module Gitlab
module Diff
- class InlineDiffMarker
- MARKDOWN_SYMBOLS = {
- addition: "+",
- deletion: "-"
- }.freeze
-
- attr_accessor :raw_line, :rich_line
-
- def initialize(raw_line, rich_line = raw_line)
- @raw_line = raw_line
- @rich_line = ERB::Util.html_escape(rich_line)
- end
-
- def mark(line_inline_diffs, mode: nil, markdown: false)
- return rich_line unless line_inline_diffs
-
- marker_ranges = []
- line_inline_diffs.each do |inline_diff_range|
- # Map the inline-diff range based on the raw line to character positions in the rich line
- inline_diff_positions = position_mapping[inline_diff_range].flatten
- # Turn the array of character positions into ranges
- marker_ranges.concat(collapse_ranges(inline_diff_positions))
- end
-
- offset = 0
-
- # Mark each range
- marker_ranges.each_with_index do |range, index|
- before_content =
- if markdown
- "{#{MARKDOWN_SYMBOLS[mode]}"
- else
- "<span class='#{html_class_names(marker_ranges, mode, index)}'>"
- end
- after_content =
- if markdown
- "#{MARKDOWN_SYMBOLS[mode]}}"
- else
- "</span>"
- end
- offset = insert_around_range(rich_line, range, before_content, after_content, offset)
+ class InlineDiffMarker < Gitlab::StringRangeMarker
+ def mark(line_inline_diffs, mode: nil)
+ super(line_inline_diffs) do |text, left:, right:|
+ %{<span class="#{html_class_names(left, right, mode)}">#{text}</span>}
end
-
- rich_line.html_safe
end
private
- def html_class_names(marker_ranges, mode, index)
+ def html_class_names(left, right, mode)
class_names = ["idiff"]
- class_names << "left" if index == 0
- class_names << "right" if index == marker_ranges.length - 1
+ class_names << "left" if left
+ class_names << "right" if right
class_names << mode if mode
class_names.join(" ")
end
-
- # Mapping of character positions in the raw line, to the rich (highlighted) line
- def position_mapping
- @position_mapping ||= begin
- mapping = []
- rich_pos = 0
- (0..raw_line.length).each do |raw_pos|
- rich_char = rich_line[rich_pos]
-
- # The raw and rich lines are the same except for HTML tags,
- # so skip over any `<...>` segment
- while rich_char == '<'
- until rich_char == '>'
- rich_pos += 1
- rich_char = rich_line[rich_pos]
- end
-
- rich_pos += 1
- rich_char = rich_line[rich_pos]
- end
-
- # multi-char HTML entities in the rich line correspond to a single character in the raw line
- if rich_char == '&'
- multichar_mapping = [rich_pos]
- until rich_char == ';'
- rich_pos += 1
- multichar_mapping << rich_pos
- rich_char = rich_line[rich_pos]
- end
-
- mapping[raw_pos] = multichar_mapping
- else
- mapping[raw_pos] = rich_pos
- end
-
- rich_pos += 1
- end
-
- mapping
- end
- end
-
- # Takes an array of integers, and returns an array of ranges covering the same integers
- def collapse_ranges(positions)
- return [] if positions.empty?
- ranges = []
-
- start = prev = positions[0]
- range = start..prev
- positions[1..-1].each do |pos|
- if pos == prev + 1
- range = start..pos
- prev = pos
- else
- ranges << range
- start = prev = pos
- range = start..prev
- end
- end
- ranges << range
-
- ranges
- end
-
- # Inserts tags around the characters identified by the given range
- def insert_around_range(text, range, before, after, offset = 0)
- # Just to be sure
- return offset if offset + range.end + 1 > text.length
-
- text.insert(offset + range.begin, before)
- offset += before.length
-
- text.insert(offset + range.end + 1, after)
- offset += after.length
-
- offset
- end
end
end
end
diff --git a/lib/gitlab/file_finder.rb b/lib/gitlab/file_finder.rb
new file mode 100644
index 00000000000..093d9ed8092
--- /dev/null
+++ b/lib/gitlab/file_finder.rb
@@ -0,0 +1,32 @@
+# This class finds files in a repository by name and content
+# the result is joined and sorted by file name
+module Gitlab
+ class FileFinder
+ BATCH_SIZE = 100
+
+ attr_reader :project, :ref
+
+ def initialize(project, ref)
+ @project = project
+ @ref = ref
+ end
+
+ def find(query)
+ blobs = project.repository.search_files_by_content(query, ref).first(BATCH_SIZE)
+ found_file_names = Set.new
+
+ results = blobs.map do |blob|
+ blob = Gitlab::ProjectSearchResults.parse_search_result(blob)
+ found_file_names << blob.filename
+
+ [blob.filename, blob]
+ end
+
+ project.repository.search_files_by_name(query, ref).first(BATCH_SIZE).each do |filename|
+ results << [filename, OpenStruct.new(ref: ref)] unless found_file_names.include?(filename)
+ end
+
+ results.sort_by(&:first)
+ end
+ end
+end
diff --git a/lib/gitlab/git/diff_collection.rb b/lib/gitlab/git/diff_collection.rb
index 4e45ec7c174..bcbad8ec829 100644
--- a/lib/gitlab/git/diff_collection.rb
+++ b/lib/gitlab/git/diff_collection.rb
@@ -15,7 +15,6 @@ module Gitlab
@safe_max_bytes = @safe_max_files * 5120 # Average 5 KB per file
@all_diffs = !!options.fetch(:all_diffs, false)
@no_collapse = !!options.fetch(:no_collapse, true)
- @deltas_only = !!options.fetch(:deltas_only, false)
@line_count = 0
@byte_count = 0
@@ -27,8 +26,6 @@ module Gitlab
if @populated
# @iterator.each is slower than just iterating the array in place
@array.each(&block)
- elsif @deltas_only
- each_delta(&block)
else
Gitlab::GitalyClient.migrate(:commit_raw_diffs) do
each_patch(&block)
@@ -81,14 +78,6 @@ module Gitlab
files >= @safe_max_files || @line_count > @safe_max_lines || @byte_count >= @safe_max_bytes
end
- def each_delta
- @iterator.each_delta.with_index do |delta, i|
- diff = Gitlab::Git::Diff.new(delta)
-
- yield @array[i] = diff
- end
- end
-
def each_patch
@iterator.each_with_index do |raw, i|
# First yield cached Diff instances from @array
diff --git a/lib/gitlab/highlight.rb b/lib/gitlab/highlight.rb
index d787d5db4a0..83bc230df3e 100644
--- a/lib/gitlab/highlight.rb
+++ b/lib/gitlab/highlight.rb
@@ -13,6 +13,8 @@ module Gitlab
highlight(file_name, blob.data, repository: repository).lines.map!(&:html_safe)
end
+ attr_reader :blob_name
+
def initialize(blob_name, blob_content, repository: nil)
@formatter = Rouge::Formatters::HTMLGitlab
@repository = repository
@@ -21,16 +23,9 @@ module Gitlab
end
def highlight(text, continue: true, plain: false)
- if plain
- hl_lexer = Rouge::Lexers::PlainText
- continue = false
- else
- hl_lexer = self.lexer
- end
-
- @formatter.format(hl_lexer.lex(text, continue: continue), tag: hl_lexer.tag).html_safe
- rescue
- @formatter.format(Rouge::Lexers::PlainText.lex(text)).html_safe
+ highlighted_text = highlight_text(text, continue: continue, plain: plain)
+ highlighted_text = link_dependencies(text, highlighted_text) if blob_name
+ highlighted_text
end
def lexer
@@ -50,5 +45,27 @@ module Gitlab
Rouge::Lexer.find_fancy(language_name)
end
+
+ def highlight_text(text, continue: true, plain: false)
+ if plain
+ highlight_plain(text)
+ else
+ highlight_rich(text, continue: continue)
+ end
+ end
+
+ def highlight_plain(text)
+ @formatter.format(Rouge::Lexers::PlainText.lex(text)).html_safe
+ end
+
+ def highlight_rich(text, continue: true)
+ @formatter.format(lexer.lex(text, continue: continue), tag: lexer.tag).html_safe
+ rescue
+ highlight_plain(text)
+ end
+
+ def link_dependencies(text, highlighted_text)
+ Gitlab::DependencyLinker.link(blob_name, text, highlighted_text)
+ end
end
end
diff --git a/lib/gitlab/project_search_results.rb b/lib/gitlab/project_search_results.rb
index 47cfe412715..561aa9e162c 100644
--- a/lib/gitlab/project_search_results.rb
+++ b/lib/gitlab/project_search_results.rb
@@ -84,23 +84,7 @@ module Gitlab
def blobs
return [] unless Ability.allowed?(@current_user, :download_code, @project)
- @blobs ||= begin
- blobs = project.repository.search_files_by_content(query, repository_ref).first(100)
- found_file_names = Set.new
-
- results = blobs.map do |blob|
- blob = self.class.parse_search_result(blob)
- found_file_names << blob.filename
-
- [blob.filename, blob]
- end
-
- project.repository.search_files_by_name(query, repository_ref).first(100).each do |filename|
- results << [filename, nil] unless found_file_names.include?(filename)
- end
-
- results.sort_by(&:first)
- end
+ @blobs ||= Gitlab::FileFinder.new(project, repository_ref).find(query)
end
def wiki_blobs
diff --git a/lib/gitlab/prometheus/queries/base_query.rb b/lib/gitlab/prometheus/queries/base_query.rb
new file mode 100644
index 00000000000..2a2eb4ae57f
--- /dev/null
+++ b/lib/gitlab/prometheus/queries/base_query.rb
@@ -0,0 +1,26 @@
+module Gitlab
+ module Prometheus
+ module Queries
+ class BaseQuery
+ attr_accessor :client
+ delegate :query_range, :query, to: :client, prefix: true
+
+ def raw_memory_usage_query(environment_slug)
+ %{avg(container_memory_usage_bytes{container_name!="POD",environment="#{environment_slug}"}) / 2^20}
+ end
+
+ def raw_cpu_usage_query(environment_slug)
+ %{avg(rate(container_cpu_usage_seconds_total{container_name!="POD",environment="#{environment_slug}"}[2m])) * 100}
+ end
+
+ def initialize(client)
+ @client = client
+ end
+
+ def query(*args)
+ raise NotImplementedError
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/prometheus/queries/deployment_query.rb b/lib/gitlab/prometheus/queries/deployment_query.rb
new file mode 100644
index 00000000000..2cc08731f8d
--- /dev/null
+++ b/lib/gitlab/prometheus/queries/deployment_query.rb
@@ -0,0 +1,26 @@
+module Gitlab::Prometheus::Queries
+ class DeploymentQuery < BaseQuery
+ def query(deployment_id)
+ deployment = Deployment.find_by(id: deployment_id)
+ environment_slug = deployment.environment.slug
+
+ memory_query = raw_memory_usage_query(environment_slug)
+ memory_avg_query = %{avg(avg_over_time(container_memory_usage_bytes{container_name!="POD",environment="#{environment_slug}"}[30m]))}
+ cpu_query = raw_cpu_usage_query(environment_slug)
+ cpu_avg_query = %{avg(rate(container_cpu_usage_seconds_total{container_name!="POD",environment="#{environment_slug}"}[30m])) * 100}
+
+ timeframe_start = (deployment.created_at - 30.minutes).to_f
+ timeframe_end = (deployment.created_at + 30.minutes).to_f
+
+ {
+ memory_values: client_query_range(memory_query, start: timeframe_start, stop: timeframe_end),
+ memory_before: client_query(memory_avg_query, time: deployment.created_at.to_f),
+ memory_after: client_query(memory_avg_query, time: timeframe_end),
+
+ cpu_values: client_query_range(cpu_query, start: timeframe_start, stop: timeframe_end),
+ cpu_before: client_query(cpu_avg_query, time: deployment.created_at.to_f),
+ cpu_after: client_query(cpu_avg_query, time: timeframe_end)
+ }
+ end
+ end
+end
diff --git a/lib/gitlab/prometheus/queries/environment_query.rb b/lib/gitlab/prometheus/queries/environment_query.rb
new file mode 100644
index 00000000000..01d756d7284
--- /dev/null
+++ b/lib/gitlab/prometheus/queries/environment_query.rb
@@ -0,0 +1,20 @@
+module Gitlab::Prometheus::Queries
+ class EnvironmentQuery < BaseQuery
+ def query(environment_id)
+ environment = Environment.find_by(id: environment_id)
+ environment_slug = environment.slug
+ timeframe_start = 8.hours.ago.to_f
+ timeframe_end = Time.now.to_f
+
+ memory_query = raw_memory_usage_query(environment_slug)
+ cpu_query = raw_cpu_usage_query(environment_slug)
+
+ {
+ memory_values: client_query_range(memory_query, start: timeframe_start, stop: timeframe_end),
+ memory_current: client_query(memory_query, time: timeframe_end),
+ cpu_values: client_query_range(cpu_query, start: timeframe_start, stop: timeframe_end),
+ cpu_current: client_query(cpu_query, time: timeframe_end)
+ }
+ end
+ end
+end
diff --git a/lib/gitlab/prometheus.rb b/lib/gitlab/prometheus_client.rb
index 37125980b1c..5b51a1779dd 100644
--- a/lib/gitlab/prometheus.rb
+++ b/lib/gitlab/prometheus_client.rb
@@ -2,7 +2,7 @@ module Gitlab
PrometheusError = Class.new(StandardError)
# Helper methods to interact with Prometheus network services & resources
- class Prometheus
+ class PrometheusClient
attr_reader :api_url
def initialize(api_url:)
@@ -15,7 +15,7 @@ module Gitlab
def query(query, time: Time.now)
get_result('vector') do
- json_api_get('query', query: query, time: time.utc.to_f)
+ json_api_get('query', query: query, time: time.to_f)
end
end
diff --git a/lib/gitlab/string_range_marker.rb b/lib/gitlab/string_range_marker.rb
new file mode 100644
index 00000000000..94fba0a221a
--- /dev/null
+++ b/lib/gitlab/string_range_marker.rb
@@ -0,0 +1,102 @@
+module Gitlab
+ class StringRangeMarker
+ attr_accessor :raw_line, :rich_line
+
+ def initialize(raw_line, rich_line = raw_line)
+ @raw_line = raw_line
+ @rich_line = ERB::Util.html_escape(rich_line)
+ end
+
+ def mark(marker_ranges)
+ return rich_line unless marker_ranges
+
+ rich_marker_ranges = []
+ marker_ranges.each do |range|
+ # Map the inline-diff range based on the raw line to character positions in the rich line
+ rich_positions = position_mapping[range].flatten
+ # Turn the array of character positions into ranges
+ rich_marker_ranges.concat(collapse_ranges(rich_positions))
+ end
+
+ offset = 0
+ # Mark each range
+ rich_marker_ranges.each_with_index do |range, i|
+ offset_range = (range.begin + offset)..(range.end + offset)
+ original_text = rich_line[offset_range]
+
+ text = yield(original_text, left: i == 0, right: i == rich_marker_ranges.length - 1)
+
+ rich_line[offset_range] = text
+
+ offset += text.length - original_text.length
+ end
+
+ rich_line.html_safe
+ end
+
+ private
+
+ # Mapping of character positions in the raw line, to the rich (highlighted) line
+ def position_mapping
+ @position_mapping ||= begin
+ mapping = []
+ rich_pos = 0
+ (0..raw_line.length).each do |raw_pos|
+ rich_char = rich_line[rich_pos]
+
+ # The raw and rich lines are the same except for HTML tags,
+ # so skip over any `<...>` segment
+ while rich_char == '<'
+ until rich_char == '>'
+ rich_pos += 1
+ rich_char = rich_line[rich_pos]
+ end
+
+ rich_pos += 1
+ rich_char = rich_line[rich_pos]
+ end
+
+ # multi-char HTML entities in the rich line correspond to a single character in the raw line
+ if rich_char == '&'
+ multichar_mapping = [rich_pos]
+ until rich_char == ';'
+ rich_pos += 1
+ multichar_mapping << rich_pos
+ rich_char = rich_line[rich_pos]
+ end
+
+ mapping[raw_pos] = multichar_mapping
+ else
+ mapping[raw_pos] = rich_pos
+ end
+
+ rich_pos += 1
+ end
+
+ mapping
+ end
+ end
+
+ # Takes an array of integers, and returns an array of ranges covering the same integers
+ def collapse_ranges(positions)
+ return [] if positions.empty?
+ ranges = []
+
+ start = prev = positions[0]
+ range = start..prev
+ positions[1..-1].each do |pos|
+ if pos == prev + 1
+ range = start..pos
+ prev = pos
+ else
+ ranges << range
+ start = prev = pos
+ range = start..prev
+ end
+ end
+ ranges << range
+
+ ranges
+ end
+ end
+end
diff --git a/lib/gitlab/string_regex_marker.rb b/lib/gitlab/string_regex_marker.rb
new file mode 100644
index 00000000000..7ebf1c0428c
--- /dev/null
+++ b/lib/gitlab/string_regex_marker.rb
@@ -0,0 +1,13 @@
+module Gitlab
+ class StringRegexMarker < StringRangeMarker
+ def mark(regex, group: 0, &block)
+ regex_match = raw_line.match(regex)
+ return rich_line unless regex_match
+
+ begin_index, end_index = regex_match.offset(group)
+ name_range = begin_index..(end_index - 1)
+
+ super([name_range], &block)
+ end
+ end
+end
diff --git a/spec/controllers/projects/deployments_controller_spec.rb b/spec/controllers/projects/deployments_controller_spec.rb
index 3de38bb4dac..4c69443314d 100644
--- a/spec/controllers/projects/deployments_controller_spec.rb
+++ b/spec/controllers/projects/deployments_controller_spec.rb
@@ -42,39 +42,68 @@ describe Projects::DeploymentsController do
before do
allow(controller).to receive(:deployment).and_return(deployment)
end
-
- context 'when environment has no metrics' do
+ context 'when metrics are disabled' do
before do
- expect(deployment).to receive(:metrics).and_return(nil)
+ allow(deployment).to receive(:has_metrics?).and_return false
end
- it 'returns a empty response 204 resposne' do
+ it 'responds with not found' do
get :metrics, deployment_params(id: deployment.id)
- expect(response).to have_http_status(204)
- expect(response.body).to eq('')
+
+ expect(response).to be_not_found
end
end
- context 'when environment has some metrics' do
- let(:empty_metrics) do
- {
- success: true,
- metrics: {},
- last_update: 42
- }
+ context 'when metrics are enabled' do
+ before do
+ allow(deployment).to receive(:has_metrics?).and_return true
end
- before do
- expect(deployment).to receive(:metrics).and_return(empty_metrics)
+ context 'when environment has no metrics' do
+ before do
+ expect(deployment).to receive(:metrics).and_return(nil)
+ end
+
+ it 'returns a empty response 204 resposne' do
+ get :metrics, deployment_params(id: deployment.id)
+ expect(response).to have_http_status(204)
+ expect(response.body).to eq('')
+ end
end
- it 'returns a metrics JSON document' do
- get :metrics, deployment_params(id: deployment.id)
+ context 'when environment has some metrics' do
+ let(:empty_metrics) do
+ {
+ success: true,
+ metrics: {},
+ last_update: 42
+ }
+ end
+
+ before do
+ expect(deployment).to receive(:metrics).and_return(empty_metrics)
+ end
+
+ it 'returns a metrics JSON document' do
+ get :metrics, deployment_params(id: deployment.id)
+
+ expect(response).to be_ok
+ expect(json_response['success']).to be(true)
+ expect(json_response['metrics']).to eq({})
+ expect(json_response['last_update']).to eq(42)
+ end
+ end
+
+ context 'when metrics service does not implement deployment metrics' do
+ before do
+ allow(deployment).to receive(:metrics).and_raise(NotImplementedError)
+ end
+
+ it 'responds with not found' do
+ get :metrics, deployment_params(id: deployment.id)
- expect(response).to be_ok
- expect(json_response['success']).to be(true)
- expect(json_response['metrics']).to eq({})
- expect(json_response['last_update']).to eq(42)
+ expect(response).to be_not_found
+ end
end
end
end
diff --git a/spec/features/dashboard/issuables_counter_spec.rb b/spec/features/dashboard/issuables_counter_spec.rb
index 6f7bf0eba6e..354267dbee7 100644
--- a/spec/features/dashboard/issuables_counter_spec.rb
+++ b/spec/features/dashboard/issuables_counter_spec.rb
@@ -19,7 +19,7 @@ describe 'Navigation bar counter', feature: true, caching: true do
issue.assignees = []
- user.update_cache_counts
+ user.invalidate_cache_counts
Timecop.travel(3.minutes.from_now) do
visit issues_path
@@ -35,6 +35,8 @@ describe 'Navigation bar counter', feature: true, caching: true do
merge_request.update(assignee: nil)
+ user.invalidate_cache_counts
+
Timecop.travel(3.minutes.from_now) do
visit merge_requests_path
diff --git a/spec/features/dashboard/milestone_filter_spec.rb b/spec/features/dashboard/milestone_filter_spec.rb
index e9ca7fd50e7..d60a002a8d7 100644
--- a/spec/features/dashboard/milestone_filter_spec.rb
+++ b/spec/features/dashboard/milestone_filter_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
-describe 'Dashboard > milestone filter', feature: true, js: true do
+describe 'Dashboard > milestone filter', :feature, :js do
+ include WaitForAjax
+
let(:user) { create(:user) }
let(:project) { create(:project, name: 'test', namespace: user.namespace) }
let(:milestone) { create(:milestone, title: "v1.0", project: project) }
@@ -26,30 +28,29 @@ describe 'Dashboard > milestone filter', feature: true, js: true do
before do
find(milestone_select).click
+ wait_for_ajax
page.within('.dropdown-content') do
click_link 'v1.0'
end
find(milestone_select).click
+ wait_for_ajax
end
it 'shows issues with Milestone v1.0' do
expect(find('.issues-list')).to have_selector('.issue', count: 1)
-
- find(milestone_select).click
-
expect(find('.dropdown-content')).to have_selector('a.is-active', count: 1)
end
it 'should not change active Milestone unless clicked' do
- find(milestone_select).trigger('click')
-
expect(find('.dropdown-content')).to have_selector('a.is-active', count: 1)
# open & close dropdown
find('.dropdown-menu-close').click
+
expect(find('.milestone-filter')).not_to have_selector('.dropdown.open')
+
find(milestone_select).click
expect(find('.dropdown-content')).to have_selector('a.is-active', count: 1)
diff --git a/spec/features/issues/form_spec.rb b/spec/features/issues/form_spec.rb
index 87adce3cddd..095cbb65c16 100644
--- a/spec/features/issues/form_spec.rb
+++ b/spec/features/issues/form_spec.rb
@@ -1,8 +1,9 @@
require 'rails_helper'
-describe 'New/edit issue', feature: true, js: true do
+describe 'New/edit issue', :feature, :js do
include GitlabRoutingHelper
include ActionView::Helpers::JavaScriptHelper
+ include WaitForAjax
let!(:project) { create(:project) }
let!(:user) { create(:user)}
@@ -26,6 +27,8 @@ describe 'New/edit issue', feature: true, js: true do
describe 'multiple assignees' do
before do
click_button 'Unassigned'
+
+ wait_for_ajax
end
it 'unselects other assignees when unassigned is selected' do
@@ -65,6 +68,9 @@ describe 'New/edit issue', feature: true, js: true do
expect(find('a', text: 'Assign to me')).to be_visible
click_button 'Unassigned'
+
+ wait_for_ajax
+
page.within '.dropdown-menu-user' do
click_link user2.name
end
@@ -148,16 +154,15 @@ describe 'New/edit issue', feature: true, js: true do
it 'correctly updates the selected user when changing assignee' do
click_button 'Unassigned'
+
+ wait_for_ajax
+
page.within '.dropdown-menu-user' do
click_link user.name
end
expect(find('input[name="issue[assignee_ids][]"]', visible: false).value).to match(user.id.to_s)
-
- click_button user.name
-
expect(find('.dropdown-menu-user a.is-active').first(:xpath, '..')['data-user-id']).to eq(user.id.to_s)
-
# check the ::before pseudo element to ensure checkmark icon is present
expect(before_for_selector('.dropdown-menu-selectable a.is-active')).not_to eq('')
expect(before_for_selector('.dropdown-menu-selectable a:not(.is-active)')).to eq('')
@@ -167,9 +172,6 @@ describe 'New/edit issue', feature: true, js: true do
end
expect(find('input[name="issue[assignee_ids][]"]', visible: false).value).to match(user2.id.to_s)
-
- click_button user2.name
-
expect(find('.dropdown-menu-user a.is-active').first(:xpath, '..')['data-user-id']).to eq(user2.id.to_s)
end
end
diff --git a/spec/features/issues/notes_on_issues_spec.rb b/spec/features/issues/notes_on_issues_spec.rb
new file mode 100644
index 00000000000..a4035324d2b
--- /dev/null
+++ b/spec/features/issues/notes_on_issues_spec.rb
@@ -0,0 +1,77 @@
+require 'spec_helper'
+
+describe 'Create notes on issues', :js, :feature do
+ let(:user) { create(:user) }
+
+ shared_examples 'notes with reference' do
+ let(:issue) { create(:issue, project: project) }
+ let(:note_text) { "Check #{mention.to_reference}" }
+
+ before do
+ project.team << [user, :developer]
+ login_as(user)
+ visit namespace_project_issue_path(project.namespace, project, issue)
+
+ fill_in 'note[note]', with: note_text
+ click_button 'Comment'
+
+ wait_for_ajax
+ end
+
+ it 'creates a note with reference and cross references the issue' do
+ page.within('div#notes li.note div.note-text') do
+ expect(page).to have_content(note_text)
+ expect(page.find('a')).to have_content(mention.to_reference)
+ end
+
+ find('div#notes li.note div.note-text a').click
+
+ page.within('div#notes li.note .system-note-message') do
+ expect(page).to have_content('mentioned in issue')
+ expect(page.find('a')).to have_content(issue.to_reference)
+ end
+ end
+ end
+
+ context 'mentioning issue on a private project' do
+ it_behaves_like 'notes with reference' do
+ let(:project) { create(:project, :private) }
+ let(:mention) { create(:issue, project: project) }
+ end
+ end
+
+ context 'mentioning issue on an internal project' do
+ it_behaves_like 'notes with reference' do
+ let(:project) { create(:project, :internal) }
+ let(:mention) { create(:issue, project: project) }
+ end
+ end
+
+ context 'mentioning issue on a public project' do
+ it_behaves_like 'notes with reference' do
+ let(:project) { create(:project, :public) }
+ let(:mention) { create(:issue, project: project) }
+ end
+ end
+
+ context 'mentioning merge request on a private project' do
+ it_behaves_like 'notes with reference' do
+ let(:project) { create(:project, :private) }
+ let(:mention) { create(:merge_request, source_project: project) }
+ end
+ end
+
+ context 'mentioning merge request on an internal project' do
+ it_behaves_like 'notes with reference' do
+ let(:project) { create(:project, :internal) }
+ let(:mention) { create(:merge_request, source_project: project) }
+ end
+ end
+
+ context 'mentioning merge request on a public project' do
+ it_behaves_like 'notes with reference' do
+ let(:project) { create(:project, :public) }
+ let(:mention) { create(:merge_request, source_project: project) }
+ end
+ end
+end
diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb
index 5285dda361b..fdd78600a1d 100644
--- a/spec/features/issues_spec.rb
+++ b/spec/features/issues_spec.rb
@@ -573,6 +573,8 @@ describe 'Issues', feature: true do
end
describe 'new issue' do
+ let!(:issue) { create(:issue, project: project) }
+
context 'by unauthenticated user' do
before do
logout
diff --git a/spec/features/projects/pipeline_schedules_spec.rb b/spec/features/projects/pipeline_schedules_spec.rb
index cdac4fe2111..fe9f94db574 100644
--- a/spec/features/projects/pipeline_schedules_spec.rb
+++ b/spec/features/projects/pipeline_schedules_spec.rb
@@ -6,6 +6,7 @@ feature 'Pipeline Schedules', :feature do
let!(:project) { create(:project) }
let!(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project) }
+ let!(:pipeline) { create(:ci_pipeline, pipeline_schedule: pipeline_schedule) }
let(:scope) { nil }
let!(:user) { create(:user) }
@@ -32,7 +33,7 @@ feature 'Pipeline Schedules', :feature do
page.within('.pipeline-schedule-table-row') do
expect(page).to have_content('pipeline schedule')
expect(page).to have_link('master')
- expect(page).to have_content('None')
+ expect(page).to have_link("##{pipeline.id}")
end
end
diff --git a/spec/features/protected_branches/access_control_ce_spec.rb b/spec/features/protected_branches/access_control_ce_spec.rb
index d30e7947106..7fda4ade665 100644
--- a/spec/features/protected_branches/access_control_ce_spec.rb
+++ b/spec/features/protected_branches/access_control_ce_spec.rb
@@ -31,7 +31,7 @@ RSpec.shared_examples "protected branches > access control > CE" do
within(".protected-branches-list") do
find(".js-allowed-to-push").click
-
+
within('.js-allowed-to-push-container') do
expect(first("li")).to have_content("Roles")
click_on access_type_name
diff --git a/spec/helpers/diff_helper_spec.rb b/spec/helpers/diff_helper_spec.rb
index eae097126ce..dd6566d25bb 100644
--- a/spec/helpers/diff_helper_spec.rb
+++ b/spec/helpers/diff_helper_spec.rb
@@ -122,9 +122,9 @@ describe DiffHelper do
it "returns strings with marked inline diffs" do
marked_old_line, marked_new_line = mark_inline_diffs(old_line, new_line)
- expect(marked_old_line).to eq("abc <span class='idiff left right deletion'>&#39;def&#39;</span>")
+ expect(marked_old_line).to eq(%q{abc <span class="idiff left right deletion">&#39;def&#39;</span>})
expect(marked_old_line).to be_html_safe
- expect(marked_new_line).to eq("abc <span class='idiff left right addition'>&quot;def&quot;</span>")
+ expect(marked_new_line).to eq(%q{abc <span class="idiff left right addition">&quot;def&quot;</span>})
expect(marked_new_line).to be_html_safe
end
end
diff --git a/spec/javascripts/droplab/drop_down_spec.js b/spec/javascripts/droplab/drop_down_spec.js
index e7786e8cc2c..2bbcebeeac0 100644
--- a/spec/javascripts/droplab/drop_down_spec.js
+++ b/spec/javascripts/droplab/drop_down_spec.js
@@ -1,5 +1,3 @@
-/* eslint-disable */
-
import DropDown from '~/droplab/drop_down';
import utils from '~/droplab/utils';
import { SELECTED_CLASS, IGNORE_CLASS } from '~/droplab/constants';
@@ -17,7 +15,7 @@ describe('DropDown', function () {
it('sets the .hidden property to true', function () {
expect(this.dropdown.hidden).toBe(true);
- })
+ });
it('sets the .list property', function () {
expect(this.dropdown.list).toBe(this.list);
@@ -152,7 +150,7 @@ describe('DropDown', function () {
it('should call addSelectedClass', function () {
expect(this.dropdown.addSelectedClass).toHaveBeenCalledWith(this.closestElement);
- })
+ });
it('should call .preventDefault', function () {
expect(this.event.preventDefault).toHaveBeenCalled();
@@ -293,36 +291,6 @@ describe('DropDown', function () {
});
});
- describe('toggle', function () {
- beforeEach(function () {
- this.dropdown = { hidden: true, show: () => {}, hide: () => {} };
-
- spyOn(this.dropdown, 'show');
- spyOn(this.dropdown, 'hide');
-
- DropDown.prototype.toggle.call(this.dropdown);
- });
-
- it('should call .show if hidden is true', function () {
- expect(this.dropdown.show).toHaveBeenCalled();
- });
-
- describe('if hidden is false', function () {
- beforeEach(function () {
- this.dropdown = { hidden: false, show: () => {}, hide: () => {} };
-
- spyOn(this.dropdown, 'show');
- spyOn(this.dropdown, 'hide');
-
- DropDown.prototype.toggle.call(this.dropdown);
- });
-
- it('should call .show if hidden is true', function () {
- expect(this.dropdown.hide).toHaveBeenCalled();
- });
- });
- });
-
describe('setData', function () {
beforeEach(function () {
this.dropdown = { render: () => {} };
@@ -399,7 +367,7 @@ describe('DropDown', function () {
expect(this.data.map).toHaveBeenCalledWith(jasmine.any(Function));
});
- it('should call .renderChildren for each data item', function() {
+ it('should call .renderChildren for each data item', function () {
expect(this.dropdown.renderChildren.calls.count()).toBe(this.data.length);
});
@@ -407,7 +375,7 @@ describe('DropDown', function () {
expect(this.renderableList.innerHTML).toBe('01');
});
- describe('if no data argument is passed' , function () {
+ describe('if no data argument is passed', function () {
beforeEach(function () {
this.data.map.calls.reset();
this.dropdown.renderChildren.calls.reset();
@@ -446,14 +414,14 @@ describe('DropDown', function () {
describe('renderChildren', function () {
beforeEach(function () {
this.templateString = 'templateString';
- this.dropdown = { setImagesSrc: () => {}, templateString: this.templateString };
+ this.dropdown = { templateString: this.templateString };
this.data = { droplab_hidden: true };
this.html = 'html';
this.template = { firstChild: { outerHTML: 'outerHTML', style: {} } };
spyOn(utils, 'template').and.returnValue(this.html);
spyOn(document, 'createElement').and.returnValue(this.template);
- spyOn(this.dropdown, 'setImagesSrc');
+ spyOn(DropDown, 'setImagesSrc');
this.renderChildren = DropDown.prototype.renderChildren.call(this.dropdown, this.data);
});
@@ -471,7 +439,7 @@ describe('DropDown', function () {
});
it('should call .setImagesSrc with the template', function () {
- expect(this.dropdown.setImagesSrc).toHaveBeenCalledWith(this.template);
+ expect(DropDown.setImagesSrc).toHaveBeenCalledWith(this.template);
});
it('should set the template display to none', function () {
@@ -496,12 +464,11 @@ describe('DropDown', function () {
describe('setImagesSrc', function () {
beforeEach(function () {
- this.dropdown = {};
this.template = { querySelectorAll: () => {} };
spyOn(this.template, 'querySelectorAll').and.returnValue([]);
- DropDown.prototype.setImagesSrc.call(this.dropdown, this.template);
+ DropDown.setImagesSrc(this.template);
});
it('should call .querySelectorAll', function () {
@@ -562,7 +529,7 @@ describe('DropDown', function () {
describe('toggle', function () {
beforeEach(function () {
- this.hidden = true
+ this.hidden = true;
this.dropdown = { hidden: this.hidden, show: () => {}, hide: () => {} };
spyOn(this.dropdown, 'show');
@@ -577,7 +544,7 @@ describe('DropDown', function () {
describe('if .hidden is false', function () {
beforeEach(function () {
- this.hidden = false
+ this.hidden = false;
this.dropdown = { hidden: this.hidden, show: () => {}, hide: () => {} };
spyOn(this.dropdown, 'show');
diff --git a/spec/javascripts/droplab/hook_spec.js b/spec/javascripts/droplab/hook_spec.js
index 8ebdcdd1404..75bf5f3d611 100644
--- a/spec/javascripts/droplab/hook_spec.js
+++ b/spec/javascripts/droplab/hook_spec.js
@@ -1,5 +1,3 @@
-/* eslint-disable */
-
import Hook from '~/droplab/hook';
import * as dropdownSrc from '~/droplab/drop_down';
@@ -73,10 +71,4 @@ describe('Hook', function () {
});
});
});
-
- describe('addEvents', function () {
- it('should exist', function () {
- expect(Hook.prototype.hasOwnProperty('addEvents')).toBe(true);
- });
- });
});
diff --git a/spec/javascripts/lib/utils/poll_spec.js b/spec/javascripts/lib/utils/poll_spec.js
index 918b6d32c43..22f30191ab9 100644
--- a/spec/javascripts/lib/utils/poll_spec.js
+++ b/spec/javascripts/lib/utils/poll_spec.js
@@ -1,9 +1,5 @@
-import Vue from 'vue';
-import VueResource from 'vue-resource';
import Poll from '~/lib/utils/poll';
-Vue.use(VueResource);
-
const waitForAllCallsToFinish = (service, waitForCount, successCallback) => {
const timer = () => {
setTimeout(() => {
@@ -12,45 +8,33 @@ const waitForAllCallsToFinish = (service, waitForCount, successCallback) => {
} else {
timer();
}
- }, 5);
+ }, 0);
};
timer();
};
-class ServiceMock {
- constructor(endpoint) {
- this.service = Vue.resource(endpoint);
- }
+function mockServiceCall(service, response, shouldFail = false) {
+ const action = shouldFail ? Promise.reject : Promise.resolve;
+ const responseObject = response;
+
+ if (!responseObject.headers) responseObject.headers = {};
- fetch() {
- return this.service.get();
- }
+ service.fetch.and.callFake(action.bind(Promise, responseObject));
}
describe('Poll', () => {
- let callbacks;
- let service;
+ const service = jasmine.createSpyObj('service', ['fetch']);
+ const callbacks = jasmine.createSpyObj('callbacks', ['success', 'error']);
- beforeEach(() => {
- callbacks = {
- success: () => {},
- error: () => {},
- };
-
- service = new ServiceMock('endpoint');
-
- spyOn(callbacks, 'success');
- spyOn(callbacks, 'error');
- spyOn(service, 'fetch').and.callThrough();
+ afterEach(() => {
+ callbacks.success.calls.reset();
+ callbacks.error.calls.reset();
+ service.fetch.calls.reset();
});
it('calls the success callback when no header for interval is provided', (done) => {
- const successInterceptor = (request, next) => {
- next(request.respondWith(JSON.stringify([]), { status: 200 }));
- };
-
- Vue.http.interceptors.push(successInterceptor);
+ mockServiceCall(service, { status: 200 });
new Poll({
resource: service,
@@ -63,18 +47,12 @@ describe('Poll', () => {
expect(callbacks.success).toHaveBeenCalled();
expect(callbacks.error).not.toHaveBeenCalled();
- Vue.http.interceptors = _.without(Vue.http.interceptors, successInterceptor);
-
done();
- }, 0);
+ });
});
it('calls the error callback whe the http request returns an error', (done) => {
- const errorInterceptor = (request, next) => {
- next(request.respondWith(JSON.stringify([]), { status: 500 }));
- };
-
- Vue.http.interceptors.push(errorInterceptor);
+ mockServiceCall(service, { status: 500 }, true);
new Poll({
resource: service,
@@ -86,42 +64,29 @@ describe('Poll', () => {
waitForAllCallsToFinish(service, 1, () => {
expect(callbacks.success).not.toHaveBeenCalled();
expect(callbacks.error).toHaveBeenCalled();
- Vue.http.interceptors = _.without(Vue.http.interceptors, errorInterceptor);
done();
});
});
it('should call the success callback when the interval header is -1', (done) => {
- const intervalInterceptor = (request, next) => {
- next(request.respondWith(JSON.stringify([]), { status: 200, headers: { 'poll-interval': -1 } }));
- };
-
- Vue.http.interceptors.push(intervalInterceptor);
+ mockServiceCall(service, { status: 200, headers: { 'poll-interval': -1 } });
new Poll({
resource: service,
method: 'fetch',
successCallback: callbacks.success,
errorCallback: callbacks.error,
- }).makeRequest();
-
- setTimeout(() => {
+ }).makeRequest().then(() => {
expect(callbacks.success).toHaveBeenCalled();
expect(callbacks.error).not.toHaveBeenCalled();
- Vue.http.interceptors = _.without(Vue.http.interceptors, intervalInterceptor);
-
done();
- }, 0);
+ }).catch(done.fail);
});
it('starts polling when http status is 200 and interval header is provided', (done) => {
- const pollInterceptor = (request, next) => {
- next(request.respondWith(JSON.stringify([]), { status: 200, headers: { 'poll-interval': 2 } }));
- };
-
- Vue.http.interceptors.push(pollInterceptor);
+ mockServiceCall(service, { status: 200, headers: { 'poll-interval': 1 } });
const Polling = new Poll({
resource: service,
@@ -141,19 +106,13 @@ describe('Poll', () => {
expect(callbacks.success).toHaveBeenCalled();
expect(callbacks.error).not.toHaveBeenCalled();
- Vue.http.interceptors = _.without(Vue.http.interceptors, pollInterceptor);
-
done();
});
});
describe('stop', () => {
it('stops polling when method is called', (done) => {
- const pollInterceptor = (request, next) => {
- next(request.respondWith(JSON.stringify([]), { status: 200, headers: { 'poll-interval': 2 } }));
- };
-
- Vue.http.interceptors.push(pollInterceptor);
+ mockServiceCall(service, { status: 200, headers: { 'poll-interval': 1 } });
const Polling = new Poll({
resource: service,
@@ -174,8 +133,6 @@ describe('Poll', () => {
expect(service.fetch).toHaveBeenCalledWith({ page: 1 });
expect(Polling.stop).toHaveBeenCalled();
- Vue.http.interceptors = _.without(Vue.http.interceptors, pollInterceptor);
-
done();
});
});
@@ -183,11 +140,7 @@ describe('Poll', () => {
describe('restart', () => {
it('should restart polling when its called', (done) => {
- const pollInterceptor = (request, next) => {
- next(request.respondWith(JSON.stringify([]), { status: 200, headers: { 'poll-interval': 2 } }));
- };
-
- Vue.http.interceptors.push(pollInterceptor);
+ mockServiceCall(service, { status: 200, headers: { 'poll-interval': 1 } });
const Polling = new Poll({
resource: service,
@@ -215,8 +168,6 @@ describe('Poll', () => {
expect(Polling.stop).toHaveBeenCalled();
expect(Polling.restart).toHaveBeenCalled();
- Vue.http.interceptors = _.without(Vue.http.interceptors, pollInterceptor);
-
done();
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js
index 2f971b39d16..d4b200875df 100644
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js
@@ -1,7 +1,7 @@
import Vue from 'vue';
import deploymentComponent from '~/vue_merge_request_widget/components/mr_widget_deployment';
import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service';
-import { statusClassToSvgMap } from '~/vue_shared/pipeline_svg_icons';
+import { statusIconEntityMap } from '~/vue_shared/ci_status_icons';
const deploymentMockData = [
{
@@ -46,7 +46,7 @@ describe('MRWidgetDeployment', () => {
describe('svg', () => {
it('should have the proper SVG icon', () => {
const vm = createComponent(deploymentMockData);
- expect(vm.svg).toEqual(statusClassToSvgMap.icon_status_success);
+ expect(vm.svg).toEqual(statusIconEntityMap.icon_status_success);
});
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js
index 48f816c8460..7f3eea7d2e5 100644
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js
@@ -48,10 +48,12 @@ describe('MRWidgetHeader', () => {
describe('template', () => {
let vm;
let el;
+ const sourceBranchPath = '/foo/bar/mr-widget-refactor';
const mr = {
divergedCommitsCount: 12,
sourceBranch: 'mr-widget-refactor',
- sourceBranchLink: '/foo/bar/mr-widget-refactor',
+ sourceBranchLink: `<a href="${sourceBranchPath}">mr-widget-refactor</a>`,
+ targetBranchPath: 'foo/bar/commits-path',
targetBranch: 'master',
isOpen: true,
emailPatchesPath: '/mr/email-patches',
@@ -65,8 +67,13 @@ describe('MRWidgetHeader', () => {
it('should render template elements correctly', () => {
expect(el.classList.contains('mr-source-target')).toBeTruthy();
- expect(el.querySelectorAll('.label-branch')[0].textContent).toContain(mr.sourceBranch);
- expect(el.querySelectorAll('.label-branch')[1].textContent).toContain(mr.targetBranch);
+ const sourceBranchLink = el.querySelectorAll('.label-branch')[0];
+ const targetBranchLink = el.querySelectorAll('.label-branch')[1];
+
+ expect(sourceBranchLink.textContent).toContain(mr.sourceBranch);
+ expect(targetBranchLink.textContent).toContain(mr.targetBranch);
+ expect(sourceBranchLink.querySelector('a').getAttribute('href')).toEqual(sourceBranchPath);
+ expect(targetBranchLink.querySelector('a').getAttribute('href')).toEqual(mr.targetBranchPath);
expect(el.querySelector('.diverged-commits-count').textContent).toContain('12 commits behind');
expect(el.textContent).toContain('Check out branch');
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js
index 1b418c7dfcf..647b59520f8 100644
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import { statusClassToSvgMap } from '~/vue_shared/pipeline_svg_icons';
+import { statusIconEntityMap } from '~/vue_shared/ci_status_icons';
import pipelineComponent from '~/vue_merge_request_widget/components/mr_widget_pipeline';
import mockData from '../mock_data';
@@ -24,7 +24,7 @@ describe('MRWidgetPipeline', () => {
describe('components', () => {
it('should have components added', () => {
expect(pipelineComponent.components['pipeline-stage']).toBeDefined();
- expect(pipelineComponent.components['pipeline-status-icon']).toBeDefined();
+ expect(pipelineComponent.components.ciIcon).toBeDefined();
});
});
@@ -33,7 +33,7 @@ describe('MRWidgetPipeline', () => {
it('should have the proper SVG icon', () => {
const vm = createComponent({ pipeline: mockData.pipeline });
- expect(vm.svg).toEqual(statusClassToSvgMap.icon_status_failed);
+ expect(vm.svg).toEqual(statusIconEntityMap.icon_status_failed);
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_closed_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_closed_spec.js
index 78a70725e94..47303d1e80f 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_closed_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_closed_spec.js
@@ -3,7 +3,7 @@ import closedComponent from '~/vue_merge_request_widget/components/states/mr_wid
const mr = {
targetBranch: 'good-branch',
- targetBranchCommitsPath: '/good-branch',
+ targetBranchPath: '/good-branch',
closedBy: {
name: 'Fatih Acet',
username: 'fatihacet',
@@ -44,7 +44,7 @@ describe('MRWidgetClosed', () => {
expect(el.querySelector('h4').textContent).toContain('Closed by');
expect(el.querySelector('h4').textContent).toContain(mr.closedBy.name);
expect(el.textContent).toContain('The changes were not merged into');
- expect(el.querySelector('.label-branch').getAttribute('href')).toEqual(mr.targetBranchCommitsPath);
+ expect(el.querySelector('.label-branch').getAttribute('href')).toEqual(mr.targetBranchPath);
expect(el.querySelector('.label-branch').textContent).toContain(mr.targetBranch);
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_sha_mismatch_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_sha_mismatch_spec.js
new file mode 100644
index 00000000000..5fb1d69a8b3
--- /dev/null
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_sha_mismatch_spec.js
@@ -0,0 +1,16 @@
+import Vue from 'vue';
+import shaMismatchComponent from '~/vue_merge_request_widget/components/states/mr_widget_sha_mismatch';
+
+describe('MRWidgetSHAMismatch', () => {
+ describe('template', () => {
+ const Component = Vue.extend(shaMismatchComponent);
+ const vm = new Component({
+ el: document.createElement('div'),
+ });
+ it('should have correct elements', () => {
+ expect(vm.$el.classList.contains('mr-widget-body')).toBeTruthy();
+ expect(vm.$el.querySelector('button').getAttribute('disabled')).toBeTruthy();
+ expect(vm.$el.innerText).toContain('The source branch HEAD has recently changed. Please reload the page and review the changes before merging.');
+ });
+ });
+});
diff --git a/spec/javascripts/vue_mr_widget/stores/get_state_key_spec.js b/spec/javascripts/vue_mr_widget/stores/get_state_key_spec.js
index ee944f4d4e5..9a331d99865 100644
--- a/spec/javascripts/vue_mr_widget/stores/get_state_key_spec.js
+++ b/spec/javascripts/vue_mr_widget/stores/get_state_key_spec.js
@@ -25,6 +25,9 @@ describe('getStateKey', () => {
context.canBeMerged = true;
expect(bound()).toEqual('readyToMerge');
+ context.hasSHAChanged = true;
+ expect(bound()).toEqual('shaMismatch');
+
context.isPipelineBlocked = true;
expect(bound()).toEqual('pipelineBlocked');
diff --git a/spec/javascripts/vue_mr_widget/stores/mr_widget_store_spec.js b/spec/javascripts/vue_mr_widget/stores/mr_widget_store_spec.js
new file mode 100644
index 00000000000..56dd0198ae2
--- /dev/null
+++ b/spec/javascripts/vue_mr_widget/stores/mr_widget_store_spec.js
@@ -0,0 +1,22 @@
+import MergeRequestStore from '~/vue_merge_request_widget/stores/mr_widget_store';
+import mockData from '../mock_data';
+
+describe('MergeRequestStore', () => {
+ describe('setData', () => {
+ let store;
+
+ beforeEach(() => {
+ store = new MergeRequestStore(mockData);
+ });
+
+ it('should set hasSHAChanged when the diff SHA changes', () => {
+ store.setData({ ...mockData, diff_head_sha: 'a-different-string' });
+ expect(store.hasSHAChanged).toBe(true);
+ });
+
+ it('should not set hasSHAChanged when other data changes', () => {
+ store.setData({ ...mockData, work_in_progress: !mockData.work_in_progress });
+ expect(store.hasSHAChanged).toBe(false);
+ });
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/ci_badge_link_spec.js b/spec/javascripts/vue_shared/components/ci_badge_link_spec.js
new file mode 100644
index 00000000000..daed4da3e15
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/ci_badge_link_spec.js
@@ -0,0 +1,89 @@
+import Vue from 'vue';
+import ciBadge from '~/vue_shared/components/ci_badge_link.vue';
+
+describe('CI Badge Link Component', () => {
+ let CIBadge;
+
+ const statuses = {
+ canceled: {
+ text: 'canceled',
+ label: 'canceled',
+ group: 'canceled',
+ icon: 'icon_status_canceled',
+ details_path: 'status/canceled',
+ },
+ created: {
+ text: 'created',
+ label: 'created',
+ group: 'created',
+ icon: 'icon_status_created',
+ details_path: 'status/created',
+ },
+ failed: {
+ text: 'failed',
+ label: 'failed',
+ group: 'failed',
+ icon: 'icon_status_failed',
+ details_path: 'status/failed',
+ },
+ manual: {
+ text: 'manual',
+ label: 'manual action',
+ group: 'manual',
+ icon: 'icon_status_manual',
+ details_path: 'status/manual',
+ },
+ pending: {
+ text: 'pending',
+ label: 'pending',
+ group: 'pending',
+ icon: 'icon_status_pending',
+ details_path: 'status/pending',
+ },
+ running: {
+ text: 'running',
+ label: 'running',
+ group: 'running',
+ icon: 'icon_status_running',
+ details_path: 'status/running',
+ },
+ skipped: {
+ text: 'skipped',
+ label: 'skipped',
+ group: 'skipped',
+ icon: 'icon_status_skipped',
+ details_path: 'status/skipped',
+ },
+ success_warining: {
+ text: 'passed',
+ label: 'passed',
+ group: 'success_with_warnings',
+ icon: 'icon_status_warning',
+ details_path: 'status/warning',
+ },
+ success: {
+ text: 'passed',
+ label: 'passed',
+ group: 'passed',
+ icon: 'icon_status_success',
+ details_path: 'status/passed',
+ },
+ };
+
+ it('should render each status badge', () => {
+ CIBadge = Vue.extend(ciBadge);
+ Object.keys(statuses).map((status) => {
+ const vm = new CIBadge({
+ propsData: {
+ status: statuses[status],
+ },
+ }).$mount();
+
+ expect(vm.$el.getAttribute('href')).toEqual(statuses[status].details_path);
+ expect(vm.$el.textContent.trim()).toEqual(statuses[status].text);
+ expect(vm.$el.getAttribute('class')).toEqual(`ci-status ci-${statuses[status].group}`);
+ expect(vm.$el.querySelector('svg')).toBeDefined();
+ return vm;
+ });
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/ci_icon_spec.js b/spec/javascripts/vue_shared/components/ci_icon_spec.js
index 98dc6caa622..d8664408595 100644
--- a/spec/javascripts/vue_shared/components/ci_icon_spec.js
+++ b/spec/javascripts/vue_shared/components/ci_icon_spec.js
@@ -25,6 +25,7 @@ describe('CI Icon component', () => {
propsData: {
status: {
icon: 'icon_status_success',
+ group: 'success',
},
},
}).$mount();
@@ -37,6 +38,7 @@ describe('CI Icon component', () => {
propsData: {
status: {
icon: 'icon_status_failed',
+ group: 'failed',
},
},
}).$mount();
@@ -49,6 +51,7 @@ describe('CI Icon component', () => {
propsData: {
status: {
icon: 'icon_status_warning',
+ group: 'warning',
},
},
}).$mount();
@@ -61,6 +64,7 @@ describe('CI Icon component', () => {
propsData: {
status: {
icon: 'icon_status_pending',
+ group: 'pending',
},
},
}).$mount();
@@ -73,6 +77,7 @@ describe('CI Icon component', () => {
propsData: {
status: {
icon: 'icon_status_running',
+ group: 'running',
},
},
}).$mount();
@@ -85,6 +90,7 @@ describe('CI Icon component', () => {
propsData: {
status: {
icon: 'icon_status_created',
+ group: 'created',
},
},
}).$mount();
@@ -97,6 +103,7 @@ describe('CI Icon component', () => {
propsData: {
status: {
icon: 'icon_status_skipped',
+ group: 'skipped',
},
},
}).$mount();
@@ -109,6 +116,7 @@ describe('CI Icon component', () => {
propsData: {
status: {
icon: 'icon_status_canceled',
+ group: 'canceled',
},
},
}).$mount();
@@ -121,6 +129,7 @@ describe('CI Icon component', () => {
propsData: {
status: {
icon: 'icon_status_manual',
+ group: 'manual',
},
},
}).$mount();
diff --git a/spec/javascripts/vue_shared/components/commit_spec.js b/spec/javascripts/vue_shared/components/commit_spec.js
index df547299d75..242010ba688 100644
--- a/spec/javascripts/vue_shared/components/commit_spec.js
+++ b/spec/javascripts/vue_shared/components/commit_spec.js
@@ -61,16 +61,16 @@ describe('Commit component', () => {
});
it('should render a link to the ref url', () => {
- expect(component.$el.querySelector('.branch-name').getAttribute('href')).toEqual(props.commitRef.ref_url);
+ expect(component.$el.querySelector('.ref-name').getAttribute('href')).toEqual(props.commitRef.ref_url);
});
it('should render the ref name', () => {
- expect(component.$el.querySelector('.branch-name').textContent).toContain(props.commitRef.name);
+ expect(component.$el.querySelector('.ref-name').textContent).toContain(props.commitRef.name);
});
it('should render the commit short sha with a link to the commit url', () => {
- expect(component.$el.querySelector('.commit-id').getAttribute('href')).toEqual(props.commitUrl);
- expect(component.$el.querySelector('.commit-id').textContent).toContain(props.shortSha);
+ expect(component.$el.querySelector('.commit-sha').getAttribute('href')).toEqual(props.commitUrl);
+ expect(component.$el.querySelector('.commit-sha').textContent).toContain(props.shortSha);
});
it('should render the given commitIconSvg', () => {
diff --git a/spec/javascripts/vue_shared/components/pipelines_table_row_spec.js b/spec/javascripts/vue_shared/components/pipelines_table_row_spec.js
index 0a2c66e72d7..14280751053 100644
--- a/spec/javascripts/vue_shared/components/pipelines_table_row_spec.js
+++ b/spec/javascripts/vue_shared/components/pipelines_table_row_spec.js
@@ -89,7 +89,7 @@ describe('Pipelines Table Row', () => {
it('should render link to commit', () => {
component = buildComponent(pipeline);
- const commitLink = component.$el.querySelector('.branch-commit .commit-id');
+ const commitLink = component.$el.querySelector('.branch-commit .commit-sha');
expect(commitLink.getAttribute('href')).toEqual(pipeline.commit.commit_path);
});
diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb
index a25c5da488a..ec444942804 100644
--- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb
+++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb
@@ -23,6 +23,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces do
found_ids = subject.namespaces_for_paths(type: :child).
map(&:id)
+
expect(found_ids).to contain_exactly(child.id)
end
end
@@ -39,6 +40,22 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces do
found_ids = subject.namespaces_for_paths(type: :child).
map(&:id)
+
+ expect(found_ids).to contain_exactly(namespace.id)
+ end
+
+ it 'has no namespaces that look the same' do
+ _root_namespace = create(:namespace, path: 'THE-path')
+ _similar_path = create(:namespace,
+ path: 'not-really-the-path',
+ parent: create(:namespace))
+ namespace = create(:namespace,
+ path: 'the-path',
+ parent: create(:namespace))
+
+ found_ids = subject.namespaces_for_paths(type: :child).
+ map(&:id)
+
expect(found_ids).to contain_exactly(namespace.id)
end
end
@@ -53,6 +70,20 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces do
found_ids = subject.namespaces_for_paths(type: :top_level).
map(&:id)
+
+ expect(found_ids).to contain_exactly(root_namespace.id)
+ end
+
+ it 'has no namespaces that just look the same' do
+ root_namespace = create(:namespace, path: 'the-path')
+ _similar_path = create(:namespace, path: 'not-really-the-path')
+ _child_namespace = create(:namespace,
+ path: 'the-path',
+ parent: create(:namespace))
+
+ found_ids = subject.namespaces_for_paths(type: :top_level).
+ map(&:id)
+
expect(found_ids).to contain_exactly(root_namespace.id)
end
end
diff --git a/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb b/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb
new file mode 100644
index 00000000000..2e52097a946
--- /dev/null
+++ b/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb
@@ -0,0 +1,60 @@
+require 'rails_helper'
+
+describe Gitlab::DependencyLinker::GemfileLinker, lib: true do
+ describe '.support?' do
+ it 'supports Gemfile' do
+ expect(described_class.support?('Gemfile')).to be_truthy
+ end
+
+ it 'supports gems.rb' do
+ expect(described_class.support?('gems.rb')).to be_truthy
+ end
+
+ it 'does not support other files' do
+ expect(described_class.support?('Gemfile.lock')).to be_falsey
+ end
+ end
+
+ describe '#link' do
+ let(:file_name) { 'Gemfile' }
+
+ let(:file_content) do
+ <<-CONTENT.strip_heredoc
+ source 'https://rubygems.org'
+
+ gem "rails", '4.2.6', github: "rails/rails"
+ gem 'rails-deprecated_sanitizer', '~> 1.0.3'
+ gem 'responders', '~> 2.0', :github => 'rails/responders'
+ gem 'sprockets', '~> 3.6.0', git: 'https://gitlab.example.com/gems/sprockets'
+ gem 'default_value_for', '~> 3.0.0'
+ CONTENT
+ end
+
+ subject { Gitlab::Highlight.highlight(file_name, file_content) }
+
+ def link(name, url)
+ %{<a href="#{url}" rel="noopener noreferrer" target="_blank">#{name}</a>}
+ end
+
+ it 'links sources' do
+ expect(subject).to include(link('https://rubygems.org', 'https://rubygems.org'))
+ end
+
+ it 'links dependencies' do
+ expect(subject).to include(link('rails', 'https://rubygems.org/gems/rails'))
+ expect(subject).to include(link('rails-deprecated_sanitizer', 'https://rubygems.org/gems/rails-deprecated_sanitizer'))
+ expect(subject).to include(link('responders', 'https://rubygems.org/gems/responders'))
+ expect(subject).to include(link('sprockets', 'https://rubygems.org/gems/sprockets'))
+ expect(subject).to include(link('default_value_for', 'https://rubygems.org/gems/default_value_for'))
+ end
+
+ it 'links GitHub repos' do
+ expect(subject).to include(link('rails/rails', 'https://github.com/rails/rails'))
+ expect(subject).to include(link('rails/responders', 'https://github.com/rails/responders'))
+ end
+
+ it 'links Git repos' do
+ expect(subject).to include(link('https://gitlab.example.com/gems/sprockets', 'https://gitlab.example.com/gems/sprockets'))
+ end
+ end
+end
diff --git a/spec/lib/gitlab/dependency_linker_spec.rb b/spec/lib/gitlab/dependency_linker_spec.rb
new file mode 100644
index 00000000000..03d5b61d70c
--- /dev/null
+++ b/spec/lib/gitlab/dependency_linker_spec.rb
@@ -0,0 +1,13 @@
+require 'rails_helper'
+
+describe Gitlab::DependencyLinker, lib: true do
+ describe '.link' do
+ it 'links using GemfileLinker' do
+ blob_name = 'Gemfile'
+
+ expect(described_class::GemfileLinker).to receive(:link)
+
+ described_class.link(blob_name, nil, nil)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/diff/highlight_spec.rb b/spec/lib/gitlab/diff/highlight_spec.rb
index c6bd4e81f4f..7d7d4a55e63 100644
--- a/spec/lib/gitlab/diff/highlight_spec.rb
+++ b/spec/lib/gitlab/diff/highlight_spec.rb
@@ -34,7 +34,7 @@ describe Gitlab::Diff::Highlight, lib: true do
end
it 'highlights and marks added lines' do
- code = %Q{+<span id="LC9" class="line" lang="ruby"> <span class="k">raise</span> <span class="no"><span class='idiff left'>RuntimeError</span></span><span class="p"><span class='idiff'>,</span></span><span class='idiff right'> </span><span class="s2">"System commands must be given as an array of strings"</span></span>\n}
+ code = %Q{+<span id="LC9" class="line" lang="ruby"> <span class="k">raise</span> <span class="no"><span class="idiff left">RuntimeError</span></span><span class="p"><span class="idiff">,</span></span><span class="idiff right"> </span><span class="s2">"System commands must be given as an array of strings"</span></span>\n}
expect(subject[5].text).to eq(code)
end
@@ -67,7 +67,7 @@ describe Gitlab::Diff::Highlight, lib: true do
end
it 'marks added lines' do
- code = %q{+ raise <span class='idiff left right'>RuntimeError, </span>&quot;System commands must be given as an array of strings&quot;}
+ code = %q{+ raise <span class="idiff left right">RuntimeError, </span>&quot;System commands must be given as an array of strings&quot;}
expect(subject[5].text).to eq(code)
expect(subject[5].text).to be_html_safe
diff --git a/spec/lib/gitlab/diff/inline_diff_markdown_marker_spec.rb b/spec/lib/gitlab/diff/inline_diff_markdown_marker_spec.rb
new file mode 100644
index 00000000000..d6e8b8ac4b2
--- /dev/null
+++ b/spec/lib/gitlab/diff/inline_diff_markdown_marker_spec.rb
@@ -0,0 +1,14 @@
+require 'spec_helper'
+
+describe Gitlab::Diff::InlineDiffMarkdownMarker, lib: true do
+ describe '#mark' do
+ let(:raw) { "abc 'def'" }
+ let(:inline_diffs) { [2..5] }
+ let(:subject) { described_class.new(raw).mark(inline_diffs, mode: :deletion) }
+
+ it 'marks the range' do
+ expect(subject).to eq("ab{-c &#39;d-}ef&#39;")
+ expect(subject).to be_html_safe
+ end
+ end
+end
diff --git a/spec/lib/gitlab/diff/inline_diff_marker_spec.rb b/spec/lib/gitlab/diff/inline_diff_marker_spec.rb
index 198ff977f24..95da344802d 100644
--- a/spec/lib/gitlab/diff/inline_diff_marker_spec.rb
+++ b/spec/lib/gitlab/diff/inline_diff_marker_spec.rb
@@ -1,26 +1,26 @@
require 'spec_helper'
describe Gitlab::Diff::InlineDiffMarker, lib: true do
- describe '#inline_diffs' do
+ describe '#mark' do
context "when the rich text is html safe" do
- let(:raw) { "abc 'def'" }
+ let(:raw) { "abc 'def'" }
let(:rich) { %{<span class="abc">abc</span><span class="space"> </span><span class="def">&#39;def&#39;</span>}.html_safe }
let(:inline_diffs) { [2..5] }
- let(:subject) { Gitlab::Diff::InlineDiffMarker.new(raw, rich).mark(inline_diffs) }
+ let(:subject) { described_class.new(raw, rich).mark(inline_diffs) }
- it 'marks the inline diffs' do
- expect(subject).to eq(%{<span class="abc">ab<span class='idiff left'>c</span></span><span class="space"><span class='idiff'> </span></span><span class="def"><span class='idiff right'>&#39;d</span>ef&#39;</span>})
+ it 'marks the range' do
+ expect(subject).to eq(%{<span class="abc">ab<span class="idiff left">c</span></span><span class="space"><span class="idiff"> </span></span><span class="def"><span class="idiff right">&#39;d</span>ef&#39;</span>})
expect(subject).to be_html_safe
end
end
context "when the text text is not html safe" do
- let(:raw) { "abc 'def'" }
+ let(:raw) { "abc 'def'" }
let(:inline_diffs) { [2..5] }
- let(:subject) { Gitlab::Diff::InlineDiffMarker.new(raw).mark(inline_diffs) }
+ let(:subject) { described_class.new(raw).mark(inline_diffs) }
- it 'marks the inline diffs' do
- expect(subject).to eq(%{ab<span class='idiff left right'>c &#39;d</span>ef&#39;})
+ it 'marks the range' do
+ expect(subject).to eq(%{ab<span class="idiff left right">c &#39;d</span>ef&#39;})
expect(subject).to be_html_safe
end
end
diff --git a/spec/lib/gitlab/file_finder_spec.rb b/spec/lib/gitlab/file_finder_spec.rb
new file mode 100644
index 00000000000..5a32ffd462c
--- /dev/null
+++ b/spec/lib/gitlab/file_finder_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+describe Gitlab::FileFinder, lib: true do
+ describe '#find' do
+ let(:project) { create(:project, :public, :repository) }
+ let(:finder) { described_class.new(project, project.default_branch) }
+
+ it 'finds by name' do
+ results = finder.find('files')
+ expect(results.map(&:first)).to include('files/images/wm.svg')
+ end
+
+ it 'finds by content' do
+ results = finder.find('files')
+
+ blob = results.select { |result| result.first == "CHANGELOG" }.flatten.last
+
+ expect(blob.filename).to eq("CHANGELOG")
+ end
+ end
+end
diff --git a/spec/lib/gitlab/highlight_spec.rb b/spec/lib/gitlab/highlight_spec.rb
index e49799ad105..e57b3053871 100644
--- a/spec/lib/gitlab/highlight_spec.rb
+++ b/spec/lib/gitlab/highlight_spec.rb
@@ -57,4 +57,15 @@ describe Gitlab::Highlight, lib: true do
end
end
end
+
+ describe '#highlight' do
+ subject { described_class.highlight(file_name, file_content, nowrap: false) }
+
+ it 'links dependencies via DependencyLinker' do
+ expect(Gitlab::DependencyLinker).to receive(:link).
+ with('file.name', 'Contents', anything).and_call_original
+
+ described_class.highlight('file.name', 'Contents')
+ end
+ end
end
diff --git a/spec/lib/gitlab/project_search_results_spec.rb b/spec/lib/gitlab/project_search_results_spec.rb
index 6e0b1192706..1b8690ba613 100644
--- a/spec/lib/gitlab/project_search_results_spec.rb
+++ b/spec/lib/gitlab/project_search_results_spec.rb
@@ -55,7 +55,7 @@ describe Gitlab::ProjectSearchResults, lib: true do
end
it 'finds by name' do
- expect(results).to include(["files/images/wm.svg", nil])
+ expect(results.map(&:first)).to include('files/images/wm.svg')
end
it 'finds by content' do
diff --git a/spec/lib/gitlab/prometheus/queries/deployment_query_spec.rb b/spec/lib/gitlab/prometheus/queries/deployment_query_spec.rb
new file mode 100644
index 00000000000..cc7d7e57f06
--- /dev/null
+++ b/spec/lib/gitlab/prometheus/queries/deployment_query_spec.rb
@@ -0,0 +1,36 @@
+require 'spec_helper'
+
+describe Gitlab::Prometheus::Queries::DeploymentQuery, lib: true do
+ let(:environment) { create(:environment, slug: 'environment-slug') }
+ let(:deployment) { create(:deployment, environment: environment) }
+
+ let(:client) { double('prometheus_client') }
+ subject { described_class.new(client) }
+
+ around do |example|
+ Timecop.freeze { example.run }
+ end
+
+ it 'sends appropriate queries to prometheus' do
+ start_time_matcher = be_within(0.5).of((deployment.created_at - 30.minutes).to_f)
+ stop_time_matcher = be_within(0.5).of((deployment.created_at + 30.minutes).to_f)
+ created_at_matcher = be_within(0.5).of(deployment.created_at.to_f)
+
+ expect(client).to receive(:query_range).with('avg(container_memory_usage_bytes{container_name!="POD",environment="environment-slug"}) / 2^20',
+ start: start_time_matcher, stop: stop_time_matcher)
+ expect(client).to receive(:query).with('avg(avg_over_time(container_memory_usage_bytes{container_name!="POD",environment="environment-slug"}[30m]))',
+ time: created_at_matcher)
+ expect(client).to receive(:query).with('avg(avg_over_time(container_memory_usage_bytes{container_name!="POD",environment="environment-slug"}[30m]))',
+ time: stop_time_matcher)
+
+ expect(client).to receive(:query_range).with('avg(rate(container_cpu_usage_seconds_total{container_name!="POD",environment="environment-slug"}[2m])) * 100',
+ start: start_time_matcher, stop: stop_time_matcher)
+ expect(client).to receive(:query).with('avg(rate(container_cpu_usage_seconds_total{container_name!="POD",environment="environment-slug"}[30m])) * 100',
+ time: created_at_matcher)
+ expect(client).to receive(:query).with('avg(rate(container_cpu_usage_seconds_total{container_name!="POD",environment="environment-slug"}[30m])) * 100',
+ time: stop_time_matcher)
+
+ expect(subject.query(deployment.id)).to eq(memory_values: nil, memory_before: nil, memory_after: nil,
+ cpu_values: nil, cpu_before: nil, cpu_after: nil)
+ end
+end
diff --git a/spec/lib/gitlab/prometheus_spec.rb b/spec/lib/gitlab/prometheus_client_spec.rb
index 9d67e3d2f37..2d8bd2f6b97 100644
--- a/spec/lib/gitlab/prometheus_spec.rb
+++ b/spec/lib/gitlab/prometheus_client_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Prometheus, lib: true do
+describe Gitlab::PrometheusClient, lib: true do
include PrometheusHelpers
subject { described_class.new(api_url: 'https://prometheus.example.com') }
diff --git a/spec/lib/gitlab/string_range_marker_spec.rb b/spec/lib/gitlab/string_range_marker_spec.rb
new file mode 100644
index 00000000000..7c77772b3f6
--- /dev/null
+++ b/spec/lib/gitlab/string_range_marker_spec.rb
@@ -0,0 +1,36 @@
+require 'spec_helper'
+
+describe Gitlab::StringRangeMarker, lib: true do
+ describe '#mark' do
+ context "when the rich text is html safe" do
+ let(:raw) { "abc <def>" }
+ let(:rich) { %{<span class="abc">abc</span><span class="space"> </span><span class="def">&lt;def&gt;</span>}.html_safe }
+ let(:inline_diffs) { [2..5] }
+ subject do
+ described_class.new(raw, rich).mark(inline_diffs) do |text, left:, right:|
+ "LEFT#{text}RIGHT"
+ end
+ end
+
+ it 'marks the inline diffs' do
+ expect(subject).to eq(%{<span class="abc">abLEFTcRIGHT</span><span class="space">LEFT RIGHT</span><span class="def">LEFT&lt;dRIGHTef&gt;</span>})
+ expect(subject).to be_html_safe
+ end
+ end
+
+ context "when the rich text is not html safe" do
+ let(:raw) { "abc <def>" }
+ let(:inline_diffs) { [2..5] }
+ subject do
+ described_class.new(raw).mark(inline_diffs) do |text, left:, right:|
+ "LEFT#{text}RIGHT"
+ end
+ end
+
+ it 'marks the inline diffs' do
+ expect(subject).to eq(%{abLEFTc &lt;dRIGHTef&gt;})
+ expect(subject).to be_html_safe
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/string_regex_marker_spec.rb b/spec/lib/gitlab/string_regex_marker_spec.rb
new file mode 100644
index 00000000000..2f5cf6c6e3b
--- /dev/null
+++ b/spec/lib/gitlab/string_regex_marker_spec.rb
@@ -0,0 +1,18 @@
+require 'spec_helper'
+
+describe Gitlab::StringRegexMarker, lib: true do
+ describe '#mark' do
+ let(:raw) { %{"name": "AFNetworking"} }
+ let(:rich) { %{<span class="key">"name"</span><span class="punctuation">: </span><span class="value">"AFNetworking"</span>}.html_safe }
+ subject do
+ described_class.new(raw, rich).mark(/"[^"]+":\s*"(?<name>[^"]+)"/, group: :name) do |text, left:, right:|
+ %{<a href="#">#{text}</a>}
+ end
+ end
+
+ it 'marks the inline diffs' do
+ expect(subject).to eq(%{<span class="key">"name"</span><span class="punctuation">: </span><span class="value">"<a href="#">AFNetworking</a>"</span>})
+ expect(subject).to be_html_safe
+ end
+ end
+end
diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb
index 852889d4540..131fea8be43 100644
--- a/spec/models/commit_spec.rb
+++ b/spec/models/commit_spec.rb
@@ -395,24 +395,11 @@ eos
allow(Gitlab::GitalyClient).to receive(:feature_enabled?).with(:commit_raw_diffs).and_return(true)
end
- context 'when a truthy deltas_only is not passed to args' do
- it 'fetches diffs from Gitaly server' do
- expect(Gitlab::GitalyClient::Commit).to receive(:diff_from_parent).
- with(commit)
+ it 'fetches diffs from Gitaly server' do
+ expect(Gitlab::GitalyClient::Commit).to receive(:diff_from_parent).
+ with(commit)
- commit.raw_diffs
- end
- end
-
- context 'when a truthy deltas_only is passed to args' do
- it 'fetches diffs using Rugged' do
- opts = { deltas_only: true }
-
- expect(Gitlab::GitalyClient::Commit).not_to receive(:diff_from_parent)
- expect(commit.raw).to receive(:diffs).with(opts)
-
- commit.raw_diffs(opts)
- end
+ commit.raw_diffs
end
end
end
diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb
index 212fcd884a8..4bda7d4314a 100644
--- a/spec/models/deployment_spec.rb
+++ b/spec/models/deployment_spec.rb
@@ -52,7 +52,7 @@ describe Deployment, models: true do
describe '#metrics' do
let(:deployment) { create(:deployment) }
- subject { deployment.metrics(1.hour) }
+ subject { deployment.metrics }
context 'metrics are disabled' do
it { is_expected.to eq({}) }
@@ -63,16 +63,17 @@ describe Deployment, models: true do
{
success: true,
metrics: {},
- last_update: 42
+ last_update: 42,
+ deployment_time: 1494408956
}
end
before do
- allow(deployment.project).to receive_message_chain(:monitoring_service, :metrics)
+ allow(deployment.project).to receive_message_chain(:monitoring_service, :deployment_metrics)
.with(any_args).and_return(simple_metrics)
end
- it { is_expected.to eq(simple_metrics.merge(deployment_time: deployment.created_at.utc.to_i)) }
+ it { is_expected.to eq(simple_metrics) }
end
end
diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb
index edc1c204014..12519de8636 100644
--- a/spec/models/environment_spec.rb
+++ b/spec/models/environment_spec.rb
@@ -393,7 +393,7 @@ describe Environment, models: true do
it 'returns the metrics from the deployment service' do
expect(project.monitoring_service)
- .to receive(:metrics).with(environment)
+ .to receive(:environment_metrics).with(environment)
.and_return(:fake_metrics)
is_expected.to eq(:fake_metrics)
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index 725f5c2311f..bb4e70db2e9 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -38,46 +38,6 @@ describe Issue, models: true do
end
end
- describe "before_save" do
- describe "#update_cache_counts when an issue is reassigned" do
- let(:issue) { create(:issue) }
- let(:assignee) { create(:user) }
-
- context "when previous assignee exists" do
- before do
- issue.project.team << [assignee, :developer]
- issue.assignees << assignee
- end
-
- it "updates cache counts for new assignee" do
- user = create(:user)
-
- expect(user).to receive(:update_cache_counts)
-
- issue.assignees << user
- end
-
- it "updates cache counts for previous assignee" do
- issue.assignees.first
-
- expect_any_instance_of(User).to receive(:update_cache_counts)
-
- issue.assignees.destroy_all
- end
- end
-
- context "when previous assignee does not exist" do
- it "updates cache count for the new assignee" do
- issue.assignees = []
-
- expect_any_instance_of(User).to receive(:update_cache_counts)
-
- issue.assignees << assignee
- end
- end
- end
- end
-
describe '#card_attributes' do
it 'includes the author name' do
allow(subject).to receive(:author).and_return(double(name: 'Robert'))
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index ef349530761..e8124c4cbe9 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -87,48 +87,6 @@ describe MergeRequest, models: true do
end
end
- describe "before_save" do
- describe "#update_cache_counts when a merge request is reassigned" do
- let(:project) { create :project }
- let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
- let(:assignee) { create :user }
-
- context "when previous assignee exists" do
- before do
- project.team << [assignee, :developer]
- merge_request.update(assignee: assignee)
- end
-
- it "updates cache counts for new assignee" do
- user = create(:user)
-
- expect(user).to receive(:update_cache_counts)
-
- merge_request.update(assignee: user)
- end
-
- it "updates cache counts for previous assignee" do
- old_assignee = merge_request.assignee
- allow(User).to receive(:find_by_id).with(old_assignee.id).and_return(old_assignee)
-
- expect(old_assignee).to receive(:update_cache_counts)
-
- merge_request.update(assignee: nil)
- end
- end
-
- context "when previous assignee does not exist" do
- it "updates cache count for the new assignee" do
- merge_request.update(assignee: nil)
-
- expect_any_instance_of(User).to receive(:update_cache_counts)
-
- merge_request.update(assignee: assignee)
- end
- end
- end
- end
-
describe '#card_attributes' do
it 'includes the author name' do
allow(subject).to receive(:author).and_return(double(name: 'Robert'))
diff --git a/spec/models/project_services/chat_message/pipeline_message_spec.rb b/spec/models/project_services/chat_message/pipeline_message_spec.rb
index e005be42b0d..7d2599dc703 100644
--- a/spec/models/project_services/chat_message/pipeline_message_spec.rb
+++ b/spec/models/project_services/chat_message/pipeline_message_spec.rb
@@ -62,7 +62,7 @@ describe ChatMessage::PipelineMessage do
def build_message(status_text = status, name = user[:name])
"<http://example.gitlab.com|project_name>:" \
" Pipeline <http://example.gitlab.com/pipelines/123|#123>" \
- " of <http://example.gitlab.com/commits/develop|develop> branch" \
+ " of branch `<http://example.gitlab.com/commits/develop|develop>`" \
" by #{name} #{status_text} in 02:00:10"
end
end
@@ -81,7 +81,7 @@ describe ChatMessage::PipelineMessage do
expect(subject.pretext).to be_empty
expect(subject.attachments).to eq(message)
expect(subject.activity).to eq({
- title: 'Pipeline [#123](http://example.gitlab.com/pipelines/123) of [develop](http://example.gitlab.com/commits/develop) branch by hacker passed',
+ title: 'Pipeline [#123](http://example.gitlab.com/pipelines/123) of branch `[develop](http://example.gitlab.com/commits/develop)` by hacker passed',
subtitle: 'in [project_name](http://example.gitlab.com)',
text: 'in 02:00:10',
image: ''
@@ -98,7 +98,7 @@ describe ChatMessage::PipelineMessage do
expect(subject.pretext).to be_empty
expect(subject.attachments).to eq(message)
expect(subject.activity).to eq({
- title: 'Pipeline [#123](http://example.gitlab.com/pipelines/123) of [develop](http://example.gitlab.com/commits/develop) branch by hacker failed',
+ title: 'Pipeline [#123](http://example.gitlab.com/pipelines/123) of branch `[develop](http://example.gitlab.com/commits/develop)` by hacker failed',
subtitle: 'in [project_name](http://example.gitlab.com)',
text: 'in 02:00:10',
image: ''
@@ -113,7 +113,7 @@ describe ChatMessage::PipelineMessage do
expect(subject.pretext).to be_empty
expect(subject.attachments).to eq(message)
expect(subject.activity).to eq({
- title: 'Pipeline [#123](http://example.gitlab.com/pipelines/123) of [develop](http://example.gitlab.com/commits/develop) branch by API failed',
+ title: 'Pipeline [#123](http://example.gitlab.com/pipelines/123) of branch `[develop](http://example.gitlab.com/commits/develop)` by API failed',
subtitle: 'in [project_name](http://example.gitlab.com)',
text: 'in 02:00:10',
image: ''
@@ -125,8 +125,8 @@ describe ChatMessage::PipelineMessage do
def build_markdown_message(status_text = status, name = user[:name])
"[project_name](http://example.gitlab.com):" \
" Pipeline [#123](http://example.gitlab.com/pipelines/123)" \
- " of [develop](http://example.gitlab.com/commits/develop)" \
- " branch by #{name} #{status_text} in 02:00:10"
+ " of branch `[develop](http://example.gitlab.com/commits/develop)`" \
+ " by #{name} #{status_text} in 02:00:10"
end
end
end
diff --git a/spec/models/project_services/chat_message/push_message_spec.rb b/spec/models/project_services/chat_message/push_message_spec.rb
index c794f659c41..e38117b75f6 100644
--- a/spec/models/project_services/chat_message/push_message_spec.rb
+++ b/spec/models/project_services/chat_message/push_message_spec.rb
@@ -28,7 +28,7 @@ describe ChatMessage::PushMessage, models: true do
context 'without markdown' do
it 'returns a message regarding pushes' do
expect(subject.pretext).to eq(
- 'test.user pushed to branch <http://url.com/commits/master|master> of '\
+ 'test.user pushed to branch `<http://url.com/commits/master|master>` of '\
'<http://url.com|project_name> (<http://url.com/compare/before...after|Compare changes>)')
expect(subject.attachments).to eq([{
text: "<http://url1.com|abcdefgh>: message1 - author1\n\n"\
@@ -45,7 +45,7 @@ describe ChatMessage::PushMessage, models: true do
it 'returns a message regarding pushes' do
expect(subject.pretext).to eq(
- 'test.user pushed to branch [master](http://url.com/commits/master) of [project_name](http://url.com) ([Compare changes](http://url.com/compare/before...after))')
+ 'test.user pushed to branch `[master](http://url.com/commits/master)` of [project_name](http://url.com) ([Compare changes](http://url.com/compare/before...after))')
expect(subject.attachments).to eq(
"[abcdefgh](http://url1.com): message1 - author1\n\n[12345678](http://url2.com): message2 - author2")
expect(subject.activity).to eq({
@@ -74,7 +74,7 @@ describe ChatMessage::PushMessage, models: true do
context 'without markdown' do
it 'returns a message regarding pushes' do
expect(subject.pretext).to eq('test.user pushed new tag ' \
- '<http://url.com/commits/new_tag|new_tag> to ' \
+ '`<http://url.com/commits/new_tag|new_tag>` to ' \
'<http://url.com|project_name>')
expect(subject.attachments).to be_empty
end
@@ -87,7 +87,7 @@ describe ChatMessage::PushMessage, models: true do
it 'returns a message regarding pushes' do
expect(subject.pretext).to eq(
- 'test.user pushed new tag [new_tag](http://url.com/commits/new_tag) to [project_name](http://url.com)')
+ 'test.user pushed new tag `[new_tag](http://url.com/commits/new_tag)` to [project_name](http://url.com)')
expect(subject.attachments).to be_empty
expect(subject.activity).to eq({
title: 'test.user created tag',
@@ -107,7 +107,7 @@ describe ChatMessage::PushMessage, models: true do
context 'without markdown' do
it 'returns a message regarding a new branch' do
expect(subject.pretext).to eq(
- 'test.user pushed new branch <http://url.com/commits/master|master> to '\
+ 'test.user pushed new branch `<http://url.com/commits/master|master>` to '\
'<http://url.com|project_name>')
expect(subject.attachments).to be_empty
end
@@ -120,7 +120,7 @@ describe ChatMessage::PushMessage, models: true do
it 'returns a message regarding a new branch' do
expect(subject.pretext).to eq(
- 'test.user pushed new branch [master](http://url.com/commits/master) to [project_name](http://url.com)')
+ 'test.user pushed new branch `[master](http://url.com/commits/master)` to [project_name](http://url.com)')
expect(subject.attachments).to be_empty
expect(subject.activity).to eq({
title: 'test.user created branch',
@@ -140,7 +140,7 @@ describe ChatMessage::PushMessage, models: true do
context 'without markdown' do
it 'returns a message regarding a removed branch' do
expect(subject.pretext).to eq(
- 'test.user removed branch master from <http://url.com|project_name>')
+ 'test.user removed branch `master` from <http://url.com|project_name>')
expect(subject.attachments).to be_empty
end
end
@@ -152,7 +152,7 @@ describe ChatMessage::PushMessage, models: true do
it 'returns a message regarding a removed branch' do
expect(subject.pretext).to eq(
- 'test.user removed branch master from [project_name](http://url.com)')
+ 'test.user removed branch `master` from [project_name](http://url.com)')
expect(subject.attachments).to be_empty
expect(subject.activity).to eq({
title: 'test.user removed branch',
diff --git a/spec/models/project_services/prometheus_service_spec.rb b/spec/models/project_services/prometheus_service_spec.rb
index 82a3e2698c1..1f9d3c07b51 100644
--- a/spec/models/project_services/prometheus_service_spec.rb
+++ b/spec/models/project_services/prometheus_service_spec.rb
@@ -6,6 +6,7 @@ describe PrometheusService, models: true, caching: true do
let(:project) { create(:prometheus_project) }
let(:service) { project.prometheus_service }
+ let(:environment_query) { Gitlab::Prometheus::Queries::EnvironmentQuery }
describe "Associations" do
it { is_expected.to belong_to :project }
@@ -45,49 +46,56 @@ describe PrometheusService, models: true, caching: true do
end
end
- describe '#metrics' do
+ describe '#environment_metrics' do
let(:environment) { build_stubbed(:environment, slug: 'env-slug') }
around do |example|
Timecop.freeze { example.run }
end
- context 'with valid data without time range' do
- subject { service.metrics(environment) }
+ context 'with valid data' do
+ subject { service.environment_metrics(environment) }
before do
- stub_reactive_cache(service, prometheus_data, 'env-slug', nil, nil)
+ stub_reactive_cache(service, prometheus_data, environment_query, environment.id)
end
it 'returns reactive data' do
is_expected.to eq(prometheus_data)
end
end
+ end
+
+ describe '#deployment_metrics' do
+ let(:deployment) { build_stubbed(:deployment)}
+ let(:deployment_query) { Gitlab::Prometheus::Queries::DeploymentQuery }
+
+ around do |example|
+ Timecop.freeze { example.run }
+ end
- context 'with valid data with time range' do
- let(:t_start) { 1.hour.ago.utc }
- let(:t_end) { Time.now.utc }
- subject { service.metrics(environment, timeframe_start: t_start, timeframe_end: t_end) }
+ context 'with valid data' do
+ subject { service.deployment_metrics(deployment) }
before do
- stub_reactive_cache(service, prometheus_data, 'env-slug', t_start, t_end)
+ stub_reactive_cache(service, prometheus_data, deployment_query, deployment.id)
end
it 'returns reactive data' do
- is_expected.to eq(prometheus_data)
+ is_expected.to eq(prometheus_data.merge(deployment_time: deployment.created_at.to_i))
end
end
end
describe '#calculate_reactive_cache' do
- let(:environment) { build_stubbed(:environment, slug: 'env-slug') }
+ let(:environment) { create(:environment, slug: 'env-slug') }
around do |example|
Timecop.freeze { example.run }
end
subject do
- service.calculate_reactive_cache(environment.slug, nil, nil)
+ service.calculate_reactive_cache(environment_query.to_s, environment.id)
end
context 'when service is inactive' do
diff --git a/spec/models/protected_branch/merge_access_level_spec.rb b/spec/models/protected_branch/merge_access_level_spec.rb
new file mode 100644
index 00000000000..1e7242e9fa8
--- /dev/null
+++ b/spec/models/protected_branch/merge_access_level_spec.rb
@@ -0,0 +1,5 @@
+require 'spec_helper'
+
+describe ProtectedBranch::MergeAccessLevel, :models do
+ it { is_expected.to validate_inclusion_of(:access_level).in_array([Gitlab::Access::MASTER, Gitlab::Access::DEVELOPER, Gitlab::Access::NO_ACCESS]) }
+end
diff --git a/spec/models/protected_branch/push_access_level_spec.rb b/spec/models/protected_branch/push_access_level_spec.rb
new file mode 100644
index 00000000000..de68351198c
--- /dev/null
+++ b/spec/models/protected_branch/push_access_level_spec.rb
@@ -0,0 +1,5 @@
+require 'spec_helper'
+
+describe ProtectedBranch::PushAccessLevel, :models do
+ it { is_expected.to validate_inclusion_of(:access_level).in_array([Gitlab::Access::MASTER, Gitlab::Access::DEVELOPER, Gitlab::Access::NO_ACCESS]) }
+end
diff --git a/spec/services/issues/create_service_spec.rb b/spec/services/issues/create_service_spec.rb
index 01edc46496d..dab1a3469f7 100644
--- a/spec/services/issues/create_service_spec.rb
+++ b/spec/services/issues/create_service_spec.rb
@@ -118,6 +118,22 @@ describe Issues::CreateService, services: true do
end
end
+ context 'when assignee is set' do
+ let(:opts) do
+ { title: 'Title',
+ description: 'Description',
+ assignees: [assignee] }
+ end
+
+ it 'invalidates open issues counter for assignees when issue is assigned' do
+ project.team << [assignee, :master]
+
+ described_class.new(project, user, opts).execute
+
+ expect(assignee.assigned_open_issues_count).to eq 1
+ end
+ end
+
it 'executes issue hooks when issue is not confidential' do
opts = { title: 'Title', description: 'Description', confidential: false }
diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb
index 1954d8739f6..5184c1d5f19 100644
--- a/spec/services/issues/update_service_spec.rb
+++ b/spec/services/issues/update_service_spec.rb
@@ -59,6 +59,13 @@ describe Issues::UpdateService, services: true do
expect(issue.due_date).to eq Date.tomorrow
end
+ it 'updates open issue counter for assignees when issue is reassigned' do
+ update_issue(assignee_ids: [user2.id])
+
+ expect(user3.assigned_open_issues_count).to eq 0
+ expect(user2.assigned_open_issues_count).to eq 1
+ end
+
it 'sorts issues as specified by parameters' do
issue1 = create(:issue, project: project, assignees: [user3])
issue2 = create(:issue, project: project, assignees: [user3])
diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb
index ace82380cc9..41752f1a01a 100644
--- a/spec/services/merge_requests/create_service_spec.rb
+++ b/spec/services/merge_requests/create_service_spec.rb
@@ -144,6 +144,26 @@ describe MergeRequests::CreateService, services: true do
expect(merge_request.assignee).to eq(assignee)
end
+ context 'when assignee is set' do
+ let(:opts) do
+ {
+ title: 'Title',
+ description: 'Description',
+ assignee_id: assignee.id,
+ source_branch: 'feature',
+ target_branch: 'master'
+ }
+ end
+
+ it 'invalidates open merge request counter for assignees when merge request is assigned' do
+ project.team << [assignee, :master]
+
+ described_class.new(project, user, opts).execute
+
+ expect(assignee.assigned_open_merge_requests_count).to eq 1
+ end
+ end
+
context "when issuable feature is private" do
before do
project.project_feature.update(issues_access_level: ProjectFeature::PRIVATE,
diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb
index 07f5440cc36..2c8fbb46e75 100644
--- a/spec/services/merge_requests/update_service_spec.rb
+++ b/spec/services/merge_requests/update_service_spec.rb
@@ -299,6 +299,15 @@ describe MergeRequests::UpdateService, services: true do
end
end
+ context 'when the assignee changes' do
+ it 'updates open merge request counter for assignees when merge request is reassigned' do
+ update_merge_request(assignee_id: user2.id)
+
+ expect(user3.assigned_open_merge_requests_count).to eq 0
+ expect(user2.assigned_open_merge_requests_count).to eq 1
+ end
+ end
+
context 'when the target branch change' do
before do
update_merge_request({ target_branch: 'target' })
diff --git a/spec/support/prometheus_helpers.rb b/spec/support/prometheus_helpers.rb
index 51987c7767d..6b9ebcf2bb3 100644
--- a/spec/support/prometheus_helpers.rb
+++ b/spec/support/prometheus_helpers.rb
@@ -1,10 +1,10 @@
module PrometheusHelpers
def prometheus_memory_query(environment_slug)
- %{(sum(container_memory_usage_bytes{container_name!="POD",environment="#{environment_slug}"}) / count(container_memory_usage_bytes{container_name!="POD",environment="#{environment_slug}"})) /1024/1024}
+ %{avg(container_memory_usage_bytes{container_name!="POD",environment="#{environment_slug}"}) / 2^20}
end
def prometheus_cpu_query(environment_slug)
- %{sum(rate(container_cpu_usage_seconds_total{container_name!="POD",environment="#{environment_slug}"}[2m])) / count(container_cpu_usage_seconds_total{container_name!="POD",environment="#{environment_slug}"}) * 100}
+ %{avg(rate(container_cpu_usage_seconds_total{container_name!="POD",environment="#{environment_slug}"}[2m])) * 100}
end
def prometheus_ping_url(prometheus_query)
@@ -88,10 +88,8 @@ module PrometheusHelpers
metrics: {
memory_values: prometheus_values_body('matrix').dig(:data, :result),
memory_current: prometheus_value_body('vector').dig(:data, :result),
- memory_previous: prometheus_value_body('vector').dig(:data, :result),
cpu_values: prometheus_values_body('matrix').dig(:data, :result),
- cpu_current: prometheus_value_body('vector').dig(:data, :result),
- cpu_previous: prometheus_value_body('vector').dig(:data, :result)
+ cpu_current: prometheus_value_body('vector').dig(:data, :result)
},
last_update: last_update
}
diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb
index df2f2ce95e6..aee926877e0 100644
--- a/spec/tasks/gitlab/backup_rake_spec.rb
+++ b/spec/tasks/gitlab/backup_rake_spec.rb
@@ -352,7 +352,7 @@ describe 'gitlab:app namespace rake task' do
end
it 'name has human readable time' do
- expect(@backup_tar).to match(/\d+_\d{4}_\d{2}_\d{2}_\d+\.\d+\.\d+(-pre)?_gitlab_backup.tar$/)
+ expect(@backup_tar).to match(/\d+_\d{4}_\d{2}_\d{2}_\d+\.\d+\.\d+(-pre|-rc\d+)?_gitlab_backup.tar$/)
end
end
end # gitlab:app namespace