Commit fd1811d3 authored by Filipa Lacerda's avatar Filipa Lacerda

Merge branch 'master' into layout-nav-es-module

* master: (21 commits)
  Prevent some specs from mangling the gitlab-shell checkout
  Line up search dropdown with other nav dropdowns
  Fix onion-skin re-entering state
  Remove related links in MR widget when empty state
  Show inline edit button for issues
  Fix tags in the Activity tab not being clickable
  Fix shortcut links on help page
  Don't link LFS-objects multiple times.
  [CE->EE] Fix spec/lib/gitlab/git/gitlab_projects_spec.rb
  Tidy up the documentation of Gitlab HA/Gitlab Application
  Make sure two except won't overwrite each other
  Update axios.md
  Remove transitionend event from GL dropdown
  Preserve gem path so that we use the same gems
  Load commit in batches for pipelines#index
  BlobViewer::PackageJson - if private link to homepage
  Do not generate links for private NPM modules in blob view
  Remove block styling from search dropdown
  Fix sidebar height when performance bar enabled
  Remove all dropdown animations and set display: none if they're not open
  ...
parents 30ea58da febb0b9a
......@@ -80,10 +80,14 @@ stages:
except:
- /(^qa[\/-].*|.*-qa$)/
.except-docs-and-qa: &except-docs-and-qa
except:
- /(^docs[\/-].*|.*-docs$)/
- /(^qa[\/-].*|.*-qa$)/
.rspec-metadata: &rspec-metadata
<<: *dedicated-runner
<<: *except-docs
<<: *except-qa
<<: *except-docs-and-qa
<<: *pull-cache
stage: test
script:
......@@ -121,8 +125,7 @@ stages:
.spinach-metadata: &spinach-metadata
<<: *dedicated-runner
<<: *except-docs
<<: *except-qa
<<: *except-docs-and-qa
<<: *pull-cache
stage: test
script:
......@@ -222,8 +225,7 @@ review-docs-cleanup:
# Retrieve knapsack and rspec_flaky reports
retrieve-tests-metadata:
<<: *tests-metadata-state
<<: *except-docs
<<: *except-qa
<<: *except-docs-and-qa
stage: prepare
cache:
key: tests_metadata
......@@ -378,8 +380,7 @@ spinach-mysql 3 4: *spinach-metadata-mysql
.rake-exec: &rake-exec
<<: *dedicated-runner
<<: *except-docs
<<: *except-qa
<<: *except-docs-and-qa
<<: *pull-cache
<<: *ruby-static-analysis
stage: test
......@@ -443,8 +444,7 @@ ee_compat_check:
# DB migration, rollback, and seed jobs
.db-migrate-reset: &db-migrate-reset
<<: *dedicated-runner
<<: *except-docs
<<: *except-qa
<<: *except-docs-and-qa
<<: *pull-cache
stage: test
script:
......@@ -466,8 +466,7 @@ db:check-schema-pg:
.migration-paths: &migration-paths
<<: *dedicated-runner
<<: *except-docs
<<: *except-qa
<<: *except-docs-and-qa
<<: *pull-cache
stage: test
variables:
......@@ -494,8 +493,7 @@ migration:path-mysql:
.db-rollback: &db-rollback
<<: *dedicated-runner
<<: *except-docs
<<: *except-qa
<<: *except-docs-and-qa
<<: *pull-cache
stage: test
script:
......@@ -512,8 +510,7 @@ db:rollback-mysql:
.db-seed_fu: &db-seed_fu
<<: *dedicated-runner
<<: *except-docs
<<: *except-qa
<<: *except-docs-and-qa
<<: *pull-cache
stage: test
variables:
......@@ -541,8 +538,7 @@ db:seed_fu-mysql:
# Frontend-related jobs
gitlab:assets:compile:
<<: *dedicated-runner
<<: *except-docs
<<: *except-qa
<<: *except-docs-and-qa
<<: *pull-cache
stage: test
dependencies: []
......@@ -564,8 +560,7 @@ gitlab:assets:compile:
karma:
<<: *dedicated-runner
<<: *except-docs
<<: *except-qa
<<: *except-docs-and-qa
<<: *pull-cache
<<: *use-pg
stage: test
......@@ -619,8 +614,7 @@ qa:internal:
coverage:
<<: *dedicated-runner
<<: *except-docs
<<: *except-qa
<<: *except-docs-and-qa
<<: *pull-cache
stage: post-test
services: []
......@@ -639,8 +633,7 @@ coverage:
lint:javascript:report:
<<: *dedicated-runner
<<: *except-docs
<<: *except-qa
<<: *except-docs-and-qa
<<: *pull-cache
stage: post-test
dependencies:
......@@ -699,8 +692,7 @@ cache gems:
gitlab_git_test:
<<: *dedicated-runner
<<: *except-docs
<<: *except-qa
<<: *except-docs-and-qa
<<: *pull-cache
variables:
SETUP_DB: "false"
......
......@@ -263,7 +263,7 @@ gem 'gettext_i18n_rails', '~> 1.8.0'
gem 'gettext_i18n_rails_js', '~> 1.2.0'
gem 'gettext', '~> 3.2.2', require: false, group: :development
gem 'batch-loader'
gem 'batch-loader', '~> 1.2.1'
# Perf bar
gem 'peek', '~> 1.0.1'
......
......@@ -78,7 +78,7 @@ GEM
thread_safe (~> 0.3, >= 0.3.1)
babosa (1.0.2)
base32 (0.3.2)
batch-loader (1.1.1)
batch-loader (1.2.1)
bcrypt (3.1.11)
bcrypt_pbkdf (1.0.0)
benchmark-ips (2.3.0)
......@@ -988,7 +988,7 @@ DEPENDENCIES
awesome_print (~> 1.2.0)
babosa (~> 1.0.2)
base32 (~> 0.3.0)
batch-loader
batch-loader (~> 1.2.1)
bcrypt_pbkdf (~> 1.0)
benchmark-ips (~> 2.3.0)
better_errors (~> 2.1.0)
......
......@@ -176,6 +176,7 @@ export default class ImageFile {
left: dragTrackWidth
});
$frameAdded.css('opacity', 1);
framePadding = parseInt($frameAdded.css('right').replace('px', ''), 10);
_this.initDraggable($dragger, framePadding, function(e, left) {
......
import Mousetrap from 'mousetrap';
function addMousetrapClick(el, key) {
el.addEventListener('click', () => Mousetrap.trigger(key));
}
function domContentLoaded() {
addMousetrapClick(document.querySelector('.js-trigger-shortcut'), '?');
addMousetrapClick(document.querySelector('.js-trigger-search-bar'), 's');
}
document.addEventListener('DOMContentLoaded', domContentLoaded);
......@@ -300,7 +300,7 @@ GitLabDropdown = (function() {
return function(data) {
_this.fullData = data;
_this.parseData(_this.fullData);
_this.focusTextInput(true);
_this.focusTextInput();
if (_this.options.filterable && _this.filter && _this.filter.input && _this.filter.input.val() && _this.filter.input.val().trim() !== '') {
return _this.filter.input.trigger('input');
}
......@@ -790,24 +790,16 @@ GitLabDropdown = (function() {
return [selectedObject, isMarking];
};
GitLabDropdown.prototype.focusTextInput = function(triggerFocus = false) {
GitLabDropdown.prototype.focusTextInput = function() {
if (this.options.filterable) {
this.dropdown.one('transitionend', () => {
const initialScrollTop = $(window).scrollTop();
const initialScrollTop = $(window).scrollTop();
if (this.dropdown.is('.open')) {
this.filterInput.focus();
}
if ($(window).scrollTop() < initialScrollTop) {
$(window).scrollTop(initialScrollTop);
}
});
if (this.dropdown.is('.open')) {
this.filterInput.focus();
}
if (triggerFocus) {
// This triggers after a ajax request
// in case of slow requests, the dropdown transition could already be finished
this.dropdown.trigger('transitionend');
if ($(window).scrollTop() < initialScrollTop) {
$(window).scrollTop(initialScrollTop);
}
}
};
......
......@@ -32,7 +32,7 @@ export default {
showInlineEditButton: {
type: Boolean,
required: false,
default: false,
default: true,
},
showDeleteButton: {
type: Boolean,
......
......@@ -79,7 +79,7 @@
v-tooltip
v-if="showInlineEditButton && canUpdate"
type="button"
class="btn btn-default btn-edit btn-svg"
class="btn btn-default btn-edit btn-svg js-issuable-edit"
v-html="pencilIcon"
title="Edit title and description"
data-placement="bottom"
......
import Vue from 'vue';
import eventHub from './event_hub';
import issuableApp from './components/app.vue';
import '../vue_shared/vue_resource_interceptor';
......@@ -7,12 +6,6 @@ document.addEventListener('DOMContentLoaded', () => {
const initialDataEl = document.getElementById('js-issuable-app-initial-data');
const props = JSON.parse(initialDataEl.innerHTML.replace(/&quot;/g, '"'));
$('.js-issuable-edit').on('click', (e) => {
e.preventDefault();
eventHub.$emit('open.form');
});
return new Vue({
el: document.getElementById('js-issuable-app'),
components: {
......
......@@ -51,7 +51,10 @@ export default class Shortcuts {
}
onToggleHelp(e) {
e.preventDefault();
if (e.preventDefault) {
e.preventDefault();
}
Shortcuts.toggleHelp(this.enabledHelp);
}
......@@ -112,6 +115,9 @@ export default class Shortcuts {
static focusSearch(e) {
$('#search').focus();
e.preventDefault();
if (e.preventDefault) {
e.preventDefault();
}
}
}
......@@ -62,7 +62,7 @@ export default {
return this.mr.hasCI;
},
shouldRenderRelatedLinks() {
return !!this.mr.relatedLinks;
return !!this.mr.relatedLinks && !this.mr.isNothingToMergeState;
},
shouldRenderDeployments() {
return this.mr.deployments.length;
......
import { stateKey } from './state_maps';
export default function deviseState(data) {
if (data.project_archived) {
return 'archived';
return stateKey.archived;
} else if (data.branch_missing) {
return 'missingBranch';
return stateKey.missingBranch;
} else if (!data.commits_count) {
return 'nothingToMerge';
return stateKey.nothingToMerge;
} else if (this.mergeStatus === 'unchecked') {
return 'checking';
return stateKey.checking;
} else if (data.has_conflicts) {
return 'conflicts';
return stateKey.conflicts;
} else if (data.work_in_progress) {
return 'workInProgress';
return stateKey.workInProgress;
} else if (this.onlyAllowMergeIfPipelineSucceeds && this.isPipelineFailed) {
return 'pipelineFailed';
return stateKey.pipelineFailed;
} else if (this.hasMergeableDiscussionsState) {
return 'unresolvedDiscussions';
return stateKey.unresolvedDiscussions;
} else if (this.isPipelineBlocked) {
return 'pipelineBlocked';
return stateKey.pipelineBlocked;
} else if (this.hasSHAChanged) {
return 'shaMismatch';
return stateKey.shaMismatch;
} else if (this.mergeWhenPipelineSucceeds) {
return this.mergeError ? 'autoMergeFailed' : 'mergeWhenPipelineSucceeds';
return this.mergeError ? stateKey.autoMergeFailed : stateKey.mergeWhenPipelineSucceeds;
} else if (!this.canMerge) {
return 'notAllowedToMerge';
return stateKey.notAllowedToMerge;
} else if (this.canBeMerged) {
return 'readyToMerge';
return stateKey.readyToMerge;
}
return null;
}
import Timeago from 'timeago.js';
import { getStateKey } from '../dependencies';
import { stateKey } from './state_maps';
import { formatDate } from '../../lib/utils/datetime_utility';
export default class MergeRequestStore {
......@@ -120,6 +121,10 @@ export default class MergeRequestStore {
}
}
get isNothingToMergeState() {
return this.state === stateKey.nothingToMerge;
}
static getEventObject(event) {
return {
author: MergeRequestStore.getAuthorObject(event),
......
......@@ -31,6 +31,23 @@ const statesToShowHelpWidget = [
'autoMergeFailed',
];
export const stateKey = {
archived: 'archived',
missingBranch: 'missingBranch',
nothingToMerge: 'nothingToMerge',
checking: 'checking',
conflicts: 'conflicts',
workInProgress: 'workInProgress',
pipelineFailed: 'pipelineFailed',
unresolvedDiscussions: 'unresolvedDiscussions',
pipelineBlocked: 'pipelineBlocked',
shaMismatch: 'shaMismatch',
autoMergeFailed: 'autoMergeFailed',
mergeWhenPipelineSucceeds: 'mergeWhenPipelineSucceeds',
notAllowedToMerge: 'notAllowedToMerge',
readyToMerge: 'readyToMerge',
};
export default {
stateToComponentMap,
statesToShowHelpWidget,
......
......@@ -9,12 +9,6 @@
padding-left: $contextual-sidebar-width;
}
// Override position: absolute
.right-sidebar {
position: fixed;
height: calc(100% - #{$header-height});
}
.issues-bulk-update.right-sidebar.right-sidebar-expanded .issuable-sidebar-header {
padding: 10px 0 15px;
}
......
......@@ -16,27 +16,18 @@
@mixin set-visible {
transform: translateY(0);
visibility: visible;
opacity: 1;
transition-duration: 100ms, 150ms, 25ms;
transition-delay: 35ms, 50ms, 25ms;
display: block;
}
@mixin set-invisible {
transform: translateY(-10px);
visibility: hidden;
opacity: 0;
transition-property: opacity, transform, visibility;
transition-duration: 70ms, 250ms, 250ms;
transition-timing-function: linear, $dropdown-animation-timing;
transition-delay: 25ms, 50ms, 0ms;
display: none;
}
.open {
.dropdown-menu,
.dropdown-menu-nav {
@include set-visible;
display: block;
min-height: 40px;
@media (max-width: $screen-xs-max) {
......@@ -55,6 +46,11 @@
}
}
// Get search dropdown to line up with other nav dropdowns
.search-input-container .dropdown-menu {
margin-top: 11px;
}
.dropdown-toggle {
padding: 6px 8px 6px 10px;
background-color: $white-light;
......@@ -214,7 +210,6 @@
.dropdown-menu,
.dropdown-menu-nav {
@include set-invisible;
display: block;
position: absolute;
width: auto;
top: 100%;
......
......@@ -90,11 +90,6 @@
.right-sidebar {
border-left: 1px solid $border-color;
height: calc(100% - #{$header-height});
&.affix {
position: fixed;
top: $header-height;
}
}
.with-performance-bar .right-sidebar.affix {
......
......@@ -122,7 +122,7 @@
}
.right-sidebar {
position: absolute;
position: fixed;
top: $header-height;
bottom: 0;
right: 0;
......@@ -502,7 +502,7 @@
top: $header-height + $performance-bar-height;
.issuable-sidebar {
height: calc(100% - #{$header-height} - #{$performance-bar-height});
height: calc(100% - #{$performance-bar-height});
}
}
......
......@@ -108,13 +108,6 @@ input[type="checkbox"]:hover {
// Custom dropdown positioning
.dropdown-menu {
transition-property: opacity, transform;
transition-duration: 250ms, 250ms;
transition-delay: 0ms, 25ms;
transition-timing-function: $dropdown-animation-timing;
transform: translateY(0);
opacity: 0;
display: block;
left: -5px;
}
......@@ -152,13 +145,6 @@ input[type="checkbox"]:hover {
background-color: $nav-badge-bg;
border-color: $border-color;
}
.dropdown-menu {
transition-duration: 100ms, 75ms;
transition-delay: 75ms, 100ms;
transform: translateY(7px);
opacity: 1;
}
}
&.has-value {
......
......@@ -29,6 +29,8 @@ class Projects::PipelinesController < Projects::ApplicationController
@pipelines_count = PipelinesFinder
.new(project).execute.count
@pipelines.map(&:commit) # List commits for batch loading
respond_to do |format|
format.html
format.json do
......
......@@ -27,10 +27,17 @@ module BlobViewer
private
def package_name_from_json(key)
prepare!
def json_data
@json_data ||= begin
prepare!
JSON.parse(blob.data)
rescue
{}
end
end
JSON.parse(blob.data)[key] rescue nil
def package_name_from_json(key)
json_data[key]
end
def package_name_from_method_call(name)
......
......@@ -16,7 +16,25 @@ module BlobViewer
@package_name ||= package_name_from_json('name')
end
def package_type
private? ? 'private package' : super
end
def package_url
private? ? homepage : npm_url
end
private
def private?
!!json_data['private']
end
def homepage
json_data['homepage']
end
def npm_url
"https://www.npmjs.com/package/#{package_name}"
end
end
......
......@@ -287,8 +287,12 @@ module Ci
Ci::Pipeline.truncate_sha(sha)
end
# NOTE: This is loaded lazily and will never be nil, even if the commit
# cannot be found.
#
# Use constructs like: `pipeline.commit.present?`
def commit
@commit ||= project.commit_by(oid: sha)
@commit ||= Commit.lazy(project, sha)
end
def branch?
......@@ -338,12 +342,9 @@ module Ci
end
def latest?
return false unless ref
commit = project.commit(ref)
return false unless commit
return false unless ref && commit.present?
commit.sha == sha
project.commit(ref) == commit
end
def retried
......
......@@ -86,6 +86,20 @@ class Commit
def valid_hash?(key)
!!(/\A#{COMMIT_SHA_PATTERN}\z/ =~ key)
end
def lazy(project, oid)
BatchLoader.for({ project: project, oid: oid }).batch do |items, loader|
items_by_project = items.group_by { |i| i[:project] }
items_by_project.each do |project, commit_ids|
oids = commit_ids.map { |i| i[:oid] }
project.repository.commits_by(oids: oids).each do |commit|
loader.call({ project: commit.project, oid: commit.id }, commit) if commit
end
end
end
end
end
attr_accessor :raw
......@@ -103,7 +117,7 @@ class Commit
end
def ==(other)
(self.class === other) && (raw == other.raw)
other.is_a?(self.class) && raw == other.raw
end
def self.reference_prefix
......@@ -224,8 +238,8 @@ class Commit
notes.includes(:author)
end
def method_missing(m, *args, &block)
@raw.__send__(m, *args, &block) # rubocop:disable GitlabSecurity/PublicSend
def method_missing(method, *args, &block)
@raw.__send__(method, *args, &block) # rubocop:disable GitlabSecurity/PublicSend
end
def respond_to_missing?(method, include_private = false)
......
......@@ -118,6 +118,18 @@ class Repository
@commit_cache[oid] = find_commit(oid)
end
def commits_by(oids:)
return [] unless oids.present?
commits = Gitlab::Git::Commit.batch_by_oid(raw_repository, oids)
if commits.present?
Commit.decorate(commits, @project)
else
[]
end
end
def commits(ref, path: nil, limit: nil, offset: nil, skip_merges: false, after: nil, before: nil)
options = {
repo: raw_repository,
......
......@@ -5,7 +5,7 @@ module Projects
if fork_source = @project.fork_source
fork_source.lfs_objects.find_each do |lfs_object|
lfs_object.projects << @project
lfs_object.projects << @project unless lfs_object.projects.include?(@project)
end
refresh_forks_count(fork_source)
......
......@@ -7,7 +7,8 @@
%span.pushed #{event.action_name} #{event.ref_type}
%strong
- commits_link = project_commits_path(project, event.ref_name)
= link_to_if project.repository.branch_exists?(event.ref_name), event.ref_name, commits_link, class: 'ref-name'
- should_link = event.tag? ? project.repository.tag_exists?(event.ref_name) : project.repository.branch_exists?(event.ref_name)
= link_to_if should_link, event.ref_name, commits_link, class: 'ref-name'
= render "events/event_scope", event: event
......
= webpack_bundle_tag 'docs'
%div
- if current_application_settings.help_page_text.present?
= markdown_field(current_application_settings, :help_page_text)
......@@ -37,8 +39,12 @@
Quick help
%ul.well-list
%li= link_to 'See our website for getting help', support_url
%li= link_to 'Use the search bar on the top of this page', '#', onclick: 'Shortcuts.focusSearch(event)'
%li= link_to 'Use shortcuts', '#', onclick: 'Shortcuts.toggleHelp()'
%li
%button.btn-blank.btn-link.js-trigger-search-bar{ type: 'button' }
Use the search bar on the top of this page
%li
%button.btn-blank.btn-link.js-trigger-shortcut{ type: 'button' }
Use shortcuts
- unless current_application_settings.help_page_hide_commercial_content?
%li= link_to 'Get a support subscription', 'https://about.gitlab.com/pricing/'
%li= link_to 'Compare GitLab editions', 'https://about.gitlab.com/features/#compare'
......@@ -6,6 +6,6 @@
- if viewer.package_name
and defines a #{viewer.package_type} named
%strong<
= link_to viewer.package_name, viewer.package_url, target: '_blank', rel: 'noopener noreferrer'
= link_to_if viewer.package_url.present?, viewer.package_name, viewer.package_url, target: '_blank', rel: 'noopener noreferrer'
= link_to 'Learn more', viewer.manager_url, target: '_blank', rel: 'noopener noreferrer'
......@@ -39,8 +39,6 @@
= icon('caret-down')
.dropdown-menu.dropdown-menu-align-right.hidden-lg
%ul
- if can_update_issue
%li= link_to 'Edit', edit_project_issue_path(@project, @issue), class: 'js-issuable-edit'
- unless current_user == @issue.author
%li= link_to 'Report abuse', new_abuse_report_path(user_id: @issue.author.id, ref_url: issue_url(@issue))
- if can_update_issue
......@@ -52,9 +50,6 @@
%li.divider
%li= link_to 'New issue', new_project_issue_path(@project), title: 'New issue', id: 'new_issue_link'
- if can_update_issue
= link_to 'Edit', edit_project_issue_path(@project, @issue), class: 'hidden-xs hidden-sm btn btn-grouped js-issuable-edit'
= render 'shared/issuable/close_reopen_button', issuable: @issue, can_update: can_update_issue
- if can_report_spam
......
#js-pipeline-header-vue.pipeline-header-container
- if @commit
- if @commit.present?
.commit-box
%h3.commit-title
= markdown(@commit.title, pipeline: :single_line)
......@@ -8,28 +8,28 @@
%pre.commit-description
= preserve(markdown(@commit.description, pipeline: :single_line))
.info-well
- if @commit.status
.well-segment.pipeline-info
.icon-container
= icon('clock-o')
= pluralize @pipeline.total_size, "job"
- if @pipeline.ref
from
= link_to @pipeline.ref, project_ref_path(@project, @pipeline.ref), class: "ref-name"
- if @pipeline.duration
in
= time_interval_in_words(@pipeline.duration)
- if @pipeline.queued_duration
= "(queued for #{time_interval_in_words(@pipeline.queued_duration)})"
.info-well
- if @commit.status
.well-segment.pipeline-info
.icon-container
= icon('clock-o')
= pluralize @pipeline.total_size, "job"
- if @pipeline.ref
from
= link_to @pipeline.ref, project_ref_path(@project, @pipeline.ref), class: "ref-name"
- if @pipeline.duration
in
= time_interval_in_words(@pipeline.duration)
- if @pipeline.queued_duration
= "(queued for #{time_interval_in_words(@pipeline.queued_duration)})"
.well-segment.branch-info
.icon-container.commit-icon
= custom_icon("icon_commit")
= link_to @commit.short_id, project_commit_path(@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, project_commit_path(@project, @pipeline.sha), class: "commit-sha commit-hash-full"
= clipboard_button(text: @pipeline.sha, title: "Copy commit SHA to clipboard")
.well-segment.branch-info
.icon-container.commit-icon
= custom_icon("icon_commit")
= link_to @commit.short_id, project_commit_path(@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, project_commit_path(@project, @pipeline.sha), class: "commit-sha commit-hash-full"
= clipboard_button(text: @pipeline.sha, title: "Copy commit SHA to clipboard")
......@@ -13,7 +13,7 @@ class ExpirePipelineCacheWorker
store.touch(project_pipelines_path(project))
store.touch(project_pipeline_path(project, pipeline))
store.touch(commit_pipelines_path(project, pipeline.commit)) if pipeline.commit
store.touch(commit_pipelines_path(project, pipeline.commit)) unless pipeline.commit.nil?
store.touch(new_merge_request_pipelines_path(project))
each_pipelines_merge_request_path(project, pipeline) do |path|
store.touch(path)
......
---
title: Fix tags in the Activity tab not being clickable
merge_request: 15996
author: Mario de la Ossa
type: fixed
---
title: Do not generate NPM links for private NPM modules in blob view
merge_request: 16002
author: Mario de la Ossa
type: added
---
title: Don't link LFS objects to a project when unlinking forks when they were already
linked
merge_request: 16006
author:
type: fixed
---
title: Fix shortcut links on help page
merge_request:
author:
type: fixed
---
title: Fix onion-skin re-entering state
merge_request:
author:
type: fixed
---
title: Remove related links in MR widget when empty state
merge_request:
author:
type: fixed
---
title: Move edit button to second row on issue page (and change it to a pencil icon)
merge_request:
author:
type: changed
......@@ -36,6 +36,7 @@ var config = {
cycle_analytics: './cycle_analytics/cycle_analytics_bundle.js',
commit_pipelines: './commit/pipelines/pipelines_bundle.js',
deploy_keys: './deploy_keys/index.js',
docs: './docs/docs_bundle.js',
diff_notes: './diff_notes/diff_notes_bundle.js',
environments: './environments/environments_bundle.js',
environments_folder: './environments/folder/environments_folder_bundle.js',
......
# Configuring GitLab for HA
Assuming you have already configured a database, Redis, and NFS, you can
Assuming you have already configured a [database](database.md), [Redis](redis.md), and [NFS](nfs.md), you can
configure the GitLab application server(s) now. Complete the steps below
for each GitLab application server in your environment.
......@@ -48,34 +48,33 @@ for each GitLab application server in your environment.
data locations. See [NFS documentation](nfs.md) for `/etc/gitlab/gitlab.rb`
configuration values for various scenarios. The example below assumes you've
added NFS mounts in the default data locations.
```ruby
external_url 'https://gitlab.example.com'
# Prevent GitLab from starting if NFS data mounts are not available
high_availability['mountpoint'] = '/var/opt/gitlab/git-data'
# Disable components that will not be on the GitLab application server
postgresql['enable'] = false
redis['enable'] = false
roles ['application_role']
# PostgreSQL connection details
gitlab_rails['db_adapter'] = 'postgresql'
gitlab_rails['db_encoding'] = 'unicode'
gitlab_rails['db_host'] = '10.1.0.5' # IP/hostname of database server
gitlab_rails['db_password'] = 'DB password'
# Redis connection details
gitlab_rails['redis_port'] = '6379'
gitlab_rails['redis_host'] = '10.1.0.6' # IP/hostname of Redis server
gitlab_rails['redis_password'] = 'Redis Password'
```
> **Note:** To maintain uniformity of links across HA clusters, the `external_url`
on the first application server as well as the additional application
servers should point to the external url that users will use to access GitLab.
> **Note:** To maintain uniformity of links across HA clusters, the `external_url`
on the first application server as well as the additional application
servers should point to the external url that users will use to access GitLab.
In a typical HA setup, this will be the url of the load balancer which will
route traffic to all GitLab application servers in the HA cluster.
route traffic to all GitLab application servers in the HA cluster.
1. Run `sudo gitlab-ctl reconfigure` to compile the configuration.
......
......@@ -11,7 +11,7 @@ This exported module should be used instead of directly using `axios` to ensure
## Usage
```javascript
import axios from '~/lib/utils/axios_utils';
import axios from './lib/utils/axios_utils';
axios.get(url)
.then((response) => {
......
......@@ -228,6 +228,19 @@ module Gitlab
end
end
end
# Only to be used when the object ids will not necessarily have a
# relation to each other. The last 10 commits for a branch for example,
# should go through .where
def batch_by_oid(repo, oids)
repo.gitaly_migrate(:list_commits_by_oid) do |is_enabled|
if is_enabled
repo.gitaly_commit_client.list_commits_by_oid(oids)
else
oids.map { |oid| find(repo, oid) }.compact
end
end
end
end
def initialize(repository, raw_commit, head = nil)
......
......@@ -169,6 +169,15 @@ module Gitlab
consume_commits_response(response)
end
def list_commits_by_oid(oids)
request = Gitaly::ListCommitsByOidRequest.new(repository: @gitaly_repo, oid: oids)
response = GitalyClient.call(@repository.storage, :commit_service, :list_commits_by_oid, request, timeout: GitalyClient.medium_timeout)
consume_commits_response(response)
rescue GRPC::Unknown # If no repository is found, happens mainly during testing
[]
end
def commits_by_message(query, revision: '', path: '', limit: 1000, offset: 0)
request = Gitaly::CommitsByMessageRequest.new(
repository: @gitaly_repo,
......
#!/usr/bin/env ruby
gitaly_dir = 'tmp/tests/gitaly'
env = { 'HOME' => File.expand_path('tmp/tests') }
env = { 'HOME' => File.expand_path('tmp/tests'),
'GEM_PATH' => Gem.path.join(':') }
args = %W[#{gitaly_dir}/gitaly #{gitaly_dir}/config.toml]
# Print the PID of the spawned process
......
......@@ -17,13 +17,10 @@ describe Projects::PipelinesController do
describe 'GET index.json' do
before do
branch_head = project.commit
parent = branch_head.parent
create(:ci_empty_pipeline, status: 'pending', project: project, sha: branch_head.id)
create(:ci_empty_pipeline, status: 'running', project: project, sha: branch_head.id)
create(:ci_empty_pipeline, status: 'created', project: project, sha: parent.id)
create(:ci_empty_pipeline, status: 'success', project: project, sha: parent.id)
%w(pending running created success).each_with_index do |status, index|
sha = project.commit("HEAD~#{index}")
create(:ci_empty_pipeline, status: status, project: project, sha: sha)
end
end
subject do
......@@ -46,7 +43,7 @@ describe Projects::PipelinesController do
context 'when performing gitaly calls', :request_store do
it 'limits the Gitaly requests' do
expect { subject }.to change { Gitlab::GitalyClient.get_request_count }.by(8)
expect { subject }.to change { Gitlab::GitalyClient.get_request_count }.by(3)
end
end
end
......
......@@ -32,6 +32,24 @@ describe 'Help Pages' do
it_behaves_like 'help page', prefix: '/gitlab'
end
context 'quick link shortcuts', :js do
before do
visit help_path
end
it 'focuses search bar' do
find('.js-trigger-search-bar').click
expect(page).to have_selector('#search:focus')
end
it 'opens shortcuts help dialog' do
find('.js-trigger-shortcut').click
expect(page).to have_selector('#modal-shortcuts')
end
end
end
context 'in a production environment with version check enabled', :js do
......
......@@ -24,7 +24,7 @@ feature 'Issue Detail', :js do
visit project_issue_path(project, issue)
wait_for_requests
click_link 'Edit'
page.find('.js-issuable-edit').click
fill_in 'issuable-title', with: 'issue title'
click_button 'Save'
wait_for_requests
......
......@@ -10,8 +10,6 @@ feature 'image diff notes', :js do
project.team << [user, :master]
sign_in user
page.driver.set_cookie('sidebar_collapsed', 'true')
# Stub helper to return any blob file as image from public app folder.
# This is necessary to run this specs since we don't display repo images in capybara.
allow_any_instance_of(DiffHelper).to receive(:diff_file_blob_raw_path).and_return('/apple-touch-icon.png')
......@@ -141,13 +139,13 @@ feature 'image diff notes', :js do
end
it 'allows expanding/collapsing the discussion notes' do
page.all('.js-diff-notes-toggle')[0].trigger('click')
page.all('.js-diff-notes-toggle')[1].trigger('click')
page.all('.js-diff-notes-toggle')[0].click
page.all('.js-diff-notes-toggle')[1].click
expect(page).not_to have_content('image diff test comment')
page.all('.js-diff-notes-toggle')[0].trigger('click')
page.all('.js-diff-notes-toggle')[1].trigger('click')
page.all('.js-diff-notes-toggle')[0].click
page.all('.js-diff-notes-toggle')[1].click
expect(page).to have_content('image diff test comment')
end
......@@ -196,13 +194,31 @@ feature 'image diff notes', :js do
expect(find('.onion-skin-frame')['style']).to match('width: 228px; height: 240px;')
end
it 'resets onion skin view mode opacity when toggling between view modes' do
find('.view-modes-menu .onion-skin').click
# Simulate dragging onion-skin slider
drag_and_drop_by(find('.dragger'), -30, 0)
expect(find('.onion-skin-frame .frame.added', visible: false)['style']).not_to match('opacity: 1;')
find('.view-modes-menu .swipe').click
find('.view-modes-menu .onion-skin').click
expect(find('.onion-skin-frame .frame.added', visible: false)['style']).to match('opacity: 1;')
end
end
end
def create_image_diff_note
find('.js-add-image-diff-note-button', match: :first).click
page.all('.js-add-image-diff-note-button')[0].trigger('click')
find('.diff-content .note-textarea').native.send_keys('image diff test comment')
click_button 'Comment'
wait_for_requests
def drag_and_drop_by(element, right_by, down_by)
page.driver.browser.action.drag_and_drop_by(element.native, right_by, down_by).perform
end
def create_image_diff_note
find('.js-add-image-diff-note-button', match: :first).click
page.all('.js-add-image-diff-note-button')[0].click
find('.diff-content .note-textarea').native.send_keys('image diff test comment')
click_button 'Comment'
wait_for_requests
end
end
......@@ -2,15 +2,15 @@ require 'spec_helper'
feature 'project owner sees a link to create a license file in empty project', :js do
let(:project_master) { create(:user) }
let(:project) { create(:project) }
let(:project) { create(:project_empty_repo) }
background do
project.team << [project_master, :master]
project.add_master(project_master)
sign_in(project_master)
end
scenario 'project master creates a license file from a template' do
visit project_path(project)
click_link 'Create empty bare repository'
click_on 'LICENSE'
expect(page).to have_content('New file')
......@@ -26,8 +26,6 @@ feature 'project owner sees a link to create a license file in empty project', :
expect(file_content).to have_content("Copyright (c) #{Time.now.year} #{project.namespace.human_name}")
fill_in :commit_message, with: 'Add a LICENSE file', visible: true
# Remove pre-receive hook so we can push without auth
FileUtils.rm_f(File.join(project.repository.path, 'hooks', 'pre-receive'))
click_button 'Commit changes'
expect(current_path).to eq(
......
......@@ -32,9 +32,7 @@ feature 'issuable templates', :js do
message: 'added issue template',
branch_name: 'master')
visit project_issue_path project, issue
page.within('.js-issuable-actions') do
click_on 'Edit'
end
page.find('.js-issuable-edit').click
fill_in :'issuable-title', with: 'test issue title'
end
......@@ -77,9 +75,7 @@ feature 'issuable templates', :js do
message: 'added issue template',
branch_name: 'master')
visit project_issue_path project, issue
page.within('.js-issuable-actions') do
click_on 'Edit'
end
page.find('.js-issuable-edit').click
fill_in :'issuable-title', with: 'test issue title'
fill_in :'issue-description', with: prior_description
end
......
......@@ -4,18 +4,17 @@ feature 'Master views tags' do
let(:user) { create(:user) }
before do
project.team << [user, :master]
project.add_master(user)
sign_in(user)
end
context 'when project has no tags' do
let(:project) { create(:project_empty_repo) }
before do
visit project_path(project)
click_on 'README'
fill_in :commit_message, with: 'Add a README file', visible: true
# Remove pre-receive hook so we can push without auth
FileUtils.rm_f(File.join(project.repository.path, 'hooks', 'pre-receive'))
click_button 'Commit changes'
visit project_tags_path(project)
end
......
......@@ -2,6 +2,7 @@ import Vue from 'vue';
import mrWidgetOptions from '~/vue_merge_request_widget/mr_widget_options';
import eventHub from '~/vue_merge_request_widget/event_hub';
import notify from '~/lib/utils/notify';
import { stateKey } from '~/vue_merge_request_widget/stores/state_maps';
import mockData from './mock_data';
import mountComponent from '../helpers/vue_mount_component_helper';
......@@ -344,4 +345,31 @@ describe('mrWidgetOptions', () => {
expect(comps['mr-widget-merge-when-pipeline-succeeds']).toBeDefined();
});
});
describe('rendering relatedLinks', () => {
beforeEach((done) => {
vm.mr.relatedLinks = {
assignToMe: null,
closing: `
<a class="close-related-link" href="#'>
Close
</a>
`,
mentioned: '',
};
Vue.nextTick(done);
});
it('renders if there are relatedLinks', () => {
expect(vm.$el.querySelector('.close-related-link')).toBeDefined();
});
it('does not render if state is nothingToMerge', (done) => {
vm.mr.state = stateKey.nothingToMerge;
Vue.nextTick(() => {
expect(vm.$el.querySelector('.close-related-link')).toBeNull();
done();
});
});
});
});
import MergeRequestStore from '~/vue_merge_request_widget/stores/mr_widget_store';
import { stateKey } from '~/vue_merge_request_widget/stores/state_maps';
import mockData from '../mock_data';
describe('MergeRequestStore', () => {
......@@ -52,5 +53,17 @@ describe('MergeRequestStore', () => {
expect(store.isPipelineSkipped).toBe(false);
});
});
describe('isNothingToMergeState', () => {
it('returns true when nothingToMerge', () => {
store.state = stateKey.nothingToMerge;
expect(store.isNothingToMergeState).toEqual(true);
});
it('returns false when not nothingToMerge', () => {
store.state = 'state';
expect(store.isNothingToMergeState).toEqual(false);
});
});
});
});
......@@ -41,7 +41,8 @@ describe Gitlab::Git::GitlabProjects do
end
it "fails if the source path doesn't exist" do
expect(logger).to receive(:error).with("mv-project failed: source path <#{tmp_repos_path}/bad-src.git> does not exist.")
expected_source_path = File.join(tmp_repos_path, 'bad-src.git')
expect(logger).to receive(:error).with("mv-project failed: source path <#{expected_source_path}> does not exist.")
result = build_gitlab_projects(tmp_repos_path, 'bad-src.git').mv_project('repo.git')
expect(result).to be_falsy
......@@ -50,7 +51,8 @@ describe Gitlab::Git::GitlabProjects do
it 'fails if the destination path already exists' do
FileUtils.mkdir_p(File.join(tmp_repos_path, 'already-exists.git'))
message = "mv-project failed: destination path <#{tmp_repos_path}/already-exists.git> already exists."
expected_distination_path = File.join(tmp_repos_path, 'already-exists.git')
message = "mv-project failed: destination path <#{expected_distination_path}> already exists."
expect(logger).to receive(:error).with(message)
expect(gl_projects.mv_project('already-exists.git')).to be_falsy
......
......@@ -22,4 +22,51 @@ describe BlobViewer::PackageJson do
expect(subject.package_name).to eq('module-name')
end
end
describe '#package_url' do
it 'returns the package URL' do
expect(subject).to receive(:prepare!)
expect(subject.package_url).to eq("https://www.npmjs.com/package/#{subject.package_name}")
end
end
describe '#package_type' do
it 'returns "package"' do
expect(subject).to receive(:prepare!)
expect(subject.package_type).to eq('package')
end
end
context 'when package.json has "private": true' do
let(:data) do
<<-SPEC.strip_heredoc
{
"name": "module-name",
"version": "10.3.1",
"private": true,
"homepage": "myawesomepackage.com"
}
SPEC
end
let(:blob) { fake_blob(path: 'package.json', data: data) }
subject { described_class.new(blob) }
describe '#package_url' do
it 'returns homepage if any' do
expect(subject).to receive(:prepare!)
expect(subject.package_url).to eq('myawesomepackage.com')
end
end
describe '#package_type' do
it 'returns "private package"' do
expect(subject).to receive(:prepare!)
expect(subject.package_type).to eq('private package')
end
end
end
end
......@@ -13,6 +13,45 @@ describe Commit do
it { is_expected.to include_module(StaticModel) }
end
describe '.lazy' do
set(:project) { create(:project, :repository) }
context 'when the commits are found' do
let(:oids) do
%w(
498214de67004b1da3d820901307bed2a68a8ef6
c642fe9b8b9f28f9225d7ea953fe14e74748d53b
6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
048721d90c449b244b7b4c53a9186b04330174ec
281d3a76f31c812dbf48abce82ccf6860adedd81
)
end
subject { oids.map { |oid| described_class.lazy(project, oid) } }
it 'batches requests for commits' do
expect(project.repository).to receive(:commits_by).once.and_call_original
subject.first.title
subject.last.title
end
it 'maintains ordering' do
subject.each_with_index do |commit, i|
expect(commit.id).to eq(oids[i])
end
end
end
context 'when not found' do
it 'returns nil as commit' do
commit = described_class.lazy(project, 'deadbeef').__sync
expect(commit).to be_nil
end
end
end
describe '#author' do
it 'looks up the author in a case-insensitive way' do
user = create(:user, email: commit.author_email.upcase)
......
......@@ -239,6 +239,54 @@ describe Repository do
end
end
describe '#commits_by' do
set(:project) { create(:project, :repository) }
shared_examples 'batch commits fetching' do
let(:oids) { TestEnv::BRANCH_SHA.values }
subject { project.repository.commits_by(oids: oids) }
it 'finds each commit' do
expect(subject).not_to include(nil)
expect(subject.size).to eq(oids.size)
end
it 'returns only Commit instances' do
expect(subject).to all( be_a(Commit) )
end
context 'when some commits are not found ' do
let(:oids) do
['deadbeef'] + TestEnv::BRANCH_SHA.values.first(10)
end
it 'returns only found commits' do
expect(subject).not_to include(nil)
expect(subject.size).to eq(10)
end
end
context 'when no oids are passed' do
let(:oids) { [] }
it 'does not call #batch_by_oid' do
expect(Gitlab::Git::Commit).not_to receive(:batch_by_oid)
subject
end
end
end
context 'when Gitaly list_commits_by_oid is enabled' do
it_behaves_like 'batch commits fetching'
end
context 'when Gitaly list_commits_by_oid is enabled', :disable_gitaly do
it_behaves_like 'batch commits fetching'
end
end
describe '#find_commits_by_message' do
shared_examples 'finding commits by message' do
it 'returns commits with messages containing a given string' do
......
require 'spec_helper'
describe PipelineSerializer do
set(:project) { create(:project, :repository) }
set(:user) { create(:user) }
let(:serializer) do
......@@ -16,7 +17,7 @@ describe PipelineSerializer do
end
context 'when a single object is being serialized' do
let(:resource) { create(:ci_empty_pipeline) }
let(:resource) { create(:ci_empty_pipeline, project: project) }
it 'serializers the pipeline object' do
expect(subject[:id]).to eq resource.id
......@@ -24,7 +25,7 @@ describe PipelineSerializer do
end
context 'when multiple objects are being serialized' do
let(:resource) { create_list(:ci_pipeline, 2) }
let(:resource) { create_list(:ci_pipeline, 2, project: project) }
it 'serializers the array of pipelines' do
expect(subject).not_to be_empty
......@@ -100,7 +101,6 @@ describe PipelineSerializer do
context 'number of queries' do
let(:resource) { Ci::Pipeline.all }
let(:project) { create(:project) }
before do
# Since RequestStore.active? is true we have to allow the
......
......@@ -62,6 +62,26 @@ describe Projects::UnlinkForkService do
expect(source.forks_count).to be_zero
end
context 'when the source has LFS objects' do
let(:lfs_object) { create(:lfs_object) }
before do
lfs_object.projects << project
end
it 'links the fork to the lfs object before unlinking' do
subject.execute
expect(lfs_object.projects).to include(forked_project)
end
it 'does not fail if the lfs objects were already linked' do
lfs_object.projects << forked_project
expect { subject.execute }.not_to raise_error
end
end
context 'when the original project was deleted' do
it 'does not fail when the original project is deleted' do
source = forked_project.forked_from_project
......
require 'spec_helper'
describe 'events/event/_push.html.haml' do
let(:event) { build_stubbed(:push_event) }
context 'with a branch' do
let(:payload) { build_stubbed(:push_event_payload, event: event) }
before do
allow(event).to receive(:push_event_payload).and_return(payload)
end
it 'links to the branch' do
allow(event.project.repository).to receive(:branch_exists?).with(event.ref_name).and_return(true)
link = project_commits_path(event.project, event.ref_name)
render partial: 'events/event/push', locals: { event: event }
expect(rendered).to have_link(event.ref_name, href: link)
end
context 'that has been deleted' do
it 'does not link to the branch' do
render partial: 'events/event/push', locals: { event: event }
expect(rendered).not_to have_link(event.ref_name)
end
end
end
context 'with a tag' do
let(:payload) { build_stubbed(:push_event_payload, event: event, ref_type: :tag, ref: 'v0.1.0') }
before do
allow(event).to receive(:push_event_payload).and_return(payload)
end
it 'links to the tag' do
allow(event.project.repository).to receive(:tag_exists?).with(event.ref_name).and_return(true)
link = project_commits_path(event.project, event.ref_name)
render partial: 'events/event/push', locals: { event: event }
expect(rendered).to have_link(event.ref_name, href: link)
end
context 'that has been deleted' do
it 'does not link to the tag' do
render partial: 'events/event/push', locals: { event: event }
expect(rendered).not_to have_link(event.ref_name)
end
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment