Commit ed055046 authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge remote-tracking branch 'origin/master' into 3995-improve-sast

parents 91897b3d f02217c0
......@@ -344,6 +344,7 @@ setup-test-env:
expire_in: 7d
paths:
- tmp/tests
- config/secrets.yml
rspec-pg geo: *rspec-metadata-pg-geo
......
Please view this file on the master branch, on stable branches it's out of date.
## 10.4.2 (2018-01-30)
### Fixed (7 changes)
- Fix Epic issue item reordering to handle different scenarios. !4142
- Fix visually broken admin dashboard until license is added. !4196
- Handle empty event timestamp and larger memory units. !4206
- Use a fixed remote name for Geo mirrors. !4249
- Preserve updated issue order to store when reorder is completed. !4278
- Geo - Fix OPENSSH_EXPECTED_COMMAND in the geo:check rake task.
- Execute group hooks after-commit when moving an issue.
## 10.4.1 (2018-01-24)
### Fixed (1 change)
......
......@@ -2,6 +2,22 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
## 10.4.2 (2018-01-30)
### Fixed (6 changes)
- Fix copy/paste on iOS devices due to a bug in webkit. !15804
- Fix missing "allow users to request access" option in public project permissions. !16485
- Fix encoding issue when counting commit count. !16637
- Fixes destination already exists, and some particular service errors on Import/Export error. !16714
- Fix cache clear bug withg using : on Windows. !16740
- Use has_table_privilege for TRIGGER on PostgreSQL.
### Changed (1 change)
- Vendor Auto DevOps template with DAST security checks enabled. !16691
## 10.4.1 (2018-01-24)
### Fixed (4 changes)
......
......@@ -337,7 +337,7 @@ group :development, :test do
gem 'spinach-rerun-reporter', '~> 0.0.2'
gem 'rspec_profiling', '~> 0.0.5'
gem 'rspec-set', '~> 0.1.3'
gem 'rspec-parameterized'
gem 'rspec-parameterized', require: false
# Prevent occasions where minitest is not bundled in packaged versions of ruby (see #3826)
gem 'minitest', '~> 5.7.0'
......
......@@ -329,7 +329,7 @@ GEM
posix-spawn (~> 0.3)
gitlab-license (1.0.0)
gitlab-markup (1.6.3)
gitlab-styles (2.3.1)
gitlab-styles (2.3.2)
rubocop (~> 0.51)
rubocop-gitlab-security (~> 0.1.0)
rubocop-rspec (~> 1.19)
......
/* eslint-disable class-methods-use-this */
import _ from 'underscore';
import Cookies from 'js-cookie';
import { s__ } from './locale';
import { isInIssuePage, updateTooltipTitle } from './lib/utils/common_utils';
import Flash from './flash';
import flash from './flash';
import axios from './lib/utils/axios_utils';
const animationEndEventString = 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd';
const transitionEndEventString = 'transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd';
......@@ -441,13 +443,15 @@ class AwardsHandler {
if (this.isUserAuthored($emojiButton)) {
this.userAuthored($emojiButton);
} else {
$.post(awardUrl, {
axios.post(awardUrl, {
name: emoji,
}, (data) => {
})
.then(({ data }) => {
if (data.ok) {
callback();
}
}).fail(() => new Flash('Something went wrong on our end.'));
})
.catch(() => flash(s__('Something went wrong on our end.')));
}
}
......
/* global ace */
import axios from '~/lib/utils/axios_utils';
import createFlash from '~/flash';
import { __ } from '~/locale';
import TemplateSelectorMediator from '../blob/file_template_mediator';
export default class EditBlob {
......@@ -56,12 +59,14 @@ export default class EditBlob {
if (paneId === '#preview') {
this.$toggleButton.hide();
return $.post(currentLink.data('preview-url'), {
axios.post(currentLink.data('preview-url'), {
content: this.editor.getValue(),
}, (response) => {
currentPane.empty().append(response);
return currentPane.renderGFM();
});
})
.then(({ data }) => {
currentPane.empty().append(data);
currentPane.renderGFM();
})
.catch(() => createFlash(__('An error occurred previewing the blob')));
}
this.$toggleButton.show();
......
/* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, no-var, object-shorthand, consistent-return, no-unused-vars, comma-dangle, vars-on-top, prefer-template, max-len */
import { localTimeAgo } from './lib/utils/datetime_utility';
import axios from './lib/utils/axios_utils';
export default class Compare {
constructor(opts) {
......@@ -41,17 +42,14 @@ export default class Compare {
}
getTargetProject() {
return $.ajax({
url: this.opts.targetProjectUrl,
data: {
target_project_id: $("input[name='merge_request[target_project_id]']").val()
},
beforeSend: function() {
return $('.mr_target_commit').empty();
$('.mr_target_commit').empty();
return axios.get(this.opts.targetProjectUrl, {
params: {
target_project_id: $("input[name='merge_request[target_project_id]']").val(),
},
success: function(html) {
return $('.js-target-branch-dropdown .dropdown-content').html(html);
}
}).then(({ data }) => {
$('.js-target-branch-dropdown .dropdown-content').html(data);
});
}
......@@ -68,22 +66,19 @@ export default class Compare {
});
}
static sendAjax(url, loading, target, data) {
var $target;
$target = $(target);
return $.ajax({
url: url,
data: data,
beforeSend: function() {
loading.show();
return $target.empty();
},
success: function(html) {
loading.hide();
$target.html(html);
var className = '.' + $target[0].className.replace(' ', '.');
localTimeAgo($('.js-timeago', className));
}
static sendAjax(url, loading, target, params) {
const $target = $(target);
loading.show();
$target.empty();
return axios.get(url, {
params,
}).then(({ data }) => {
loading.hide();
$target.html(data);
const className = '.' + $target[0].className.replace(' ', '.');
localTimeAgo($('.js-timeago', className));
});
}
}
/* eslint-disable func-names, space-before-function-paren, one-var, no-var, one-var-declaration-per-line, object-shorthand, comma-dangle, prefer-arrow-callback, no-else-return, newline-per-chained-call, wrap-iife, max-len */
import { __ } from './locale';
import axios from './lib/utils/axios_utils';
import flash from './flash';
export default function initCompareAutocomplete() {
$('.js-compare-dropdown').each(function() {
......@@ -10,15 +13,14 @@ export default function initCompareAutocomplete() {
const $filterInput = $('input[type="search"]', $dropdownContainer);
$dropdown.glDropdown({
data: function(term, callback) {
return $.ajax({
url: $dropdown.data('refs-url'),
data: {
axios.get($dropdown.data('refsUrl'), {
params: {
ref: $dropdown.data('ref'),
search: term,
}
}).done(function(refs) {
return callback(refs);
});
},
}).then(({ data }) => {
callback(data);
}).catch(() => flash(__('Error fetching refs')));
},
selectable: true,
filterable: true,
......
/* eslint-disable no-new */
import _ from 'underscore';
import axios from './lib/utils/axios_utils';
import Flash from './flash';
import DropLab from './droplab/drop_lab';
import ISetter from './droplab/plugins/input_setter';
......@@ -74,60 +75,52 @@ export default class CreateMergeRequestDropdown {
}
checkAbilityToCreateBranch() {
return $.ajax({
type: 'GET',
dataType: 'json',
url: this.canCreatePath,
beforeSend: () => this.setUnavailableButtonState(),
})
.done((data) => {
this.setUnavailableButtonState(false);
if (data.can_create_branch) {
this.available();
this.enable();
if (!this.droplabInitialized) {
this.droplabInitialized = true;
this.initDroplab();
this.bindEvents();
this.setUnavailableButtonState();
axios.get(this.canCreatePath)
.then(({ data }) => {
this.setUnavailableButtonState(false);
if (data.can_create_branch) {
this.available();
this.enable();
if (!this.droplabInitialized) {
this.droplabInitialized = true;
this.initDroplab();
this.bindEvents();
}
} else if (data.has_related_branch) {
this.hide();
}
} else if (data.has_related_branch) {
this.hide();
}
}).fail(() => {
this.unavailable();
this.disable();
new Flash('Failed to check if a new branch can be created.');
});
})
.catch(() => {
this.unavailable();
this.disable();
Flash('Failed to check if a new branch can be created.');
});
}
createBranch() {
return $.ajax({
method: 'POST',
dataType: 'json',
url: this.createBranchPath,
beforeSend: () => (this.isCreatingBranch = true),
})
.done((data) => {
this.branchCreated = true;
window.location.href = data.url;
})
.fail(() => new Flash('Failed to create a branch for this issue. Please try again.'));
this.isCreatingBranch = true;
return axios.post(this.createBranchPath)
.then(({ data }) => {
this.branchCreated = true;
window.location.href = data.url;
})
.catch(() => Flash('Failed to create a branch for this issue. Please try again.'));
}
createMergeRequest() {
return $.ajax({
method: 'POST',
dataType: 'json',
url: this.createMrPath,
beforeSend: () => (this.isCreatingMergeRequest = true),
})
.done((data) => {
this.mergeRequestCreated = true;
window.location.href = data.url;
})
.fail(() => new Flash('Failed to create Merge Request. Please try again.'));
this.isCreatingMergeRequest = true;
return axios.post(this.createMrPath)
.then(({ data }) => {
this.mergeRequestCreated = true;
window.location.href = data.url;
})
.catch(() => Flash('Failed to create Merge Request. Please try again.'));
}
disable() {
......@@ -200,39 +193,33 @@ export default class CreateMergeRequestDropdown {
getRef(ref, target = 'all') {
if (!ref) return false;
return $.ajax({
method: 'GET',
dataType: 'json',
url: this.refsPath + ref,
beforeSend: () => {
this.isGettingRef = true;
},
})
.always(() => {
this.isGettingRef = false;
})
.done((data) => {
const branches = data[Object.keys(data)[0]];
const tags = data[Object.keys(data)[1]];
let result;
return axios.get(this.refsPath + ref)
.then(({ data }) => {
const branches = data[Object.keys(data)[0]];
const tags = data[Object.keys(data)[1]];
let result;
if (target === 'branch') {
result = CreateMergeRequestDropdown.findByValue(branches, ref);
} else {
result = CreateMergeRequestDropdown.findByValue(branches, ref, true) ||
CreateMergeRequestDropdown.findByValue(tags, ref, true);
this.suggestedRef = result;
}
if (target === 'branch') {
result = CreateMergeRequestDropdown.findByValue(branches, ref);
} else {
result = CreateMergeRequestDropdown.findByValue(branches, ref, true) ||
CreateMergeRequestDropdown.findByValue(tags, ref, true);
this.suggestedRef = result;
}
this.isGettingRef = false;
return this.updateInputState(target, ref, result);
})
.fail(() => {
this.unavailable();
this.disable();
new Flash('Failed to get ref.');
return this.updateInputState(target, ref, result);
})
.catch(() => {
this.unavailable();
this.disable();
new Flash('Failed to get ref.');
return false;
});
this.isGettingRef = false;
return false;
});
}
getTargetData(target) {
......@@ -332,12 +319,12 @@ export default class CreateMergeRequestDropdown {
xhr = this.createBranch();
}
xhr.fail(() => {
xhr.catch(() => {
this.isCreatingMergeRequest = false;
this.isCreatingBranch = false;
});
xhr.always(() => this.enable());
this.enable();
});
this.disable();
}
......
......@@ -2,6 +2,7 @@ import Dropzone from 'dropzone';
import _ from 'underscore';
import './preview_markdown';
import csrf from './lib/utils/csrf';
import axios from './lib/utils/axios_utils';
Dropzone.autoDiscover = false;
......@@ -235,25 +236,21 @@ export default function dropzoneInput(form) {
uploadFile = (item, filename) => {
const formData = new FormData();
formData.append('file', item, filename);
return $.ajax({
url: uploadsPath,
type: 'POST',
data: formData,
dataType: 'json',
processData: false,
contentType: false,
headers: csrf.headers,
beforeSend: () => {
showSpinner();
return closeAlertMessage();
},
success: (e, text, response) => {
const md = response.responseJSON.link.markdown;
showSpinner();
closeAlertMessage();
axios.post(uploadsPath, formData)
.then(({ data }) => {
const md = data.link.markdown;
insertToTextArea(filename, md);
},
error: response => showError(response.responseJSON.message),
complete: () => closeSpinner(),
});
closeSpinner();
})
.catch((e) => {
showError(e.response.data.message);
closeSpinner();
});
};
updateAttachingMessage = (files, messageContainer) => {
......
/* global dateFormat */
import Pikaday from 'pikaday';
import axios from './lib/utils/axios_utils';
import { parsePikadayDate, pikadayToString } from './lib/utils/datefix';
class DueDateSelect {
......@@ -125,37 +126,30 @@ class DueDateSelect {
}
submitSelectedDate(isDropdown) {
return $.ajax({
type: 'PUT',
url: this.issueUpdateURL,
data: this.datePayload,
dataType: 'json',
beforeSend: () => {
const selectedDateValue = this.datePayload[this.abilityName].due_date;
const displayedDateStyle = this.displayedDate !== 'No due date' ? 'bold' : 'no-value';
const selectedDateValue = this.datePayload[this.abilityName].due_date;
const displayedDateStyle = this.displayedDate !== 'No due date' ? 'bold' : 'no-value';
this.$loading.removeClass('hidden').fadeIn();
this.$loading.removeClass('hidden').fadeIn();
if (isDropdown) {
this.$dropdown.trigger('loading.gl.dropdown');
this.$selectbox.hide();
}
if (isDropdown) {
this.$dropdown.trigger('loading.gl.dropdown');
this.$selectbox.hide();
}
this.$value.css('display', '');
this.$valueContent.html(`<span class='${displayedDateStyle}'>${this.displayedDate}</span>`);
this.$sidebarValue.html(this.displayedDate);
this.$value.css('display', '');
this.$valueContent.html(`<span class='${displayedDateStyle}'>${this.displayedDate}</span>`);
this.$sidebarValue.html(this.displayedDate);
return selectedDateValue.length ?
$('.js-remove-due-date-holder').removeClass('hidden') :
$('.js-remove-due-date-holder').addClass('hidden');
},
}).done(() => {
if (isDropdown) {
this.$dropdown.trigger('loaded.gl.dropdown');
this.$dropdown.dropdown('toggle');
}
return this.$loading.fadeOut();
});
$('.js-remove-due-date-holder').toggleClass('hidden', selectedDateValue.length);
return axios.put(this.issueUpdateURL, this.datePayload)
.then(() => {
if (isDropdown) {
this.$dropdown.trigger('loaded.gl.dropdown');
this.$dropdown.dropdown('toggle');
}
return this.$loading.fadeOut();
});
}
}
......
import _ from 'underscore';
import axios from './lib/utils/axios_utils';
/**
* Makes search request for content when user types a value in the search input.
......@@ -54,32 +55,26 @@ export default class FilterableList {
this.listFilterElement.removeEventListener('input', this.debounceFilter);
}
filterResults(queryData) {
filterResults(params) {
if (this.isBusy) {
return false;
}
$(this.listHolderElement).fadeTo(250, 0.5);
return $.ajax({
url: this.getFilterEndpoint(),
data: queryData,
type: 'GET',
dataType: 'json',
context: this,
complete: this.onFilterComplete,
beforeSend: () => {
this.isBusy = true;
},
success: (response, textStatus, xhr) => {
this.onFilterSuccess(response, xhr, queryData);
},
});
this.isBusy = true;
return axios.get(this.getFilterEndpoint(), {
params,
}).then((res) => {
this.onFilterSuccess(res, params);
this.onFilterComplete();
}).catch(() => this.onFilterComplete());
}
onFilterSuccess(response, xhr, queryData) {
if (response.html) {
this.listHolderElement.innerHTML = response.html;
onFilterSuccess(response, queryData) {
if (response.data.html) {
this.listHolderElement.innerHTML = response.data.html;
}
// Change url so if user reload a page - search results are saved
......
import FilterableList from '~/filterable_list';
import eventHub from './event_hub';
import { getParameterByName } from '../lib/utils/common_utils';
import { normalizeHeaders, getParameterByName } from '../lib/utils/common_utils';
export default class GroupFilterableList extends FilterableList {
constructor({ form, filter, holder, filterEndpoint, pagePath, dropdownSel, filterInputField }) {
......@@ -94,23 +94,14 @@ export default class GroupFilterableList extends FilterableList {
this.form.querySelector(`[name="${this.filterInputField}"]`).value = '';
}
onFilterSuccess(data, xhr, queryData) {
onFilterSuccess(res, queryData) {
const currentPath = this.getPagePath(queryData);
const paginationData = {
'X-Per-Page': xhr.getResponseHeader('X-Per-Page'),
'X-Page': xhr.getResponseHeader('X-Page'),
'X-Total': xhr.getResponseHeader('X-Total'),
'X-Total-Pages': xhr.getResponseHeader('X-Total-Pages'),
'X-Next-Page': xhr.getResponseHeader('X-Next-Page'),
'X-Prev-Page': xhr.getResponseHeader('X-Prev-Page'),
};
window.history.replaceState({
page: currentPath,
}, document.title, currentPath);
eventHub.$emit('updateGroups', data, Object.prototype.hasOwnProperty.call(queryData, this.filterInputField));
eventHub.$emit('updatePagination', paginationData);
eventHub.$emit('updateGroups', res.data, Object.prototype.hasOwnProperty.call(queryData, this.filterInputField));
eventHub.$emit('updatePagination', normalizeHeaders(res.headers));
}
}
......@@ -115,9 +115,12 @@ export default {
reordered(event) {
this.removeDraggingCursor();
const { beforeId, afterId } = this.getBeforeAfterId(event.item);
const { oldIndex, newIndex } = event;
this.$emit('saveReorder', {
issueId: parseInt(event.item.dataset.key, 10),
oldIndex,
newIndex,
afterId,
beforeId,
});
......
......@@ -176,7 +176,7 @@ export default {
Flash('An error occurred while fetching issues.');
});
},
saveIssueOrder({ issueId, beforeId, afterId }) {
saveIssueOrder({ issueId, beforeId, afterId, oldIndex, newIndex }) {
const issueToReorder = _.find(this.state.relatedIssues, issue => issue.id === issueId);
if (issueToReorder) {
......@@ -184,7 +184,14 @@ export default {
endpoint: issueToReorder.relation_path,
move_before_id: beforeId,
move_after_id: afterId,
}).catch(() => {
})
.then(res => res.json())
.then((res) => {
if (!res.message) {
this.store.updateIssueOrder(oldIndex, newIndex);
}
})
.catch(() => {
Flash('An error occurred while reordering issues.');
});
}
......
......@@ -16,6 +16,13 @@ class RelatedIssuesStore {
this.state.relatedIssues = this.state.relatedIssues.filter(issue => issue.id !== idToRemove);
}
updateIssueOrder(oldIndex, newIndex) {
if (this.state.relatedIssues.length > 0) {
const updatedIssue = this.state.relatedIssues.splice(oldIndex, 1)[0];
this.state.relatedIssues.splice(newIndex, 0, updatedIssue);
}
}
setPendingReferences(issues) {
this.state.pendingReferences = issues;
}
......
......@@ -76,7 +76,13 @@
.then(data => this.store.storeDeploymentData(data))
.catch(() => new Flash('Error getting deployment information.')),
])
.then(() => { this.showEmptyState = false; })
.then(() => {
if (this.store.groups.length < 1) {
this.state = 'noData';
return;
}
this.showEmptyState = false;
})
.catch(() => { this.state = 'unableToConnect'; });
},
......
......@@ -34,16 +34,23 @@
svgUrl: this.emptyGettingStartedSvgPath,
title: 'Get started with performance monitoring',
description: `Stay updated about the performance and health
of your environment by configuring Prometheus to monitor your deployments.`,
of your environment by configuring Prometheus to monitor your deployments.`,
buttonText: 'Configure Prometheus',
},
loading: {
svgUrl: this.emptyLoadingSvgPath,
title: 'Waiting for performance data',
description: `Creating graphs uses the data from the Prometheus server.
If this takes a long time, ensure that data is available.`,
If this takes a long time, ensure that data is available.`,
buttonText: 'View documentation',
},
noData: {
svgUrl: this.emptyUnableToConnectSvgPath,
title: 'No data found',
description: `You are connected to the Prometheus server, but there is currently
no data to display.`,
buttonText: 'Configure Prometheus',
},
unableToConnect: {
svgUrl: this.emptyUnableToConnectSvgPath,
title: 'Unable to connect to Prometheus server',
......
......@@ -84,7 +84,7 @@ export default {
return !this.showLess || (index < this.defaultRenderCount && this.showLess);
},
avatarUrl(user) {
return user.avatar || user.avatar_url;
return user.avatar || user.avatar_url || gon.default_avatar_url;
},
assigneeUrl(user) {
return `${this.rootPath}${user.username}`;
......
......@@ -492,7 +492,7 @@ function UsersSelect(currentUser, els, options = {}) {
renderRow: function(user) {
var avatar, img, listClosingTags, listWithName, listWithUserName, username;
username = user.username ? "@" + user.username : "";
avatar = user.avatar_url ? user.avatar_url : false;
avatar = user.avatar_url ? user.avatar_url : gon.default_avatar_url;
let selected = false;
......@@ -513,9 +513,7 @@ function UsersSelect(currentUser, els, options = {}) {
if (user.beforeDivider != null) {
`<li><a href='#' class='${selected === true ? 'is-active' : ''}'>${_.escape(user.name)}</a></li>`;
} else {
if (avatar) {
img = "<img src='" + avatar + "' class='avatar avatar-inline' width='32' />";
}
img = "<img src='" + avatar + "' class='avatar avatar-inline' width='32' />";
}
return `
......
......@@ -76,6 +76,7 @@ export default {
<a
href="#modal_merge_info"
data-toggle="modal"
:disabled="mr.sourceBranchRemoved"
class="btn btn-sm inline">
Check out branch
</a>
......
import Flash from '../../../flash';
import mrWidgetAuthorTime from '../../components/mr_widget_author_time';
import tooltip from '../../../vue_shared/directives/tooltip';
import loadingIcon from '../../../vue_shared/components/loading_icon.vue';
import statusIcon from '../mr_widget_status_icon.vue';
import eventHub from '../../event_hub';
export default {
name: 'MRWidgetMerged',
props: {
mr: { type: Object, required: true },
service: { type: Object, required: true },
},
data() {
return {
isMakingRequest: false,
};
},
directives: {
tooltip,
},
components: {
'mr-widget-author-and-time': mrWidgetAuthorTime,
loadingIcon,
statusIcon,
},
computed: {
shouldShowRemoveSourceBranch() {
const { sourceBranchRemoved, isRemovingSourceBranch, canRemoveSourceBranch } = this.mr;
return !sourceBranchRemoved && canRemoveSourceBranch &&
!this.isMakingRequest && !isRemovingSourceBranch;
},
shouldShowSourceBranchRemoving() {
const { sourceBranchRemoved, isRemovingSourceBranch } = this.mr;
return !sourceBranchRemoved && (isRemovingSourceBranch || this.isMakingRequest);
},
shouldShowMergedButtons() {
const { canRevertInCurrentMR, canCherryPickInCurrentMR, revertInForkPath,
cherryPickInForkPath } = this.mr;
return canRevertInCurrentMR || canCherryPickInCurrentMR ||
revertInForkPath || cherryPickInForkPath;
},
},
methods: {
removeSourceBranch() {
this.isMakingRequest = true;
this.service.removeSourceBranch()
.then(res => res.data)
.then((data) => {
if (data.message === 'Branch was removed') {
eventHub.$emit('MRWidgetUpdateRequested', () => {
this.isMakingRequest = false;
});
}
})
.catch(() => {
this.isMakingRequest = false;
new Flash('Something went wrong. Please try again.'); // eslint-disable-line
});
},
},
template: `
<div class="mr-widget-body media">
<status-icon status="success" />
<div class="media-body">
<div class="space-children">
<mr-widget-author-and-time
actionText="Merged by"
:author="mr.metrics.mergedBy"
:date-title="mr.metrics.mergedAt"
:date-readable="mr.metrics.readableMergedAt" />
<a
v-if="mr.canRevertInCurrentMR"
v-tooltip
class="btn btn-close btn-xs"
href="#modal-revert-commit"
data-toggle="modal"
data-container="body"
title="Revert this merge request in a new merge request">
Revert
</a>
<a
v-else-if="mr.revertInForkPath"
v-tooltip
class="btn btn-close btn-xs"
data-method="post"
:href="mr.revertInForkPath"
title="Revert this merge request in a new merge request">
Revert
</a>
<a
v-if="mr.canCherryPickInCurrentMR"
v-tooltip
class="btn btn-default btn-xs"
href="#modal-cherry-pick-commit"
data-toggle="modal"
data-container="body"
title="Cherry-pick this merge request in a new merge request">
Cherry-pick
</a>
<a
v-else-if="mr.cherryPickInForkPath"
v-tooltip
class="btn btn-default btn-xs"
data-method="post"
:href="mr.cherryPickInForkPath"
title="Cherry-pick this merge request in a new merge request">
Cherry-pick
</a>
</div>
<section class="mr-info-list">
<p>
The changes were merged into
<span class="label-branch">
<a :href="mr.targetBranchPath">{{mr.targetBranch}}</a>
</span>
</p>
<p v-if="mr.sourceBranchRemoved">The source branch has been removed</p>
<p v-if="shouldShowRemoveSourceBranch" class="space-children">
<span>You can remove source branch now</span>
<button
@click="removeSourceBranch"
:disabled="isMakingRequest"
type="button"
class="btn btn-xs btn-default js-remove-branch-button">
Remove Source Branch
</button>
</p>
<p v-if="shouldShowSourceBranchRemoving">
<loading-icon inline />
<span>The source branch is being removed</span>
</p>
</section>
</div>
</div>
`,
};
<script>
import Flash from '~/flash';
import tooltip from '~/vue_shared/directives/tooltip';
import loadingIcon from '~/vue_shared/components/loading_icon.vue';
import { s__, __ } from '~/locale';
import mrWidgetAuthorTime from '../../components/mr_widget_author_time';
import statusIcon from '../mr_widget_status_icon.vue';
import eventHub from '../../event_hub';
export default {
name: 'MRWidgetMerged',
directives: {
tooltip,
},
components: {
mrWidgetAuthorTime,
loadingIcon,
statusIcon,
},
props: {
mr: {
type: Object,
required: true,
default: () => ({}),
},
service: {
type: Object,
required: true,
default: () => ({}),
},
},
data() {
return {
isMakingRequest: false,
};
},
computed: {
shouldShowRemoveSourceBranch() {
const {
sourceBranchRemoved,
isRemovingSourceBranch,
canRemoveSourceBranch,
} = this.mr;
return !sourceBranchRemoved &&
canRemoveSourceBranch &&
!this.isMakingRequest &&
!isRemovingSourceBranch;
},
shouldShowSourceBranchRemoving() {
const {
sourceBranchRemoved,
isRemovingSourceBranch,
} = this.mr;
return !sourceBranchRemoved &&
(isRemovingSourceBranch || this.isMakingRequest);
},
shouldShowMergedButtons() {
const {
canRevertInCurrentMR,
canCherryPickInCurrentMR,
revertInForkPath,
cherryPickInForkPath,
} = this.mr;
return canRevertInCurrentMR ||
canCherryPickInCurrentMR ||
revertInForkPath ||
cherryPickInForkPath;
},
revertTitle() {
return s__('mrWidget|Revert this merge request in a new merge request');
},
cherryPickTitle() {
return s__('mrWidget|Cherry-pick this merge request in a new merge request');
},
revertLabel() {
return s__('mrWidget|Revert');
},
cherryPickLabel() {
return s__('mrWidget|Cherry-pick');
},
},
methods: {
removeSourceBranch() {
this.isMakingRequest = true;
this.service.removeSourceBranch()
.then(res => res.data)
.then((data) => {
if (data.message === 'Branch was removed') {
eventHub.$emit('MRWidgetUpdateRequested', () => {
this.isMakingRequest = false;
});
}
})
.catch(() => {
this.isMakingRequest = false;
Flash(__('Something went wrong. Please try again.'));
});
},
},
};
</script>
<template>
<div class="mr-widget-body media">
<status-icon status="success" />
<div class="media-body">
<div class="space-children">
<mr-widget-author-time
:action-text="s__('mrWidget|Merged by')"
:author="mr.metrics.mergedBy"
:date-title="mr.metrics.mergedAt"
:date-readable="mr.metrics.readableMergedAt"
/>
<a
v-if="mr.canRevertInCurrentMR"
v-tooltip
class="btn btn-close btn-xs"
href="#modal-revert-commit"
data-toggle="modal"
data-container="body"
:title="revertTitle"
>
{{ revertLabel }}
</a>
<a
v-else-if="mr.revertInForkPath"
v-tooltip
class="btn btn-close btn-xs"
data-method="post"
:href="mr.revertInForkPath"
:title="revertTitle"
>
{{ revertLabel }}
</a>
<a
v-if="mr.canCherryPickInCurrentMR"
v-tooltip
class="btn btn-default btn-xs"
href="#modal-cherry-pick-commit"
data-toggle="modal"
data-container="body"
:title="cherryPickTitle"
>
{{ cherryPickLabel }}
</a>
<a
v-else-if="mr.cherryPickInForkPath"
v-tooltip
class="btn btn-default btn-xs"
data-method="post"
:href="mr.cherryPickInForkPath"
:title="cherryPickTitle"
>
{{ cherryPickLabel }}
</a>
</div>
<section class="mr-info-list">
<p>
{{ s__("mrWidget|The changes were merged into") }}
<span class="label-branch">
<a :href="mr.targetBranchPath">{{ mr.targetBranch }}</a>
</span>
</p>
<p v-if="mr.sourceBranchRemoved">
{{ s__("mrWidget|The source branch has been removed") }}
</p>
<p
v-if="shouldShowRemoveSourceBranch"
class="space-children"
>
<span>{{ s__("mrWidget|You can remove source branch now") }}</span>
<button
@click="removeSourceBranch"
:disabled="isMakingRequest"
type="button"
class="btn btn-xs btn-default js-remove-branch-button"
>
{{ s__("mrWidget|Remove Source Branch") }}
</button>
</p>
<p v-if="shouldShowSourceBranchRemoving">
<loading-icon :inline="true" />
<span>
{{ s__("mrWidget|The source branch is being removed") }}
</span>
</p>
</section>
</div>
</div>
</template>
......@@ -16,7 +16,7 @@ export { default as WidgetMergeHelp } from './components/mr_widget_merge_help';
export { default as WidgetPipeline } from './components/mr_widget_pipeline.vue';
export { default as WidgetDeployment } from './components/mr_widget_deployment';
export { default as WidgetRelatedLinks } from './components/mr_widget_related_links';
export { default as MergedState } from './components/states/mr_widget_merged';
export { default as MergedState } from './components/states/mr_widget_merged.vue';
export { default as FailedToMerge } from './components/states/mr_widget_failed_to_merge.vue';
export { default as ClosedState } from './components/states/mr_widget_closed.vue';
export { default as MergingState } from './components/states/mr_widget_merging.vue';
......
......@@ -89,7 +89,7 @@ module ApplicationHelper
end
def default_avatar
'no_avatar.png'
asset_path('no_avatar.png')
end
def last_commit(project)
......
......@@ -476,7 +476,7 @@ module Ci
if cache && project.jobs_cache_index
cache = cache.merge(
key: "#{cache[:key]}:#{project.jobs_cache_index}")
key: "#{cache[:key]}_#{project.jobs_cache_index}")
end
[cache]
......
......@@ -1016,13 +1016,13 @@ class MergeRequest < ActiveRecord::Base
merged_at = metrics&.merged_at
notes_association = notes_with_associations
# It is not guaranteed that Note#created_at will be strictly later than
# MergeRequestMetric#merged_at. Nanoseconds on MySQL may break this
# comparison, as will a HA environment if clocks are not *precisely*
# synchronized. Add a minute's leeway to compensate for both possibilities
cutoff = merged_at - 1.minute
if merged_at
# It is not guaranteed that Note#created_at will be strictly later than
# MergeRequestMetric#merged_at. Nanoseconds on MySQL may break this
# comparison, as will a HA environment if clocks are not *precisely*
# synchronized. Add a minute's leeway to compensate for both possibilities
cutoff = merged_at - 1.minute
notes_association = notes_association.where('created_at >= ?', cutoff)
end
......
......@@ -43,7 +43,7 @@ class JiraService < IssueTrackerService
username: self.username,
password: self.password,
site: URI.join(url, '/').to_s,
context_path: url.path,
context_path: url.path.chomp('/'),
auth_type: :basic,
read_timeout: 120,
use_cookies: true,
......
......@@ -9,7 +9,8 @@ module MergeRequests
Gitlab::GitalyClient.allow_n_plus_1_calls(&method(:find_new_commits))
# Be sure to close outstanding MRs before reloading them to avoid generating an
# empty diff during a manual merge
close_merge_requests
close_upon_missing_source_branch_ref
post_merge_manually_merged
reload_merge_requests
reset_merge_when_pipeline_succeeds
mark_pending_todos_done
......@@ -30,11 +31,22 @@ module MergeRequests
private
def close_upon_missing_source_branch_ref
# MergeRequest#reload_diff ignores not opened MRs. This means it won't
# create an `empty` diff for `closed` MRs without a source branch, keeping
# the latest diff state as the last _valid_ one.
merge_requests_for_source_branch.reject(&:source_branch_exists?).each do |mr|
MergeRequests::CloseService
.new(mr.target_project, @current_user)
.execute(mr)
end
end
# Collect open merge requests that target same branch we push into
# and close if push to master include last commit from merge request
# We need this to close(as merged) merge requests that were merged into
# target branch manually
def close_merge_requests
def post_merge_manually_merged
commit_ids = @commits.map(&:id)
merge_requests = @project.merge_requests.preload(:latest_merge_request_diff).opened.where(target_branch: @branch_name).to_a
merge_requests = merge_requests.select(&:diff_head_commit)
......
......@@ -14,5 +14,5 @@
#{time_ago_with_tooltip(event.created_at)}
.pull-right
= link_to new_mr_path_from_push_event(event), title: _("New merge request"), class: "btn btn-info btn-sm" do
= link_to new_mr_path_from_push_event(event), title: _("New merge request"), class: "btn btn-info btn-sm qa-create-merge-request" do
#{ _('Create merge request') }
......@@ -15,7 +15,7 @@
= render layout: 'projects/md_preview', locals: { url: preview_url, referenced_users: true } do
= render 'projects/zen', f: form, attr: :description,
classes: 'note-textarea',
classes: 'note-textarea qa-issuable-form-description',
placeholder: "Write a comment or drag your files here...",
supports_quick_actions: supports_quick_actions
= render 'shared/notes/hints', supports_quick_actions: supports_quick_actions
......
......@@ -67,7 +67,7 @@
%span.append-right-10
- if issuable.new_record?
= form.submit "Submit #{issuable.class.model_name.human.downcase}", class: 'btn btn-create'
= form.submit "Submit #{issuable.class.model_name.human.downcase}", class: 'btn btn-create qa-issuable-create-button'
- else
= form.submit 'Save changes', class: 'btn btn-save'
......
......@@ -6,7 +6,7 @@
%div{ class: div_class }
= form.text_field :title, required: true, maxlength: 255, autofocus: true,
autocomplete: 'off', class: 'form-control pad'
autocomplete: 'off', class: 'form-control pad qa-issuable-form-title'
- if issuable.respond_to?(:work_in_progress?)
%p.help-block
......
require_relative "../lib/gitlab/upgrader"
Gitlab::Upgrader.new.execute
---
title: Update behavior of MR widgets that require pipeline artifacts to allow jobs
with multiple artifacts
merge_request: 4203
author:
type: changed
---
title: Remove unaproved typo check in sast:container report
merge_request:
author:
type: other
---
title: Fix Epic issue item reordering to handle different scenarios
merge_request: 4142
author:
type: fixed
---
title: Use a fixed remote name for Geo mirrors
merge_request: 4249
author:
type: fixed
---
title: Allow project to be set up to push to and pull from same mirror
merge_request:
author:
type: fixed
---
title: Fix visually broken admin dashboard until license is added
merge_request: 4196
author:
type: fixed
---
title: Handle empty event timestamp and larger memory units
merge_request: 4206
author:
type: fixed
---
title: Fix copy/paste on iOS devices due to a bug in webkit
merge_request: 15804
author:
type: fixed
---
title: Fix default avatar icon missing when Gravatar is disabled
merge_request: 16681
author: Felix Geyer
type: fixed
---
title: Fix missing "allow users to request access" option in public project permissions
merge_request: 16485
author:
type: fixed
---
title: Fix encoding issue when counting commit count
merge_request: 16637
author:
type: fixed
---
title: Disable MR check out button when source branch is deleted
merge_request: 16631
author: Jacopo Beschi @jacopo-beschi
type: fixed
---
title: Fixes destination already exists, and some particular service errors on Import/Export
error
merge_request: 16714
author:
type: fixed
---
title: Geo - Fix OPENSSH_EXPECTED_COMMAND in the geo:check rake task
title: Close and do not reload MR diffs when source branch is deleted
merge_request:
author:
type: fixed
---
title: Use has_table_privilege for TRIGGER on PostgreSQL
title: Return more consistent values for merge_status on MR APIs
merge_request:
author:
type: fixed
---
title: Execute group hooks after-commit when moving an issue
title: Fix JIRA not working when a trailing slash is included
merge_request:
author:
type: fixed
......@@ -8,6 +8,7 @@ require 'elasticsearch/rails/instrumentation'
module Gitlab
class Application < Rails::Application
require_dependency Rails.root.join('lib/gitlab/redis/wrapper')
require_dependency Rails.root.join('lib/gitlab/redis/cache')
require_dependency Rails.root.join('lib/gitlab/redis/queues')
require_dependency Rails.root.join('lib/gitlab/redis/shared_state')
......
......@@ -496,7 +496,7 @@ more of the following options:
- `BACKUP=timestamp_of_backup` - Required if more than one backup exists.
Read what the [backup timestamp is about](#backup-timestamp).
- `force=yes` - Do not ask if the authorized_keys file should get regenerated.
- `force=yes` - Does not ask if the authorized_keys file should get regenerated and assumes 'yes' for warning that database tables will be removed.
### Restore for installation from source
......
......@@ -38,10 +38,7 @@ In order for the report to show in the merge request, you need to specify a
`codeclimate.json` as an artifact. GitLab will then check this file and show
the information inside the merge request.
`codeclimate.json` needs to be the only artifact file for the job. If you try
to also include other files, like Code Climate's HTML report, it will break the
Code Climate display in the merge request.
>**Note:**
If the Code Climate report doesn't have anything to compare to, no information
will be displayed in the merge request area. That is the case when you add the
`codequality` job in your `.gitlab-ci.yml` for the very first time.
......
......@@ -117,10 +117,7 @@ export default class MergeRequestStore extends CEMergeRequestStore {
this.dockerReport.vulnerabilities = parsedVulnerabilities || [];
// There is a typo in the original repo:
// https://github.com/arminc/clair-scanner/pull/39/files
// Fix this when the above PR is accepted
const unapproved = data.unapproved || data.unaproved || [];
const unapproved = data.unapproved || [];
// Approved can be calculated by subtracting unapproved from vulnerabilities.
this.dockerReport.approved = parsedVulnerabilities
......
......@@ -63,7 +63,7 @@ module EE
private
def has_artifact?(name)
options.dig(:artifacts, :paths) == [name] &&
options.dig(:artifacts, :paths)&.include?(name) &&
artifacts_metadata?
end
end
......
......@@ -334,12 +334,6 @@ module EE
repository.async_remove_remote(::Repository::MIRROR_REMOTE)
end
def import_url_availability
if remote_mirrors.find_by(url: import_url)
errors.add(:import_url, 'is already in use by a remote mirror')
end
end
def username_only_import_url
bare_url = read_attribute(:import_url)
return bare_url unless ::Gitlab::UrlSanitizer.valid?(bare_url)
......
......@@ -17,8 +17,6 @@ class RemoteMirror < ActiveRecord::Base
belongs_to :project, inverse_of: :remote_mirrors
validates :url, presence: true, url: { protocols: %w(ssh git http https), allow_blank: true }
validate :url_availability, if: -> (mirror) { mirror.url_changed? || mirror.enabled? }
validates :url, addressable_url: true, if: :url_changed?
before_save :set_new_remote_name, if: :mirror_url_changed?
......@@ -174,14 +172,6 @@ class RemoteMirror < ActiveRecord::Base
end
end
def url_availability
return unless project
if project.import_url == url && project.mirror?
errors.add(:url, 'is already in use')
end
end
def reset_fields
update_columns(
last_error: nil,
......
......@@ -575,7 +575,15 @@ module API
expose :work_in_progress?, as: :work_in_progress
expose :milestone, using: Entities::Milestone
expose :merge_when_pipeline_succeeds
expose :merge_status
# Ideally we should deprecate `MergeRequest#merge_status` exposure and
# use `MergeRequest#mergeable?` instead (boolean).
# See https://gitlab.com/gitlab-org/gitlab-ce/issues/42344 for more
# information.
expose :merge_status do |merge_request|
merge_request.check_if_can_be_merged
merge_request.merge_status
end
expose :diff_head_sha, as: :sha
expose :merge_commit_sha
expose :user_notes_count
......
......@@ -468,9 +468,13 @@ module Gitlab
}
options = default_options.merge(options)
options[:limit] ||= 0
options[:offset] ||= 0
limit = options[:limit]
if limit == 0 || !limit.is_a?(Integer)
raise ArgumentError.new("invalid Repository#log limit: #{limit.inspect}")
end
gitaly_migrate(:find_commits) do |is_enabled|
if is_enabled
gitaly_commit_client.find_commits(options)
......
......@@ -5,7 +5,17 @@ module Gitlab
module Popen
extend self
def popen(cmd, path = nil, vars = {})
Result = Struct.new(:cmd, :stdout, :stderr, :status, :duration)
# Returns [stdout + stderr, status]
def popen(cmd, path = nil, vars = {}, &block)
result = popen_with_detail(cmd, path, vars, &block)
[result.stdout << result.stderr, result.status&.exitstatus]
end
# Returns Result
def popen_with_detail(cmd, path = nil, vars = {})
unless cmd.is_a?(Array)
raise "System commands must be given as an array of strings"
end
......@@ -18,18 +28,21 @@ module Gitlab
FileUtils.mkdir_p(path)
end
cmd_output = ""
cmd_status = 0
cmd_stdout = ''
cmd_stderr = ''
cmd_status = nil
start = Time.now
Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|
yield(stdin) if block_given?
stdin.close
cmd_output << stdout.read
cmd_output << stderr.read
cmd_status = wait_thr.value.exitstatus
cmd_stdout = stdout.read
cmd_stderr = stderr.read
cmd_status = wait_thr.value
end
[cmd_output, cmd_status]
Result.new(cmd, cmd_stdout, cmd_stderr, cmd_status, Time.now - start)
end
end
end
module Gitlab
module Popen
class Runner
attr_reader :results
def initialize
@results = []
end
def run(commands, &block)
commands.each do |cmd|
# yield doesn't support blocks, so we need to use a block variable
block.call(cmd) do # rubocop:disable Performance/RedundantBlockCall
cmd_result = Gitlab::Popen.popen_with_detail(cmd)
results << cmd_result
cmd_result
end
end
end
def all_success_and_clean?
all_success? && all_stderr_empty?
end
def all_success?
results.all? { |result| result.status.success? }
end
def all_stderr_empty?
results.all? { |result| result.stderr.empty? }
end
def failed_results
results.reject { |result| result.status.success? }
end
def warned_results
results.select do |result|
result.status.success? && !result.stderr.empty?
end
end
end
end
end
# please require all dependencies below:
require_relative 'wrapper' unless defined?(::Gitlab::Redis::Wrapper)
require_relative 'wrapper' unless defined?(::Rails) && ::Rails.root.present?
module Gitlab
module Redis
......
......@@ -5,9 +5,15 @@ module DeliverNever
end
end
module MuteNotifications
def new_note(note)
end
end
module Gitlab
class Seeder
def self.quiet
mute_notifications
mute_mailer
SeedFu.quiet = true
......@@ -18,6 +24,10 @@ module Gitlab
puts "\nOK".color(:green)
end
def self.mute_notifications
NotificationService.prepend(MuteNotifications)
end
def self.mute_mailer
ActionMailer::MessageDelivery.prepend(DeliverNever)
end
......
require 'rainbow/ext/string'
require 'gitlab/utils/strong_memoize'
# rubocop:disable Rails/Output
module Gitlab
TaskFailedError = Class.new(StandardError)
TaskAbortedByUserError = Class.new(StandardError)
......@@ -96,11 +97,9 @@ module Gitlab
end
def gid_for(group_name)
begin
Etc.getgrnam(group_name).gid
rescue ArgumentError # no group
"group #{group_name} doesn't exist"
end
Etc.getgrnam(group_name).gid
rescue ArgumentError # no group
"group #{group_name} doesn't exist"
end
def gitlab_user
......
require_relative "popen"
require_relative "version_info"
module Gitlab
class Upgrader
def execute
......
......@@ -17,6 +17,8 @@
## See installation.md#using-https for additional HTTPS configuration details.
upstream gitlab-workhorse {
# Gitlab socket file,
# for Omnibus this would be: unix:/var/opt/gitlab/gitlab-workhorse/socket
server unix:/home/git/gitlab/tmp/sockets/gitlab-workhorse.socket fail_timeout=0;
}
......@@ -110,6 +112,8 @@ server {
error_page 502 /502.html;
error_page 503 /503.html;
location ~ ^/(404|422|500|502|503)\.html$ {
# Location to the Gitlab's public directory,
# for Omnibus this would be: /opt/gitlab/embedded/service/gitlab-rails/public.
root /home/git/gitlab/public;
internal;
}
......
......@@ -21,6 +21,8 @@
## See installation.md#using-https for additional HTTPS configuration details.
upstream gitlab-workhorse {
# Gitlab socket file,
# for Omnibus this would be: unix:/var/opt/gitlab/gitlab-workhorse/socket
server unix:/home/git/gitlab/tmp/sockets/gitlab-workhorse.socket fail_timeout=0;
}
......@@ -160,6 +162,8 @@ server {
error_page 502 /502.html;
error_page 503 /503.html;
location ~ ^/(404|422|500|502|503)\.html$ {
# Location to the Gitlab's public directory,
# for Omnibus this would be: /opt/gitlab/embedded/service/gitlab-rails/public
root /home/git/gitlab/public;
internal;
}
......
require 'tasks/gitlab/task_helpers'
module SystemCheck
module Helpers
include ::Gitlab::TaskHelpers
......
desc 'Code duplication analyze via flay'
task :flay do
output = `bundle exec flay --mass 35 app/ lib/gitlab/`
output = `bundle exec flay --mass 35 app/ lib/gitlab/ 2> #{File::NULL}`
if output.include? "Similar code found"
puts output
......
......@@ -4,7 +4,7 @@ namespace :gitlab do
namespace :backup do
# Create backup of GitLab system
desc "GitLab | Create a backup of the GitLab system"
task create: :environment do
task create: :gitlab_environment do
warn_user_is_not_gitlab
configure_cron_mode
......@@ -25,7 +25,7 @@ namespace :gitlab do
# Restore backup of GitLab system
desc 'GitLab | Restore a previously created backup'
task restore: :environment do
task restore: :gitlab_environment do
warn_user_is_not_gitlab
configure_cron_mode
......@@ -73,7 +73,7 @@ namespace :gitlab do
end
namespace :repo do
task create: :environment do
task create: :gitlab_environment do
$progress.puts "Dumping repositories ...".color(:blue)
if ENV["SKIP"] && ENV["SKIP"].include?("repositories")
......@@ -84,7 +84,7 @@ namespace :gitlab do
end
end
task restore: :environment do
task restore: :gitlab_environment do
$progress.puts "Restoring repositories ...".color(:blue)
Backup::Repository.new.restore
$progress.puts "done".color(:green)
......@@ -92,7 +92,7 @@ namespace :gitlab do
end
namespace :db do
task create: :environment do
task create: :gitlab_environment do
$progress.puts "Dumping database ... ".color(:blue)
if ENV["SKIP"] && ENV["SKIP"].include?("db")
......@@ -103,7 +103,7 @@ namespace :gitlab do
end
end
task restore: :environment do
task restore: :gitlab_environment do
$progress.puts "Restoring database ... ".color(:blue)
Backup::Database.new.restore
$progress.puts "done".color(:green)
......@@ -111,7 +111,7 @@ namespace :gitlab do
end
namespace :builds do
task create: :environment do
task create: :gitlab_environment do
$progress.puts "Dumping builds ... ".color(:blue)
if ENV["SKIP"] && ENV["SKIP"].include?("builds")
......@@ -122,7 +122,7 @@ namespace :gitlab do
end
end
task restore: :environment do
task restore: :gitlab_environment do
$progress.puts "Restoring builds ... ".color(:blue)
Backup::Builds.new.restore
$progress.puts "done".color(:green)
......@@ -130,7 +130,7 @@ namespace :gitlab do
end
namespace :uploads do
task create: :environment do
task create: :gitlab_environment do
$progress.puts "Dumping uploads ... ".color(:blue)
if ENV["SKIP"] && ENV["SKIP"].include?("uploads")
......@@ -141,7 +141,7 @@ namespace :gitlab do
end
end
task restore: :environment do
task restore: :gitlab_environment do
$progress.puts "Restoring uploads ... ".color(:blue)
Backup::Uploads.new.restore
$progress.puts "done".color(:green)
......@@ -149,7 +149,7 @@ namespace :gitlab do
end
namespace :artifacts do
task create: :environment do
task create: :gitlab_environment do
$progress.puts "Dumping artifacts ... ".color(:blue)
if ENV["SKIP"] && ENV["SKIP"].include?("artifacts")
......@@ -160,7 +160,7 @@ namespace :gitlab do
end
end
task restore: :environment do
task restore: :gitlab_environment do
$progress.puts "Restoring artifacts ... ".color(:blue)
Backup::Artifacts.new.restore
$progress.puts "done".color(:green)
......@@ -168,7 +168,7 @@ namespace :gitlab do
end
namespace :pages do
task create: :environment do
task create: :gitlab_environment do
$progress.puts "Dumping pages ... ".color(:blue)
if ENV["SKIP"] && ENV["SKIP"].include?("pages")
......@@ -179,7 +179,7 @@ namespace :gitlab do
end
end
task restore: :environment do
task restore: :gitlab_environment do
$progress.puts "Restoring pages ... ".color(:blue)
Backup::Pages.new.restore
$progress.puts "done".color(:green)
......@@ -187,7 +187,7 @@ namespace :gitlab do
end
namespace :lfs do
task create: :environment do
task create: :gitlab_environment do
$progress.puts "Dumping lfs objects ... ".color(:blue)
if ENV["SKIP"] && ENV["SKIP"].include?("lfs")
......@@ -198,7 +198,7 @@ namespace :gitlab do
end
end
task restore: :environment do
task restore: :gitlab_environment do
$progress.puts "Restoring lfs objects ... ".color(:blue)
Backup::Lfs.new.restore
$progress.puts "done".color(:green)
......@@ -206,7 +206,7 @@ namespace :gitlab do
end
namespace :registry do
task create: :environment do
task create: :gitlab_environment do
$progress.puts "Dumping container registry images ... ".color(:blue)
if Gitlab.config.registry.enabled
......@@ -221,7 +221,7 @@ namespace :gitlab do
end
end
task restore: :environment do
task restore: :gitlab_environment do
$progress.puts "Restoring container registry images ... ".color(:blue)
if Gitlab.config.registry.enabled
......
# Temporary hack, until we migrate all checks to SystemCheck format
require 'system_check'
require 'system_check/helpers'
namespace :gitlab do
desc 'GitLab | Check the configuration of GitLab and its environment'
task check: %w{gitlab:gitlab_shell:check
......@@ -12,7 +8,7 @@ namespace :gitlab do
namespace :app do
desc 'GitLab | Check the configuration of the GitLab Rails app'
task check: :environment do
task check: :gitlab_environment do
warn_user_is_not_gitlab
checks = [
......@@ -46,7 +42,7 @@ namespace :gitlab do
namespace :gitlab_shell do
desc "GitLab | Check the configuration of GitLab Shell"
task check: :environment do
task check: :gitlab_environment do
warn_user_is_not_gitlab
start_checking "GitLab Shell"
......@@ -254,7 +250,7 @@ namespace :gitlab do
namespace :sidekiq do
desc "GitLab | Check the configuration of Sidekiq"
task check: :environment do
task check: :gitlab_environment do
warn_user_is_not_gitlab
start_checking "Sidekiq"
......@@ -313,7 +309,7 @@ namespace :gitlab do
namespace :incoming_email do
desc "GitLab | Check the configuration of Reply by email"
task check: :environment do
task check: :gitlab_environment do
warn_user_is_not_gitlab
if Gitlab.config.incoming_email.enabled
......@@ -336,7 +332,7 @@ namespace :gitlab do
end
namespace :ldap do
task :check, [:limit] => :environment do |_, args|
task :check, [:limit] => :gitlab_environment do |_, args|
# Only show up to 100 results because LDAP directories can be very big.
# This setting only affects the `rake gitlab:check` script.
args.with_defaults(limit: 100)
......@@ -392,7 +388,7 @@ namespace :gitlab do
namespace :repo do
desc "GitLab | Check the integrity of the repositories managed by GitLab"
task check: :environment do
task check: :gitlab_environment do
puts "This task is deprecated. Please use gitlab:git:fsck instead".color(:red)
Rake::Task["gitlab:git:fsck"].execute
end
......@@ -400,7 +396,7 @@ namespace :gitlab do
namespace :orphans do
desc 'Gitlab | Check for orphaned namespaces and repositories'
task check: :environment do
task check: :gitlab_environment do
warn_user_is_not_gitlab
checks = [
SystemCheck::Orphans::NamespaceCheck,
......@@ -411,7 +407,7 @@ namespace :gitlab do
end
desc 'GitLab | Check for orphaned namespaces in the repositories path'
task check_namespaces: :environment do
task check_namespaces: :gitlab_environment do
warn_user_is_not_gitlab
checks = [SystemCheck::Orphans::NamespaceCheck]
......@@ -419,7 +415,7 @@ namespace :gitlab do
end
desc 'GitLab | Check for orphaned repositories in the repositories path'
task check_repositories: :environment do
task check_repositories: :gitlab_environment do
warn_user_is_not_gitlab
checks = [SystemCheck::Orphans::RepositoryCheck]
......@@ -429,7 +425,7 @@ namespace :gitlab do
namespace :user do
desc "GitLab | Check the integrity of a specific user's repositories"
task :check_repos, [:username] => :environment do |t, args|
task :check_repos, [:username] => :gitlab_environment do |t, args|
username = args[:username] || prompt("Check repository integrity for username? ".color(:blue))
user = User.find_by(username: username)
if user
......
......@@ -5,7 +5,7 @@ namespace :gitlab do
HASHED_REPOSITORY_NAME = '@hashed'.freeze
desc "GitLab | Cleanup | Clean namespaces"
task dirs: :environment do
task dirs: :gitlab_environment do
warn_user_is_not_gitlab
remove_flag = ENV['REMOVE']
......@@ -79,7 +79,7 @@ namespace :gitlab do
end
desc "GitLab | Cleanup | Clean repositories"
task repos: :environment do
task repos: :gitlab_environment do
warn_user_is_not_gitlab
move_suffix = "+orphaned+#{Time.now.to_i}"
......@@ -108,7 +108,7 @@ namespace :gitlab do
end
desc "GitLab | Cleanup | Block users that have been removed in LDAP"
task block_removed_ldap_users: :environment do
task block_removed_ldap_users: :gitlab_environment do
warn_user_is_not_gitlab
block_flag = ENV['BLOCK']
......@@ -139,7 +139,7 @@ namespace :gitlab do
# released. So likely this should only be run once on gitlab.com
# Faulty refs are moved so they are kept around, else some features break.
desc 'GitLab | Cleanup | Remove faulty deployment refs'
task move_faulty_deployment_refs: :environment do
task move_faulty_deployment_refs: :gitlab_environment do
projects = Project.where(id: Deployment.select(:project_id).distinct)
projects.find_each do |project|
......
namespace :gitlab do
namespace :git do
desc "GitLab | Git | Repack"
task repack: :environment do
task repack: :gitlab_environment do
failures = perform_git_cmd(%W(#{Gitlab.config.git.bin_path} repack -a --quiet), "Repacking repo")
if failures.empty?
puts "Done".color(:green)
......@@ -11,7 +11,7 @@ namespace :gitlab do
end
desc "GitLab | Git | Run garbage collection on all repos"
task gc: :environment do
task gc: :gitlab_environment do
failures = perform_git_cmd(%W(#{Gitlab.config.git.bin_path} gc --auto --quiet), "Garbage Collecting")
if failures.empty?
puts "Done".color(:green)
......@@ -21,7 +21,7 @@ namespace :gitlab do
end
desc "GitLab | Git | Prune all repos"
task prune: :environment do
task prune: :gitlab_environment do
failures = perform_git_cmd(%W(#{Gitlab.config.git.bin_path} prune), "Git Prune")
if failures.empty?
puts "Done".color(:green)
......@@ -31,7 +31,7 @@ namespace :gitlab do
end
desc 'GitLab | Git | Check all repos integrity'
task fsck: :environment do
task fsck: :gitlab_environment do
failures = perform_git_cmd(%W(#{Gitlab.config.git.bin_path} fsck --name-objects --no-progress), "Checking integrity") do |repo|
check_config_lock(repo)
check_ref_locks(repo)
......
namespace :gitlab do
namespace :gitaly do
desc "GitLab | Install or upgrade gitaly"
task :install, [:dir, :repo] => :environment do |t, args|
task :install, [:dir, :repo] => :gitlab_environment do |t, args|
require 'toml'
warn_user_is_not_gitlab
......
require 'tasks/gitlab/task_helpers'
# Prevent StateMachine warnings from outputting during a cron task
StateMachines::Machine.ignore_method_conflicts = true if ENV['CRON']
namespace :gitlab do
task gitlab_environment: :environment do
extend SystemCheck::Helpers
end
namespace :gitlab do
namespace :env do
desc "GitLab | Show information about GitLab and its environment"
task info: :environment do
task info: :gitlab_environment do
# check if there is an RVM environment
rvm_version = run_and_match(%w(rvm --version), /[\d\.]+/).try(:to_s)
# check Ruby version
......
namespace :gitlab do
desc "GitLab | Setup production application"
task setup: :environment do
task setup: :gitlab_environment do
setup_db
end
......
namespace :gitlab do
namespace :shell do
desc "GitLab | Install or upgrade gitlab-shell"
task :install, [:repo] => :environment do |t, args|
task :install, [:repo] => :gitlab_environment do |t, args|
warn_user_is_not_gitlab
default_version = Gitlab::Shell.version_required
......@@ -58,12 +58,12 @@ namespace :gitlab do
end
desc "GitLab | Setup gitlab-shell"
task setup: :environment do
task setup: :gitlab_environment do
setup
end
desc "GitLab | Build missing projects"
task build_missing_projects: :environment do
task build_missing_projects: :gitlab_environment do
Project.find_each(batch_size: 1000) do |project|
path_to_repo = project.repository.path_to_repo
if File.exist?(path_to_repo)
......@@ -80,7 +80,7 @@ namespace :gitlab do
end
desc 'Create or repair repository hooks symlink'
task create_hooks: :environment do
task create_hooks: :gitlab_environment do
warn_user_is_not_gitlab
puts 'Creating/Repairing hooks symlinks for all repositories'
......
namespace :gitlab do
namespace :workhorse do
desc "GitLab | Install or upgrade gitlab-workhorse"
task :install, [:dir, :repo] => :environment do |t, args|
task :install, [:dir, :repo] => :gitlab_environment do |t, args|
warn_user_is_not_gitlab
unless args.dir.present?
......
......@@ -2,5 +2,14 @@ unless Rails.env.production?
require 'haml_lint/rake_task'
require 'haml_lint/inline_javascript'
# Workaround for warnings from parser/current
# TODO: Remove this after we update parser gem
task :haml_lint do
require 'parser'
def Parser.warn(*args)
puts(*args) # static-analysis ignores stdout if status is 0
end
end
HamlLint::RakeTask.new
end
require Rails.root.join('lib/gitlab/database')
require Rails.root.join('lib/gitlab/database/migration_helpers')
require Rails.root.join('db/migrate/20151007120511_namespaces_projects_path_lower_indexes')
require Rails.root.join('db/migrate/20151008110232_add_users_lower_username_email_indexes')
require Rails.root.join('db/migrate/20161212142807_add_lower_path_index_to_routes')
require Rails.root.join('db/migrate/20170317203554_index_routes_path_for_like')
require Rails.root.join('db/migrate/20170724214302_add_lower_path_index_to_redirect_routes')
require Rails.root.join('db/migrate/20170503185032_index_redirect_routes_path_for_like')
require Rails.root.join('db/migrate/20171220191323_add_index_on_namespaces_lower_name.rb')
require Rails.root.join('db/migrate/20180113220114_rework_redirect_routes_indexes.rb')
desc 'GitLab | Sets up PostgreSQL'
task setup_postgresql: :environment do
require Rails.root.join('db/migrate/20151007120511_namespaces_projects_path_lower_indexes')
require Rails.root.join('db/migrate/20151008110232_add_users_lower_username_email_indexes')
require Rails.root.join('db/migrate/20161212142807_add_lower_path_index_to_routes')
require Rails.root.join('db/migrate/20170317203554_index_routes_path_for_like')
require Rails.root.join('db/migrate/20170724214302_add_lower_path_index_to_redirect_routes')
require Rails.root.join('db/migrate/20170503185032_index_redirect_routes_path_for_like')
require Rails.root.join('db/migrate/20171220191323_add_index_on_namespaces_lower_name.rb')
require Rails.root.join('db/migrate/20180113220114_rework_redirect_routes_indexes.rb')
NamespacesProjectsPathLowerIndexes.new.up
AddUsersLowerUsernameEmailIndexes.new.up
AddLowerPathIndexToRoutes.new.up
......
......@@ -28,6 +28,7 @@ module QA
autoload :Sandbox, 'qa/factory/resource/sandbox'
autoload :Group, 'qa/factory/resource/group'
autoload :Project, 'qa/factory/resource/project'
autoload :MergeRequest, 'qa/factory/resource/merge_request'
autoload :DeployKey, 'qa/factory/resource/deploy_key'
autoload :SecretVariable, 'qa/factory/resource/secret_variable'
autoload :Runner, 'qa/factory/resource/runner'
......@@ -135,6 +136,10 @@ module QA
autoload :PersonalAccessTokens, 'qa/page/profile/personal_access_tokens'
end
module MergeRequest
autoload :New, 'qa/page/merge_request/new'
end
module Admin
autoload :Settings, 'qa/page/admin/settings'
end
......
......@@ -16,20 +16,21 @@ module QA
def build!
return if overridden?
Builder.new(@signature).fabricate!.tap do |product|
Builder.new(@signature, @factory).fabricate!.tap do |product|
@factory.public_send("#{@name}=", product)
end
end
class Builder
def initialize(signature)
def initialize(signature, caller_factory)
@factory = signature.factory
@block = signature.block
@caller_factory = caller_factory
end
def fabricate!
@factory.fabricate! do |factory|
@block&.call(factory)
@block&.call(factory, @caller_factory)
end
end
end
......
require 'securerandom'
module QA
module Factory
module Resource
class MergeRequest < Factory::Base
attr_accessor :title,
:description,
:source_branch,
:target_branch
dependency Factory::Resource::Project, as: :project do |project|
project.name = 'project-with-merge-request'
end
dependency Factory::Repository::Push, as: :target do |push, factory|
push.project = factory.project
push.branch_name = "master:#{factory.target_branch}"
end
dependency Factory::Repository::Push, as: :source do |push, factory|
push.project = factory.project
push.branch_name = "#{factory.target_branch}:#{factory.source_branch}"
push.file_name = "added_file.txt"
push.file_content = "File Added"
end
def initialize
@title = 'QA test - merge request'
@description = 'This is a test merge request'
@source_branch = "qa-test-feature-#{SecureRandom.hex(8)}"
@target_branch = "master"
end
def fabricate!
project.visit!
Page::Project::Show.act { new_merge_request }
Page::MergeRequest::New.perform do |page|
page.fill_title(@title)
page.fill_description(@description)
page.create_merge_request
end
end
end
end
end
end
......@@ -13,16 +13,18 @@ module QA
visit current_url
end
def wait(css = '.application', time: 60)
Time.now.tap do |start|
while Time.now - start < time
break if page.has_css?(css, wait: 5)
def wait(max: 60, time: 1, reload: true)
start = Time.now
refresh
end
while Time.now - start < max
return true if yield
sleep(time)
refresh if reload
end
yield if block_given?
false
end
def scroll_to(selector, text: nil)
......@@ -40,12 +42,16 @@ module QA
page.within(selector) { yield } if block_given?
end
def find_element(name)
find(element_selector_css(name))
end
def click_element(name)
find_element(name).click
end
def find_element(name)
find(element_selector_css(name))
def fill_element(name, content)
find_element(name).set(content)
end
def within_element(name)
......
......@@ -25,7 +25,12 @@ module QA
def go_to_new_subgroup
within '.new-project-subgroup' do
find('.dropdown-toggle').click
# May need to click again because it is possible to click the button quicker than the JS is bound
wait(reload: false) do
find('.dropdown-toggle').click
page.has_css?("li[data-value='new-subgroup']")
end
find("li[data-value='new-subgroup']").click
end
......@@ -34,7 +39,12 @@ module QA
def go_to_new_project
within '.new-project-subgroup' do
find('.dropdown-toggle').click
# May need to click again because it is possible to click the button quicker than the JS is bound
wait(reload: false) do
find('.dropdown-toggle').click
page.has_css?("li[data-value='new-project']")
end
find("li[data-value='new-project']").click
end
......
......@@ -10,12 +10,14 @@ module QA
view 'app/views/devise/sessions/_new_base.html.haml' do
element :login_field, 'text_field :login'
element :passowrd_field, 'password_field :password'
element :password_field, 'password_field :password'
element :sign_in_button, 'submit "Sign in"'
end
def initialize
wait('.application', time: 500)
wait(max: 500) do
page.has_css?('.application')
end
end
def sign_in_using_credentials
......
module QA
module Page
module MergeRequest
class New < Page::Base
view 'app/views/shared/issuable/_form.html.haml' do
element :issuable_create_button
end
view 'app/views/shared/issuable/form/_title.html.haml' do
element :issuable_form_title
end
view 'app/views/shared/form_elements/_description.html.haml' do
element :issuable_form_description
end
def create_merge_request
click_element :issuable_create_button
end
def fill_title(title)
fill_element :issuable_form_title, title
end
def fill_description(description)
fill_element :issuable_form_description, description
end
end
end
end
end
......@@ -4,7 +4,7 @@ module QA
class New < Page::Base
view 'app/views/projects/_new_project_fields.html.haml' do
element :project_namespace_select
element :project_namespace_field, 'select :namespace_id'
element :project_namespace_field, /select :namespace_id.*class: 'select2/
element :project_path, 'text_field :path'
element :project_description, 'text_area :description'
element :project_create_button, "submit 'Create project'"
......@@ -13,7 +13,7 @@ module QA
def choose_test_namespace
click_element :project_namespace_select
first('li', text: Runtime::Namespace.path).click
find('ul.select2-result-sub > li', text: Runtime::Namespace.path).click
end
def choose_name(name)
......
......@@ -17,7 +17,12 @@ module QA
def expand_section(name)
page.within('#content-body') do
page.within('section', text: name) do
click_button 'Expand' unless first('button', text: 'Collapse')
# Because it is possible to click the button before the JS toggle code is bound
wait(reload: false) do
click_button 'Expand' unless first('button', text: 'Collapse')
page.has_content?('Collapse')
end
yield if block_given?
end
......
......@@ -3,12 +3,14 @@ module QA
module Project
class Show < Page::Base
view 'app/views/shared/_clone_panel.html.haml' do
element :clone_holder, '.git-clone-holder'
element :clone_dropdown
element :clone_options_dropdown, '.clone-options-dropdown'
element :project_repository_location, 'text_field_tag :project_clone'
end
view 'app/views/shared/_clone_panel.html.haml' do
element :project_repository_location, 'text_field_tag :project_clone'
view 'app/views/projects/_last_push.html.haml' do
element :create_merge_request
end
view 'app/views/projects/_home_panel.html.haml' do
......@@ -16,10 +18,15 @@ module QA
end
def choose_repository_clone_http
click_element :clone_dropdown
wait(reload: false) do
click_element :clone_dropdown
page.within('.clone-options-dropdown') do
click_link('HTTP')
page.within('.clone-options-dropdown') do
click_link('HTTP')
end
# Ensure git clone textbox was updated to http URI
page.has_css?('.git-clone-holder input#project_clone[value*="http"]')
end
end
......@@ -31,6 +38,10 @@ module QA
find('.qa-project-name').text
end
def new_merge_request
click_element :create_merge_request
end
def wait_for_push
sleep 5
refresh
......
module QA
feature 'creates a merge request', :core do
scenario 'user creates a new merge request' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
Factory::Resource::MergeRequest.fabricate! do |merge_request|
merge_request.title = 'This is a merge request'
merge_request.description = 'Great feature'
end
expect(page).to have_content('This is a merge request')
expect(page).to have_content('Great feature')
expect(page).to have_content('Opened less than a minute ago')
end
end
end
......@@ -54,6 +54,19 @@ describe QA::Factory::Dependency do
expect(factory).to have_received(:mydep=).with(dependency)
end
context 'when receives a caller factory as block argument' do
let(:dependency) { QA::Factory::Base }
it 'calls given block with dependency factory and caller factory' do
allow_any_instance_of(QA::Factory::Base).to receive(:fabricate!).and_return(factory)
allow(QA::Factory::Product).to receive(:populate!).and_return(spy('any'))
subject.build!
expect(block).to have_received(:call).with(an_instance_of(QA::Factory::Base), factory)
end
end
end
end
end
#!/usr/bin/env ruby
require ::File.expand_path('../lib/gitlab/popen', __dir__)
# We don't have auto-loading here
require_relative '../lib/gitlab/popen'
require_relative '../lib/gitlab/popen/runner'
def emit_warnings(static_analysis)
static_analysis.warned_results.each do |result|
puts
puts "**** #{result.cmd.join(' ')} had the following warnings:"
puts
puts result.stderr
puts
end
end
def emit_errors(static_analysis)
static_analysis.failed_results.each do |result|
puts
puts "**** #{result.cmd.join(' ')} failed with the following error:"
puts
puts result.stdout
puts result.stderr
puts
end
end
tasks = [
%w[bundle exec rake config_lint],
......@@ -17,18 +40,16 @@ tasks = [
%w[scripts/lint-rugged]
]
failed_tasks = tasks.reduce({}) do |failures, task|
start = Time.now
puts
puts "$ #{task.join(' ')}"
static_analysis = Gitlab::Popen::Runner.new
output, status = Gitlab::Popen.popen(task)
puts "==> Finished in #{Time.now - start} seconds"
static_analysis.run(tasks) do |cmd, &run|
puts
puts "$ #{cmd.join(' ')}"
failures[task.join(' ')] = output unless status.zero?
result = run.call
failures
puts "==> Finished in #{result.duration} seconds"
puts
end
puts
......@@ -36,17 +57,20 @@ puts '==================================================='
puts
puts
if failed_tasks.empty?
if static_analysis.all_success_and_clean?
puts 'All static analyses passed successfully.'
elsif static_analysis.all_success?
puts 'All static analyses passed successfully, but we have warnings:'
puts
emit_warnings(static_analysis)
exit 2
else
puts 'Some static analyses failed:'
failed_tasks.each do |failed_task, output|
puts
puts "**** #{failed_task} failed with the following error:"
puts
puts output
end
emit_warnings(static_analysis)
emit_errors(static_analysis)
exit 1
end
......@@ -146,7 +146,7 @@ describe Ci::Build do
pipeline: pipeline,
options: {
artifacts: {
paths: [filename]
paths: [filename, 'some-other-artifact.txt']
}
}
)
......
......@@ -194,7 +194,7 @@ describe 'Commits' do
end
it 'includes the committed_date for each commit' do
commits = project.repository.commits(branch_name)
commits = project.repository.commits(branch_name, limit: 40)
commits.each do |commit|
expect(page).to have_content("authored #{commit.authored_date.strftime("%b %d, %Y")}")
......
......@@ -17,12 +17,15 @@ feature 'Editing file blob', :js do
sign_in(user)
end
def edit_and_commit
def edit_and_commit(commit_changes: true)
wait_for_requests
find('.js-edit-blob').click
find('#editor')
execute_script('ace.edit("editor").setValue("class NextFeature\nend\n")')
click_button 'Commit changes'
if commit_changes
click_button 'Commit changes'
end
end
context 'from MR diff' do
......@@ -39,13 +42,26 @@ feature 'Editing file blob', :js do
context 'from blob file path' do
before do
visit project_blob_path(project, tree_join(branch, file_path))
edit_and_commit
end
it 'updates content' do
edit_and_commit
expect(page).to have_content 'successfully committed'
expect(page).to have_content 'NextFeature'
end
it 'previews content' do
edit_and_commit(commit_changes: false)
click_link 'Preview changes'
wait_for_requests
old_line_count = page.all('.line_holder.old').size
new_line_count = page.all('.line_holder.new').size
expect(old_line_count).to be > 0
expect(new_line_count).to be > 0
end
end
end
......
......@@ -100,7 +100,7 @@ describe ApplicationHelper do
end
it 'returns a generic avatar' do
expect(helper.gravatar_icon(user_email)).to match('no_avatar.png')
expect(helper.gravatar_icon(user_email)).to match_asset_path('no_avatar.png')
end
end
......@@ -110,7 +110,7 @@ describe ApplicationHelper do
end
it 'returns a generic avatar when email is blank' do
expect(helper.gravatar_icon('')).to match('no_avatar.png')
expect(helper.gravatar_icon('')).to match_asset_path('no_avatar.png')
end
it 'returns a valid Gravatar URL' do
......
......@@ -2,55 +2,7 @@ import Vue from 'vue';
import eventHub from '~/issuable/related_issues/event_hub';
import relatedIssuesBlock from '~/issuable/related_issues/components/related_issues_block.vue';
const issuable1 = {
id: 200,
epic_issue_id: 1,
reference: 'foo/bar#123',
displayReference: '#123',
title: 'some title',
path: '/foo/bar/issues/123',
state: 'opened',
};
const issuable2 = {
id: 201,
epic_issue_id: 2,
reference: 'foo/bar#124',
displayReference: '#124',
title: 'some other thing',
path: '/foo/bar/issues/124',
state: 'opened',
};
const issuable3 = {
id: 202,
epic_issue_id: 3,
reference: 'foo/bar#125',
displayReference: '#125',
title: 'some other other thing',
path: '/foo/bar/issues/125',
state: 'opened',
};
const issuable4 = {
id: 203,
epic_issue_id: 4,
reference: 'foo/bar#126',
displayReference: '#126',
title: 'some other other other thing',
path: '/foo/bar/issues/126',
state: 'opened',
};
const issuable5 = {
id: 204,
epic_issue_id: 5,
reference: 'foo/bar#127',
displayReference: '#127',
title: 'some other other other thing',
path: '/foo/bar/issues/127',
state: 'opened',
};
import { issuable1, issuable2, issuable3, issuable4, issuable5 } from '../mock_data';
describe('RelatedIssuesBlock', () => {
let RelatedIssuesBlock;
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment