Commit 6384aeda authored by Clement Ho's avatar Clement Ho

Merge branch 'master' into 'bootstrap4'

# Conflicts:
#   app/views/admin/application_settings/_signin.html.haml
parents b3d8b85f 5d6fb753
......@@ -721,7 +721,7 @@ codequality:
tags: []
before_script: []
services:
- docker:dind
- docker:stable-dind
variables:
SETUP_DB: "false"
DOCKER_DRIVER: overlay2
......
......@@ -82,16 +82,9 @@ gem 'net-ldap'
# Git Wiki
# Required manually in config/initializers/gollum.rb to control load order
# Before updating this gem, check if
# https://github.com/gollum/gollum-lib/pull/292 has been merged.
# If it has, then remove the monkey patch for update_page, rename_page and raw_data_in_committer
# in config/initializers/gollum.rb
gem 'gollum-lib', '~> 4.2', require: false
# Before updating this gem, check if
# https://github.com/gollum/rugged_adapter/pull/28 has been merged.
# If it has, then remove the monkey patch for tree_entry in config/initializers/gollum.rb
gem 'gollum-rugged_adapter', '~> 0.4.4', require: false
gem 'gitlab-gollum-lib', '~> 4.2'
gem 'gitlab-gollum-rugged_adapter', '~> 0.4.4', require: false
# Language detection
gem 'github-linguist', '~> 5.3.3', require: 'linguist'
......
......@@ -298,11 +298,22 @@ GEM
escape_utils (~> 1.1.0)
mime-types (>= 1.19)
rugged (>= 0.25.1)
github-markup (1.6.1)
github-markup (1.7.0)
gitlab-flowdock-git-hook (1.0.1)
flowdock (~> 0.7)
gitlab-grit (>= 2.4.1)
multi_json
gitlab-gollum-lib (4.2.7.1)
gemojione (~> 3.2)
github-markup (~> 1.6)
gollum-grit_adapter (~> 1.0)
nokogiri (>= 1.6.1, < 2.0)
rouge (~> 2.1)
sanitize (~> 2.1)
stringex (~> 2.6)
gitlab-gollum-rugged_adapter (0.4.4)
mime-types (>= 1.15)
rugged (~> 0.25)
gitlab-grit (2.8.2)
charlock_holmes (~> 0.6)
diff-lcs (~> 1.1)
......@@ -325,17 +336,6 @@ GEM
activesupport (>= 4.2, < 5.2)
gollum-grit_adapter (1.0.1)
gitlab-grit (~> 2.7, >= 2.7.1)
gollum-lib (4.2.7)
gemojione (~> 3.2)
github-markup (~> 1.6)
gollum-grit_adapter (~> 1.0)
nokogiri (>= 1.6.1, < 2.0)
rouge (~> 2.1)
sanitize (~> 2.1)
stringex (~> 2.6)
gollum-rugged_adapter (0.4.4)
mime-types (>= 1.15)
rugged (~> 0.25)
gon (6.1.0)
actionpack (>= 3.0)
json
......@@ -590,7 +590,7 @@ GEM
orm_adapter (0.5.0)
os (0.9.6)
parallel (1.12.1)
parser (2.5.0.5)
parser (2.5.1.0)
ast (~> 2.4.0)
parslet (1.5.0)
blankslate (~> 2.0)
......@@ -910,7 +910,7 @@ GEM
state_machines-activerecord (0.5.1)
activerecord (>= 4.1, < 6.0)
state_machines-activemodel (>= 0.5.0)
stringex (2.7.1)
stringex (2.8.4)
sys-filesystem (1.1.6)
ffi
sysexits (1.2.0)
......@@ -1067,12 +1067,12 @@ DEPENDENCIES
gitaly-proto (~> 0.94.0)
github-linguist (~> 5.3.3)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-gollum-lib (~> 4.2)
gitlab-gollum-rugged_adapter (~> 0.4.4)
gitlab-markup (~> 1.6.2)
gitlab-styles (~> 2.3)
gitlab_omniauth-ldap (~> 2.0.4)
goldiloader (~> 2.0)
gollum-lib (~> 4.2)
gollum-rugged_adapter (~> 0.4.4)
gon (~> 6.1.0)
google-api-client (~> 0.19.8)
google-protobuf (= 3.5.1)
......
import $ from 'jquery';
import _ from 'underscore';
import {
getSelector,
togglePopover,
inserted,
mouseenter,
mouseleave,
} from './feature_highlight_helper';
import {
togglePopover,
mouseenter,
debouncedMouseleave,
} from '../shared/popover';
export function setupFeatureHighlightPopover(id, debounceTimeout = 300) {
const $selector = $(getSelector(id));
const $parent = $selector.parent();
const $popoverContent = $parent.siblings('.feature-highlight-popover-content');
const hideOnScroll = togglePopover.bind($selector, false);
const debouncedMouseleave = _.debounce(mouseleave, debounceTimeout);
$selector
// Setup popover
......@@ -29,13 +29,10 @@ export function setupFeatureHighlightPopover(id, debounceTimeout = 300) {
`,
})
.on('mouseenter', mouseenter)
.on('mouseleave', debouncedMouseleave)
.on('mouseleave', debouncedMouseleave(debounceTimeout))
.on('inserted.bs.popover', inserted)
.on('show.bs.popover', () => {
window.addEventListener('scroll', hideOnScroll);
})
.on('hide.bs.popover', () => {
window.removeEventListener('scroll', hideOnScroll);
window.addEventListener('scroll', hideOnScroll, { once: true });
})
// Display feature highlight
.removeAttr('disabled');
......
......@@ -3,20 +3,10 @@ import axios from '../lib/utils/axios_utils';
import { __ } from '../locale';
import Flash from '../flash';
import LazyLoader from '../lazy_loader';
import { togglePopover } from '../shared/popover';
export const getSelector = highlightId => `.js-feature-highlight[data-highlight=${highlightId}]`;
export function togglePopover(show) {
const isAlreadyShown = this.hasClass('js-popover-show');
if ((show && isAlreadyShown) || (!show && !isAlreadyShown)) {
return false;
}
this.popover(show ? 'show' : 'hide');
this.toggleClass('disable-animation js-popover-show', show);
return true;
}
export function dismiss(highlightId) {
axios.post(this.attr('data-dismiss-endpoint'), {
feature_name: highlightId,
......@@ -27,23 +17,6 @@ export function dismiss(highlightId) {
this.hide();
}
export function mouseleave() {
if (!$('.popover:hover').length > 0) {
const $featureHighlight = $(this);
togglePopover.call($featureHighlight, false);
}
}
export function mouseenter() {
const $featureHighlight = $(this);
const showedPopover = togglePopover.call($featureHighlight, true);
if (showedPopover) {
$('.popover')
.on('mouseleave', mouseleave.bind($featureHighlight));
}
}
export function inserted() {
const popoverId = this.getAttribute('aria-describedby');
const highlightId = this.dataset.highlight;
......
......@@ -37,9 +37,9 @@ export const setLastCommitMessage = ({ rootState, commit }, data) => {
const commitMsg = sprintf(
__('Your changes have been committed. Commit %{commitId} %{commitStats}'),
{
commitId: `<a href="${currentProject.web_url}/commit/${
commitId: `<a href="${currentProject.web_url}/commit/${data.short_id}" class="commit-sha">${
data.short_id
}" class="commit-sha">${data.short_id}</a>`,
}</a>`,
commitStats,
},
false,
......@@ -54,9 +54,7 @@ export const checkCommitStatus = ({ rootState }) =>
.then(({ data }) => {
const { id } = data.commit;
const selectedBranch =
rootState.projects[rootState.currentProjectId].branches[
rootState.currentBranchId
];
rootState.projects[rootState.currentProjectId].branches[rootState.currentBranchId];
if (selectedBranch.workingReference !== id) {
return true;
......@@ -135,32 +133,15 @@ export const updateFilesAfterCommit = (
if (state.commitAction === consts.COMMIT_TO_NEW_BRANCH) {
router.push(
`/project/${rootState.currentProjectId}/blob/${branch}/${
rootGetters.activeFile.path
}`,
`/project/${rootState.currentProjectId}/blob/${branch}/${rootGetters.activeFile.path}`,
);
}
dispatch('updateCommitAction', consts.COMMIT_TO_CURRENT_BRANCH);
};
export const commitChanges = ({
commit,
state,
getters,
dispatch,
rootState,
}) => {
export const commitChanges = ({ commit, state, getters, dispatch, rootState }) => {
const newBranch = state.commitAction !== consts.COMMIT_TO_CURRENT_BRANCH;
const payload = createCommitPayload(
getters.branchName,
newBranch,
state,
rootState,
);
const getCommitStatus = newBranch
? Promise.resolve(false)
: dispatch('checkCommitStatus');
const payload = createCommitPayload(getters.branchName, newBranch, state, rootState);
const getCommitStatus = newBranch ? Promise.resolve(false) : dispatch('checkCommitStatus');
commit(types.UPDATE_LOADING, true);
......@@ -182,28 +163,29 @@ export const commitChanges = ({
if (!data.short_id) {
flash(data.message, 'alert', document, null, false, true);
return;
return null;
}
dispatch('setLastCommitMessage', data);
dispatch('updateCommitMessage', '');
if (state.commitAction === consts.COMMIT_TO_NEW_BRANCH_MR) {
dispatch(
'redirectToUrl',
createNewMergeRequestUrl(
rootState.projects[rootState.currentProjectId].web_url,
getters.branchName,
rootState.currentBranchId,
),
{ root: true },
);
} else {
dispatch('updateFilesAfterCommit', {
data,
branch: getters.branchName,
});
}
return dispatch('updateFilesAfterCommit', {
data,
branch: getters.branchName,
})
.then(() => {
if (state.commitAction === consts.COMMIT_TO_NEW_BRANCH_MR) {
dispatch(
'redirectToUrl',
createNewMergeRequestUrl(
rootState.projects[rootState.currentProjectId].web_url,
getters.branchName,
rootState.currentBranchId,
),
{ root: true },
);
}
})
.then(() => dispatch('updateCommitAction', consts.COMMIT_TO_CURRENT_BRANCH));
})
.catch(err => {
let errMsg = __('Error committing changes. Please try again.');
......
......@@ -7,11 +7,7 @@ import flash from './flash';
import BlobForkSuggestion from './blob/blob_fork_suggestion';
import initChangesDropdown from './init_changes_dropdown';
import bp from './breakpoints';
import {
parseUrlPathname,
handleLocationHash,
isMetaClick,
} from './lib/utils/common_utils';
import { parseUrlPathname, handleLocationHash, isMetaClick } from './lib/utils/common_utils';
import { getLocationHash } from './lib/utils/url_utility';
import initDiscussionTab from './image_diff/init_discussion_tab';
import Diff from './diff';
......@@ -69,11 +65,10 @@ import Notes from './notes';
let location = window.location;
export default class MergeRequestTabs {
constructor({ action, setUrl, stubLocation } = {}) {
const mergeRequestTabs = document.querySelector('.js-tabs-affix');
const navbar = document.querySelector('.navbar-gitlab');
const peek = document.getElementById('peek');
const peek = document.getElementById('js-peek');
const paddingTop = 16;
this.diffsLoaded = false;
......@@ -109,8 +104,7 @@ export default class MergeRequestTabs {
.on('shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', this.tabShown)
.on('click', '.js-show-tab', this.showTab);
$('.merge-request-tabs a[data-toggle="tab"]')
.on('click', this.clickTab);
$('.merge-request-tabs a[data-toggle="tab"]').on('click', this.clickTab);
}
// Used in tests
......@@ -119,8 +113,7 @@ export default class MergeRequestTabs {
.off('shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', this.tabShown)
.off('click', '.js-show-tab', this.showTab);
$('.merge-request-tabs a[data-toggle="tab"]')
.off('click', this.clickTab);
$('.merge-request-tabs a[data-toggle="tab"]').off('click', this.clickTab);
}
destroyPipelinesView() {
......@@ -183,10 +176,7 @@ export default class MergeRequestTabs {
scrollToElement(container) {
if (location.hash) {
const offset = 0 - (
$('.navbar-gitlab').outerHeight() +
$('.js-tabs-affix').outerHeight()
);
const offset = 0 - ($('.navbar-gitlab').outerHeight() + $('.js-tabs-affix').outerHeight());
const $el = $(`${container} ${location.hash}:not(.match)`);
if ($el.length) {
$.scrollTo($el[0], { offset });
......@@ -240,9 +230,13 @@ export default class MergeRequestTabs {
// Turbolinks' history.
//
// See https://github.com/rails/turbolinks/issues/363
window.history.replaceState({
url: newState,
}, document.title, newState);
window.history.replaceState(
{
url: newState,
},
document.title,
newState,
);
return newState;
}
......@@ -258,7 +252,8 @@ export default class MergeRequestTabs {
this.toggleLoading(true);
axios.get(`${source}.json`)
axios
.get(`${source}.json`)
.then(({ data }) => {
document.querySelector('div#commits').innerHTML = data.html;
localTimeAgo($('.js-timeago', 'div#commits'));
......@@ -303,7 +298,8 @@ export default class MergeRequestTabs {
this.toggleLoading(true);
axios.get(`${urlPathname}.json${location.search}`)
axios
.get(`${urlPathname}.json${location.search}`)
.then(({ data }) => {
const $container = $('#diffs');
$container.html(data.html);
......@@ -332,8 +328,7 @@ export default class MergeRequestTabs {
cancelButtons: $(el).find('.js-cancel-fork-suggestion-button'),
suggestionSections: $(el).find('.js-file-fork-suggestion-section'),
actionTextPieces: $(el).find('.js-file-fork-suggestion-section-action'),
})
.init();
}).init();
});
// Scroll any linked note into view
......@@ -388,8 +383,7 @@ export default class MergeRequestTabs {
resetViewContainer() {
if (this.fixedLayoutPref !== null) {
$('.content-wrapper .container-fluid')
.toggleClass('container-limited', this.fixedLayoutPref);
$('.content-wrapper .container-fluid').toggleClass('container-limited', this.fixedLayoutPref);
}
}
......@@ -438,12 +432,11 @@ export default class MergeRequestTabs {
const $diffTabs = $('#diff-notes-app');
$tabs.off('affix.bs.affix affix-top.bs.affix')
$tabs
.off('affix.bs.affix affix-top.bs.affix')
.affix({
offset: {
top: () => (
$diffTabs.offset().top - $tabs.height() - $fixedNav.height()
),
top: () => $diffTabs.offset().top - $tabs.height() - $fixedNav.height(),
},
})
.on('affix.bs.affix', () => $diffTabs.css({ marginTop: $tabs.height() }))
......
import $ from 'jquery';
import axios from './lib/utils/axios_utils';
import flash from './flash';
import { mouseenter, debouncedMouseleave, togglePopover } from './shared/popover';
export default class Milestone {
constructor() {
......@@ -43,4 +44,25 @@ export default class Milestone {
.catch(() => flash('Error loading milestone tab'));
}
}
static initDeprecationMessage() {
const deprecationMesssageContainer = document.querySelector('.js-milestone-deprecation-message');
if (!deprecationMesssageContainer) return;
const deprecationMessage = deprecationMesssageContainer.querySelector('.js-milestone-deprecation-message-template').innerHTML;
const $popover = $('.js-popover-link', deprecationMesssageContainer);
const hideOnScroll = togglePopover.bind($popover, false);
$popover.popover({
content: deprecationMessage,
html: true,
placement: 'bottom',
})
.on('mouseenter', mouseenter)
.on('mouseleave', debouncedMouseleave())
.on('show.bs.popover', () => {
window.addEventListener('scroll', hideOnScroll, { once: true });
});
}
}
......@@ -6,4 +6,6 @@ document.addEventListener('DOMContentLoaded', () => {
new Milestone(); // eslint-disable-line no-new
new Sidebar(); // eslint-disable-line no-new
new MountMilestoneSidebar(); // eslint-disable-line no-new
Milestone.initDeprecationMessage();
});
import initMilestonesShow from '~/pages/milestones/shared/init_milestones_show';
import Milestone from '~/milestone';
document.addEventListener('DOMContentLoaded', initMilestonesShow);
document.addEventListener('DOMContentLoaded', () => {
initMilestonesShow();
Milestone.initDeprecationMessage();
});
import initNotes from '~/init_notes';
import ZenMode from '~/zen_mode';
import LineHighlighter from '../../../../line_highlighter';
import BlobViewer from '../../../../blob/viewer';
import LineHighlighter from '~/line_highlighter';
import BlobViewer from '~/blob/viewer';
import snippetEmbed from '~/snippet/snippet_embed';
document.addEventListener('DOMContentLoaded', () => {
new LineHighlighter(); // eslint-disable-line no-new
new BlobViewer(); // eslint-disable-line no-new
initNotes();
new ZenMode(); // eslint-disable-line no-new
snippetEmbed();
});
import LineHighlighter from '../../../line_highlighter';
import BlobViewer from '../../../blob/viewer';
import ZenMode from '../../../zen_mode';
import initNotes from '../../../init_notes';
import LineHighlighter from '~/line_highlighter';
import BlobViewer from '~/blob/viewer';
import ZenMode from '~/zen_mode';
import initNotes from '~/init_notes';
import snippetEmbed from '~/snippet/snippet_embed';
document.addEventListener('DOMContentLoaded', () => {
new LineHighlighter(); // eslint-disable-line no-new
new BlobViewer(); // eslint-disable-line no-new
initNotes();
new ZenMode(); // eslint-disable-line no-new
snippetEmbed();
});
<script>
import $ from 'jquery';
/**
* Renders each stage of the pipeline mini graph.
......@@ -13,8 +12,11 @@
* 3. Merge request widget
* 4. Commit widget
*/
import axios from '../../lib/utils/axios_utils';
import $ from 'jquery';
import Flash from '../../flash';
import axios from '../../lib/utils/axios_utils';
import eventHub from '../event_hub';
import Icon from '../../vue_shared/components/icon.vue';
import LoadingIcon from '../../vue_shared/components/loading_icon.vue';
import tooltip from '../../vue_shared/directives/tooltip';
......@@ -82,6 +84,7 @@
methods: {
onClickStage() {
if (!this.isDropdownOpen()) {
eventHub.$emit('clickedDropdown');
this.isLoading = true;
this.fetchJobs();
}
......
// eslint-disable-next-line import/prefer-default-export
export const CANCEL_REQUEST = 'CANCEL_REQUEST';
......@@ -7,6 +7,7 @@ import SvgBlankState from '../components/blank_state.vue';
import LoadingIcon from '../../vue_shared/components/loading_icon.vue';
import PipelinesTableComponent from '../components/pipelines_table.vue';
import eventHub from '../event_hub';
import { CANCEL_REQUEST } from '../constants';
export default {
components: {
......@@ -52,34 +53,58 @@ export default {
});
eventHub.$on('postAction', this.postAction);
eventHub.$on('clickedDropdown', this.updateTable);
},
beforeDestroy() {
eventHub.$off('postAction', this.postAction);
eventHub.$off('clickedDropdown', this.updateTable);
},
destroyed() {
this.poll.stop();
},
methods: {
updateTable() {
// Cancel ongoing request
if (this.isMakingRequest) {
this.service.cancelationSource.cancel(CANCEL_REQUEST);
}
// Stop polling
this.poll.stop();
// Update the table
return this.getPipelines()
.then(() => this.poll.restart());
},
fetchPipelines() {
if (!this.isMakingRequest) {
this.isLoading = true;
this.service.getPipelines(this.requestData)
.then(response => this.successCallback(response))
.catch(() => this.errorCallback());
this.getPipelines();
}
},
getPipelines() {
return this.service.getPipelines(this.requestData)
.then(response => this.successCallback(response))
.catch((error) => this.errorCallback(error));
},
setCommonData(pipelines) {
this.store.storePipelines(pipelines);
this.isLoading = false;
this.updateGraphDropdown = true;
this.hasMadeRequest = true;
// In case the previous polling request returned an error, we need to reset it
if (this.hasError) {
this.hasError = false;
}
},
errorCallback() {
this.hasError = true;
this.isLoading = false;
this.updateGraphDropdown = false;
errorCallback(error) {
this.hasMadeRequest = true;
this.isLoading = false;
if (error && error.message && error.message !== CANCEL_REQUEST) {
this.hasError = true;
this.updateGraphDropdown = false;
}
},
setIsMakingRequest(isMakingRequest) {
this.isMakingRequest = isMakingRequest;
......
......@@ -19,8 +19,13 @@ export default class PipelinesService {
getPipelines(data = {}) {
const { scope, page } = data;
const CancelToken = axios.CancelToken;
this.cancelationSource = CancelToken.source();
return axios.get(this.endpoint, {
params: { scope, page },
cancelToken: this.cancelationSource.token,
});
}
......
import $ from 'jquery';
import _ from 'underscore';
export function togglePopover(show) {
const isAlreadyShown = this.hasClass('js-popover-show');
if ((show && isAlreadyShown) || (!show && !isAlreadyShown)) {
return false;
}
this.popover(show ? 'show' : 'hide');
this.toggleClass('disable-animation js-popover-show', show);
return true;
}
export function mouseleave() {
if (!$('.popover:hover').length > 0) {
const $popover = $(this);
togglePopover.call($popover, false);
}
}
export function mouseenter() {
const $popover = $(this);
const showedPopover = togglePopover.call($popover, true);
if (showedPopover) {
$('.popover').on('mouseleave', mouseleave.bind($popover));
}
}
export function debouncedMouseleave(debounceTimeout = 300) {
return _.debounce(mouseleave, debounceTimeout);
}
import { visitUrl } from './lib/utils/url_utility';
/**
* Helper function that finds the href of the fiven selector and updates the location.
*
* @param {String} selector
*/
export default (selector) => {
const link = document.querySelector(selector).getAttribute('href');
export default function findAndFollowLink(selector) {
const element = document.querySelector(selector);
const link = element && element.getAttribute('href');
if (link) {
window.location = link;
visitUrl(link);
}
};
}
export default () => {
const { protocol, host, pathname } = location;
const shareBtn = document.querySelector('.js-share-btn');
const embedBtn = document.querySelector('.js-embed-btn');
const snippetUrlArea = document.querySelector('.js-snippet-url-area');
const embedAction = document.querySelector('.js-embed-action');
const url = `${protocol}//${host + pathname}`;
shareBtn.addEventListener('click', () => {
shareBtn.classList.add('is-active');
embedBtn.classList.remove('is-active');
snippetUrlArea.value = url;
embedAction.innerText = 'Share';
});
embedBtn.addEventListener('click', () => {
embedBtn.classList.add('is-active');
shareBtn.classList.remove('is-active');
const scriptTag = `<script src="${url}.js"></script>`;
snippetUrlArea.value = scriptTag;
embedAction.innerText = 'Embed';
});
};
......@@ -146,8 +146,8 @@ export default {
</p>
<p
v-if="shouldShowMemoryGraph"
class="usage-info js-usage-info">
{{ memoryChangeMessage }}
class="usage-info js-usage-info"
v-html="memoryChangeMessage">
</p>
<p
v-if="shouldShowLoadFailure"
......
......@@ -9,7 +9,7 @@
lines: {
type: Number,
required: false,
default: 6,
default: 3,
},
},
computed: {
......
......@@ -37,7 +37,11 @@
/*
* Code highlight
*/
@import "highlight/**/*";
@import "highlight/dark";
@import "highlight/monokai";
@import "highlight/solarized_dark";
@import "highlight/solarized_light";
@import "highlight/white";
/*
* Styles for JS behaviors.
......
......@@ -187,12 +187,9 @@ a {
animation: fadeInFull $fade-in-duration 1;
}
.animation-container {
background: $repo-editor-grey;
height: 40px;
overflow: hidden;
position: relative;
&.animation-container-small {
height: 12px;
......@@ -205,60 +202,43 @@ a {
}
}
&::before {
animation-duration: 1s;
animation-fill-mode: forwards;
animation-iteration-count: infinite;
animation-name: blockTextShine;
animation-timing-function: linear;
background-image: $repo-editor-linear-gradient;
background-repeat: no-repeat;
background-size: 800px 45px;
content: ' ';
display: block;
height: 100%;
[class^="skeleton-line-"] {
position: relative;
}
div {
background: $white-light;
height: 6px;
left: 0;
position: absolute;
right: 0;
}
.skeleton-line-1 {
left: 0;
top: 8px;
}
.skeleton-line-2 {
left: 150px;
top: 0;
background-color: $theme-gray-100;
height: 10px;
}
overflow: hidden;
.skeleton-line-3 {
left: 0;
top: 23px;
}
&:not(:last-of-type) {
margin-bottom: 4px;
}
.skeleton-line-4 {
left: 0;
top: 38px;
&::after {
content: ' ';
display: block;
animation: blockTextShine 1s linear infinite forwards;
background-repeat: no-repeat;
background-size: cover;
background-image: linear-gradient(
to right,
$theme-gray-100 0%,
$theme-gray-50 20%,
$theme-gray-100 40%,
$theme-gray-100 100%
);
height: 10px;
}
}
}
.skeleton-line-5 {
left: 200px;
top: 28px;
height: 10px;
}
$skeleton-line-widths: (
156px,
235px,
200px,
);
.skeleton-line-6 {
top: 14px;
left: 230px;
height: 10px;
@for $count from 1 through length($skeleton-line-widths) {
.skeleton-line-#{$count} {
width: nth($skeleton-line-widths, $count);
}
}
......
.banner-callout {
display: flex;
position: relative;
flex-wrap: wrap;
align-items: start;
.banner-close {
position: absolute;
......@@ -16,10 +16,25 @@
}
.banner-graphic {
margin: 20px auto;
margin: 0 $gl-padding $gl-padding 0;
}
&.banner-non-empty-state {
border-bottom: 1px solid $border-color;
}
@media (max-width: $screen-xs-max) {
justify-content: center;
flex-direction: column;
align-items: center;
.banner-title,
.banner-buttons {
text-align: center;
}
.banner-graphic {
margin-left: $gl-padding;
}
}
}
......@@ -423,25 +423,43 @@
}
}
.btn-link.btn-secondary-hover-link {
color: $gl-text-color-secondary;
.btn-link {
padding: 0;
background-color: transparent;
color: $blue-600;
font-weight: normal;
border-radius: 0;
border-color: transparent;
&:hover,
&:active,
&:focus {
color: $gl-link-color;
text-decoration: none;
color: $blue-800;
text-decoration: underline;
background-color: transparent;
border-color: transparent;
}
}
.btn-link.btn-primary-hover-link {
color: inherit;
&.btn-secondary-hover-link {
color: $gl-text-color-secondary;
&:hover,
&:active,
&:focus {
color: $gl-link-color;
text-decoration: none;
&:hover,
&:active,
&:focus {
color: $gl-link-color;
text-decoration: none;
}
}
&.btn-primary-hover-link {
color: inherit;
&:hover,
&:active,
&:focus {
color: $gl-link-color;
text-decoration: none;
}
}
}
......
......@@ -481,7 +481,8 @@
.dropdown-menu-selectable {
li {
a {
a,
button {
padding: 8px 40px;
position: relative;
......
......@@ -29,8 +29,10 @@
}
.snippet-title {
font-size: 24px;
color: $gl-text-color;
font-size: 2em;
font-weight: $gl-font-weight-bold;
min-height: $header-height;
}
.snippet-edited-ago {
......@@ -46,3 +48,26 @@
.snippet-scope-menu .btn-new {
margin-top: 15px;
}
.snippet-embed-input {
height: 35px;
}
.embed-snippet {
padding-right: 0;
padding-top: $gl-padding;
.form-control {
cursor: auto;
width: 101%;
margin-left: -1px;
}
.embed-toggle-list li button {
padding: 8px 40px;
}
.embed-toggle {
height: 35px;
}
}
......@@ -713,20 +713,6 @@ $color-high-score: $green-400;
$color-average-score: $orange-400;
$color-low-score: $red-400;
/*
Repo editor
*/
$repo-editor-grey: #f6f7f9;
$repo-editor-grey-darker: #e9ebee;
$repo-editor-linear-gradient: linear-gradient(
to right,
$repo-editor-grey 0%,
$repo-editor-grey-darker,
20%,
$repo-editor-grey 40%,
$repo-editor-grey 100%
);
/*
Performance Bar
*/
......
/* https://github.com/aahan/pygments-github-style */
/*
* White Syntax Colors
*/
$white-code-color: $gl-text-color;
$white-highlight: #fafe3d;
$white-pre-hll-bg: #f8eec7;
$white-hll-bg: #f8f8f8;
$white-over-bg: #ded7fc;
$white-expanded-border: #e0e0e0;
$white-expanded-bg: #f7f7f7;
$white-c: #998;
$white-err: #a61717;
$white-err-bg: #e3d2d2;
$white-cm: #998;
$white-cp: #999;
$white-c1: #998;
$white-cs: #999;
$white-gd: $black;
$white-gd-bg: #fdd;
$white-gd-x: $black;
$white-gd-x-bg: #faa;
$white-gr: #a00;
$white-gh: #999;
$white-gi: $black;
$white-gi-bg: #dfd;
$white-gi-x: $black;
$white-gi-x-bg: #afa;
$white-go: #888;
$white-gp: #555;
$white-gu: #800080;
$white-gt: #a00;
$white-kt: #458;
$white-m: #099;
$white-s: #d14;
$white-n: #333;
$white-na: teal;
$white-nb: #0086b3;
$white-nc: #458;
$white-no: teal;
$white-ni: purple;
$white-ne: #900;
$white-nf: #900;
$white-nn: #555;
$white-nt: navy;
$white-nv: teal;
$white-w: #bbb;
$white-mf: #099;
$white-mh: #099;
$white-mi: #099;
$white-mo: #099;
$white-sb: #d14;
$white-sc: #d14;
$white-sd: #d14;
$white-s2: #d14;
$white-se: #d14;
$white-sh: #d14;
$white-si: #d14;
$white-sx: #d14;
$white-sr: #009926;
$white-s1: #d14;
$white-ss: #990073;
$white-bp: #999;
$white-vc: teal;
$white-vg: teal;
$white-vi: teal;
$white-il: #099;
$white-gc-color: #999;
$white-gc-bg: #eaf2f5;
@mixin matchLine {
color: $black-transparent;
background-color: $gray-light;
}
.code.white {
// Line numbers
.line-numbers,
.diff-line-num {
background-color: $gray-light;
}
.diff-line-num,
.diff-line-num a {
color: $black-transparent;
}
// Code itself
pre.code,
.diff-line-num {
border-color: $white-normal;
}
&,
pre.code,
.line_holder .line_content {
background-color: $white-light;
color: $white-code-color;
}
// Diff line
.line_holder {
&.match .line_content {
@include matchLine;
}
.diff-line-num {
&.old {
background-color: $line-number-old;
border-color: $line-removed-dark;
a {
color: scale-color($line-number-old, $red: -30%, $green: -30%, $blue: -30%);
}
}
&.new {
background-color: $line-number-new;
border-color: $line-added-dark;
a {
color: scale-color($line-number-new, $red: -30%, $green: -30%, $blue: -30%);
}
}
&.is-over,
&.hll:not(.empty-cell).is-over {
background-color: $white-over-bg;
border-color: darken($white-over-bg, 5%);
a {
color: darken($white-over-bg, 15%);
}
}
&.hll:not(.empty-cell) {
background-color: $line-number-select;
border-color: $line-select-yellow-dark;
}
}
&:not(.diff-expanded) + .diff-expanded,
&.diff-expanded + .line_holder:not(.diff-expanded) {
> .diff-line-num,
> .line_content {
border-top: 1px solid $white-expanded-border;
}
}
&.diff-expanded {
> .diff-line-num,
> .line_content {
background: $white-expanded-bg;
border-color: $white-expanded-bg;
}
}
.line_content {
&.old {
background-color: $line-removed;
&::before {
color: scale-color($line-number-old, $red: -30%, $green: -30%, $blue: -30%);
}
span.idiff {
background-color: $line-removed-dark;
}
}
&.new {
background-color: $line-added;
&::before {
color: scale-color($line-number-new, $red: -30%, $green: -30%, $blue: -30%);
}
span.idiff {
background-color: $line-added-dark;
}
}
&.match {
@include matchLine;
}
&.hll:not(.empty-cell) {
background-color: $line-select-yellow;
}
}
}
// highlight line via anchor
pre .hll {
background-color: $white-pre-hll-bg !important;
}
// Search result highlight
span.highlight_word {
background-color: $white-highlight !important;
}
// Links to URLs, emails, or dependencies
.line a {
color: $white-nb;
}
.hll { background-color: $white-hll-bg; }
.c { color: $white-c; font-style: italic; }
.err { color: $white-err; background-color: $white-err-bg; }
.k { font-weight: $gl-font-weight-bold; }
.o { font-weight: $gl-font-weight-bold; }
.cm { color: $white-cm; font-style: italic; }
.cp { color: $white-cp; font-weight: $gl-font-weight-bold; }
.c1 { color: $white-c1; font-style: italic; }
.cs { color: $white-cs; font-weight: $gl-font-weight-bold; font-style: italic; }
.gd {
color: $white-gd;
background-color: $white-gd-bg;
.x {
color: $white-gd-x;
background-color: $white-gd-x-bg;
}
}
.ge { font-style: italic; }
.gr { color: $white-gr; }
.gh { color: $white-gh; }
.gi {
color: $white-gi;
background-color: $white-gi-bg;
.x {
color: $white-gi-x;
background-color: $white-gi-x-bg;
}
}
.go { color: $white-go; }
.gp { color: $white-gp; }
.gs { font-weight: $gl-font-weight-bold; }
.gu { color: $white-gu; font-weight: $gl-font-weight-bold; }
.gt { color: $white-gt; }
.kc { font-weight: $gl-font-weight-bold; }
.kd { font-weight: $gl-font-weight-bold; }
.kn { font-weight: $gl-font-weight-bold; }
.kp { font-weight: $gl-font-weight-bold; }
.kr { font-weight: $gl-font-weight-bold; }
.kt { color: $white-kt; font-weight: $gl-font-weight-bold; }
.m { color: $white-m; }
.s { color: $white-s; }
.n { color: $white-n; }
.na { color: $white-na; }
.nb { color: $white-nb; }
.nc { color: $white-nc; font-weight: $gl-font-weight-bold; }
.no { color: $white-no; }
.ni { color: $white-ni; }
.ne { color: $white-ne; font-weight: $gl-font-weight-bold; }
.nf { color: $white-nf; font-weight: $gl-font-weight-bold; }
.nn { color: $white-nn; }
.nt { color: $white-nt; }
.nv { color: $white-nv; }
.ow { font-weight: $gl-font-weight-bold; }
.w { color: $white-w; }
.mf { color: $white-mf; }
.mh { color: $white-mh; }
.mi { color: $white-mi; }
.mo { color: $white-mo; }
.sb { color: $white-sb; }
.sc { color: $white-sc; }
.sd { color: $white-sd; }
.s2 { color: $white-s2; }
.se { color: $white-se; }
.sh { color: $white-sh; }
.si { color: $white-si; }
.sx { color: $white-sx; }
.sr { color: $white-sr; }
.s1 { color: $white-s1; }
.ss { color: $white-ss; }
.bp { color: $white-bp; }
.vc { color: $white-vc; }
.vg { color: $white-vg; }
.vi { color: $white-vi; }
.il { color: $white-il; }
.gc { color: $white-gc-color; background-color: $white-gc-bg; }
@import "white_base";
}
/* https://github.com/aahan/pygments-github-style */
/*
* White Syntax Colors
*/
$white-code-color: $gl-text-color;
$white-highlight: #fafe3d;
$white-pre-hll-bg: #f8eec7;
$white-hll-bg: #f8f8f8;
$white-over-bg: #ded7fc;
$white-expanded-border: #e0e0e0;
$white-expanded-bg: #f7f7f7;
$white-c: #998;
$white-err: #a61717;
$white-err-bg: #e3d2d2;
$white-cm: #998;
$white-cp: #999;
$white-c1: #998;
$white-cs: #999;
$white-gd: $black;
$white-gd-bg: #fdd;
$white-gd-x: $black;
$white-gd-x-bg: #faa;
$white-gr: #a00;
$white-gh: #999;
$white-gi: $black;
$white-gi-bg: #dfd;
$white-gi-x: $black;
$white-gi-x-bg: #afa;
$white-go: #888;
$white-gp: #555;
$white-gu: #800080;
$white-gt: #a00;
$white-kt: #458;
$white-m: #099;
$white-s: #d14;
$white-n: #333;
$white-na: teal;
$white-nb: #0086b3;
$white-nc: #458;
$white-no: teal;
$white-ni: purple;
$white-ne: #900;
$white-nf: #900;
$white-nn: #555;
$white-nt: navy;
$white-nv: teal;
$white-w: #bbb;
$white-mf: #099;
$white-mh: #099;
$white-mi: #099;
$white-mo: #099;
$white-sb: #d14;
$white-sc: #d14;
$white-sd: #d14;
$white-s2: #d14;
$white-se: #d14;
$white-sh: #d14;
$white-si: #d14;
$white-sx: #d14;
$white-sr: #009926;
$white-s1: #d14;
$white-ss: #990073;
$white-bp: #999;
$white-vc: teal;
$white-vg: teal;
$white-vi: teal;
$white-il: #099;
$white-gc-color: #999;
$white-gc-bg: #eaf2f5;
@mixin matchLine {
color: $black-transparent;
background-color: $gray-light;
}
// Line numbers
.line-numbers,
.diff-line-num {
background-color: $gray-light;
}
.diff-line-num,
.diff-line-num a {
color: $black-transparent;
}
// Code itself
pre.code,
.diff-line-num {
border-color: $white-normal;
}
&,
pre.code,
.line_holder .line_content {
background-color: $white-light;
color: $white-code-color;
}
// Diff line
.line_holder {
&.match .line_content {
@include matchLine;
}
.diff-line-num {
&.old {
background-color: $line-number-old;
border-color: $line-removed-dark;
a {
color: scale-color($line-number-old, $red: -30%, $green: -30%, $blue: -30%);
}
}
&.new {
background-color: $line-number-new;
border-color: $line-added-dark;
a {
color: scale-color($line-number-new, $red: -30%, $green: -30%, $blue: -30%);
}
}
&.is-over,
&.hll:not(.empty-cell).is-over {
background-color: $white-over-bg;
border-color: darken($white-over-bg, 5%);
a {
color: darken($white-over-bg, 15%);
}
}
&.hll:not(.empty-cell) {
background-color: $line-number-select;
border-color: $line-select-yellow-dark;
}
}
&:not(.diff-expanded) + .diff-expanded,
&.diff-expanded + .line_holder:not(.diff-expanded) {
> .diff-line-num,
> .line_content {
border-top: 1px solid $white-expanded-border;
}
}
&.diff-expanded {
> .diff-line-num,
> .line_content {
background: $white-expanded-bg;
border-color: $white-expanded-bg;
}
}
.line_content {
&.old {
background-color: $line-removed;
&::before {
color: scale-color($line-number-old, $red: -30%, $green: -30%, $blue: -30%);
}
span.idiff {
background-color: $line-removed-dark;
}
}
&.new {
background-color: $line-added;
&::before {
color: scale-color($line-number-new, $red: -30%, $green: -30%, $blue: -30%);
}
span.idiff {
background-color: $line-added-dark;
}
}
&.match {
@include matchLine;
}
&.hll:not(.empty-cell) {
background-color: $line-select-yellow;
}
}
}
// highlight line via anchor
pre .hll {
background-color: $white-pre-hll-bg !important;
}
// Search result highlight
span.highlight_word {
background-color: $white-highlight !important;
}
// Links to URLs, emails, or dependencies
.line a {
color: $white-nb;
}
.hll { background-color: $white-hll-bg; }
.c { color: $white-c; font-style: italic; }
.err { color: $white-err; background-color: $white-err-bg; }
.k { font-weight: $gl-font-weight-bold; }
.o { font-weight: $gl-font-weight-bold; }
.cm { color: $white-cm; font-style: italic; }
.cp { color: $white-cp; font-weight: $gl-font-weight-bold; }
.c1 { color: $white-c1; font-style: italic; }
.cs { color: $white-cs; font-weight: $gl-font-weight-bold; font-style: italic; }
.gd {
color: $white-gd;
background-color: $white-gd-bg;
.x {
color: $white-gd-x;
background-color: $white-gd-x-bg;
}
}
.ge { font-style: italic; }
.gr { color: $white-gr; }
.gh { color: $white-gh; }
.gi {
color: $white-gi;
background-color: $white-gi-bg;
.x {
color: $white-gi-x;
background-color: $white-gi-x-bg;
}
}
.go { color: $white-go; }
.gp { color: $white-gp; }
.gs { font-weight: $gl-font-weight-bold; }
.gu { color: $white-gu; font-weight: $gl-font-weight-bold; }
.gt { color: $white-gt; }
.kc { font-weight: $gl-font-weight-bold; }
.kd { font-weight: $gl-font-weight-bold; }
.kn { font-weight: $gl-font-weight-bold; }
.kp { font-weight: $gl-font-weight-bold; }
.kr { font-weight: $gl-font-weight-bold; }
.kt { color: $white-kt; font-weight: $gl-font-weight-bold; }
.m { color: $white-m; }
.s { color: $white-s; }
.n { color: $white-n; }
.na { color: $white-na; }
.nb { color: $white-nb; }
.nc { color: $white-nc; font-weight: $gl-font-weight-bold; }
.no { color: $white-no; }
.ni { color: $white-ni; }
.ne { color: $white-ne; font-weight: $gl-font-weight-bold; }
.nf { color: $white-nf; font-weight: $gl-font-weight-bold; }
.nn { color: $white-nn; }
.nt { color: $white-nt; }
.nv { color: $white-nv; }
.ow { font-weight: $gl-font-weight-bold; }
.w { color: $white-w; }
.mf { color: $white-mf; }
.mh { color: $white-mh; }
.mi { color: $white-mi; }
.mo { color: $white-mo; }
.sb { color: $white-sb; }
.sc { color: $white-sc; }
.sd { color: $white-sd; }
.s2 { color: $white-s2; }
.se { color: $white-se; }
.sh { color: $white-sh; }
.si { color: $white-si; }
.sx { color: $white-sx; }
.sr { color: $white-sr; }
.s1 { color: $white-s1; }
.ss { color: $white-ss; }
.bp { color: $white-bp; }
.vc { color: $white-vc; }
.vg { color: $white-vg; }
.vi { color: $white-vi; }
.il { color: $white-il; }
.gc { color: $white-gc-color; background-color: $white-gc-bg; }
......@@ -194,3 +194,38 @@
.issuable-row {
background-color: $white-light;
}
.milestone-deprecation-message {
.popover {
padding: 0;
}
.popover-content {
padding: 0;
}
}
.milestone-popover-body {
padding: $gl-padding-8;
background-color: $gray-light;
}
.milestone-popover-footer {
padding: $gl-padding-8 $gl-padding;
border-top: 1px solid $white-dark;
}
.milestone-popover-instructions-list {
padding-left: 2em;
> li {
padding-left: 1em;
}
}
@media (max-width: $screen-xs-max) {
.milestone-banner-text,
.milestone-banner-link {
display: inline;
}
}
......@@ -429,6 +429,7 @@
.projects-sidebar {
display: flex;
flex-direction: column;
height: 100%;
.context-header {
width: auto;
......@@ -438,8 +439,8 @@
.multi-file-commit-panel-inner {
display: flex;
flex: 1;
flex-direction: column;
height: 100%;
}
.multi-file-commit-panel-inner-scroll {
......
@import "framework/variables";
.gitlab-embed-snippets {
@import "highlight/embedded";
@import "framework/images";
$border-style: 1px solid $border-color;
font-family: $regular_font;
font-size: $gl-font-size;
line-height: $code_line_height;
color: $gl-text-color;
margin: 20px;
font-weight: 200;
.gl-snippet-icon {
display: inline-block;
background: url(asset_path('ext_snippet_icons/ext_snippet_icons.png')) no-repeat;
overflow: hidden;
text-align: left;
width: 16px;
height: 16px;
background-size: cover;
&.gl-snippet-icon-doc_code { background-position: 0 0; }
&.gl-snippet-icon-doc_text { background-position: 0 -16px; }
&.gl-snippet-icon-download { background-position: 0 -32px; }
}
.blob-viewer {
background-color: $white-light;
text-align: left;
}
.file-content.code {
border: $border-style;
border-radius: 0 0 4px 4px;
display: flex;
box-shadow: none;
margin: 0;
padding: 0;
table-layout: fixed;
.blob-content {
overflow-x: auto;
pre {
padding: 10px;
border: 0;
border-radius: 0;
font-family: $monospace_font;
font-size: $code_font_size;
line-height: $code_line_height;
margin: 0;
overflow: auto;
overflow-y: hidden;
white-space: pre;
word-wrap: normal;
border-left: $border-style;
}
}
.line-numbers {
padding: 10px;
text-align: right;
float: left;
.diff-line-num {
font-family: $monospace_font;
display: block;
font-size: $code_font_size;
min-height: $code_line_height;
white-space: nowrap;
color: $black-transparent;
min-width: 30px;
}
.diff-line-num:hover {
color: $almost-black;
cursor: pointer;
}
}
}
.file-title-flex-parent {
display: flex;
align-items: center;
justify-content: space-between;
background-color: $gray-light;
border: $border-style;
border-bottom: 0;
padding: $gl-padding-top $gl-padding;
margin: 0;
border-radius: $border-radius-default $border-radius-default 0 0;
.file-header-content {
.file-title-name {
font-weight: $gl-font-weight-bold;
}
.gitlab-embedded-snippets-title {
text-decoration: none;
color: $gl-text-color;
&:hover {
text-decoration: underline;
}
}
.gitlab-logo {
display: inline-block;
padding-left: 5px;
text-decoration: none;
color: $gl-text-color-secondary;
.logo-text {
background: image_url('ext_snippet_icons/logo.png') no-repeat left center;
background-size: 18px;
font-weight: $gl-font-weight-normal;
padding-left: 24px;
}
}
}
img,
.gl-snippet-icon {
display: inline-block;
vertical-align: middle;
}
}
.btn-group {
a.btn {
background-color: $white-light;
text-decoration: none;
padding: 7px 9px;
border: $border-style;
border-right: 0;
&:hover {
background-color: $white-normal;
border-color: $border-white-normal;
text-decoration: none;
}
&:first-child {
border-radius: 3px 0 0 3px;
}
&:last-child {
border-radius: 0 3px 3px 0;
border-right: $border-style;
}
}
}
}
......@@ -57,22 +57,17 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
def application_setting_params
params[:application_setting] ||= {}
import_sources = params[:application_setting][:import_sources]
if import_sources.nil?
params[:application_setting][:import_sources] = []
else
import_sources.map! do |source|
source.to_str
end
end
if params[:application_setting].key?(:enabled_oauth_sign_in_sources)
enabled_oauth_sign_in_sources = params[:application_setting].delete(:enabled_oauth_sign_in_sources)
enabled_oauth_sign_in_sources&.delete("")
enabled_oauth_sign_in_sources = params[:application_setting].delete(:enabled_oauth_sign_in_sources)
params[:application_setting][:disabled_oauth_sign_in_sources] =
AuthHelper.button_based_providers.map(&:to_s) -
Array(enabled_oauth_sign_in_sources)
params[:application_setting][:disabled_oauth_sign_in_sources] =
AuthHelper.button_based_providers.map(&:to_s) -
Array(enabled_oauth_sign_in_sources)
end
params[:application_setting][:import_sources]&.delete("")
params[:application_setting][:restricted_visibility_levels]&.delete("")
params.delete(:domain_blacklist_raw) if params[:domain_blacklist_file]
......
......@@ -17,6 +17,10 @@ module SnippetsActions
end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
def js_request?
request.format.js?
end
private
def convert_line_endings(content)
......
......@@ -5,6 +5,8 @@ class Projects::SnippetsController < Projects::ApplicationController
include SnippetsActions
include RendersBlob
skip_before_action :verify_authenticity_token, only: [:show], if: :js_request?
before_action :check_snippets_available!
before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :toggle_award_emoji, :mark_as_spam]
......@@ -71,6 +73,7 @@ class Projects::SnippetsController < Projects::ApplicationController
format.json do
render_blob_json(blob)
end
format.js { render 'shared/snippets/show'}
end
end
......
......@@ -6,6 +6,8 @@ class SnippetsController < ApplicationController
include RendersBlob
include PreviewMarkdown
skip_before_action :verify_authenticity_token, only: [:show], if: :js_request?
before_action :snippet, only: [:show, :edit, :destroy, :update, :raw]
# Allow read snippet
......@@ -77,6 +79,8 @@ class SnippetsController < ApplicationController
format.json do
render_blob_json(blob)
end
format.js { render 'shared/snippets/show' }
end
end
......
......@@ -74,10 +74,12 @@ module ApplicationSettingsHelper
css_class = 'btn'
css_class << ' active' unless disabled
checkbox_name = 'application_setting[enabled_oauth_sign_in_sources][]'
name = Gitlab::Auth::OAuth::Provider.label_for(source)
label_tag(checkbox_name, class: css_class) do
check_box_tag(checkbox_name, source, !disabled,
autocomplete: 'off') + Gitlab::Auth::OAuth::Provider.label_for(source)
autocomplete: 'off',
id: name.tr(' ', '_')) + name
end
end
end
......
......@@ -43,6 +43,10 @@ module IconsHelper
content_tag(:svg, content_tag(:use, "", { "xlink:href" => "#{sprite_icon_path}##{icon_name}" } ), class: css_classes.empty? ? nil : css_classes)
end
def external_snippet_icon(name)
content_tag(:span, "", class: "gl-snippet-icon gl-snippet-icon-#{name}")
end
def audit_icon(names, options = {})
case names
when "standard"
......
......@@ -101,4 +101,39 @@ module SnippetsHelper
# Return snippet with chunk array
{ snippet_object: snippet, snippet_chunks: snippet_chunks }
end
def snippet_embed
"<script src=\"#{url_for(only_path: false, overwrite_params: nil)}.js\"></script>"
end
def embedded_snippet_raw_button
blob = @snippet.blob
return if blob.empty? || blob.raw_binary? || blob.stored_externally?
snippet_raw_url = if @snippet.is_a?(PersonalSnippet)
raw_snippet_url(@snippet)
else
raw_project_snippet_url(@snippet.project, @snippet)
end
link_to external_snippet_icon('doc_code'), snippet_raw_url, class: 'btn', target: '_blank', rel: 'noopener noreferrer', title: 'Open raw'
end
def embedded_snippet_download_button
download_url = if @snippet.is_a?(PersonalSnippet)
raw_snippet_url(@snippet, inline: false)
else
raw_project_snippet_url(@snippet.project, @snippet, inline: false)
end
link_to external_snippet_icon('download'), download_url, class: 'btn', target: '_blank', title: 'Download', rel: 'noopener noreferrer'
end
def public_snippet?
if @snippet.project_id?
can?(nil, :read_project_snippet, @snippet)
else
can?(nil, :read_personal_snippet, @snippet)
end
end
end
......@@ -23,7 +23,8 @@
must be used to authenticate.
- if omniauth_enabled? && button_based_providers.any?
.form-group.row
= f.label :enabled_oauth_sign_in_sources, 'Enabled OAuth sign-in sources', class: 'col-form-label col-sm-2'
= f.label :enabled_oauth_sign_in_sources, 'Enabled OAuth sign-in sources', class: 'control-label col-sm-2'
= hidden_field_tag 'application_setting[enabled_oauth_sign_in_sources][]'
.col-sm-10
.btn-group{ data: { toggle: 'buttons' } }
- oauth_providers_checkboxes.each do |source|
......
......@@ -32,6 +32,7 @@
.form-group.row
= f.label :import_sources, class: 'col-form-label col-sm-2'
.col-sm-10
= hidden_field_tag 'application_setting[import_sources][]'
- import_sources_checkboxes('import-sources-help').each do |source|
.form-check= source
%span.form-text.text-muted#import-sources-help
......
......@@ -126,6 +126,7 @@
GitLab
%span.float-right
= Gitlab::VERSION
= "(#{Gitlab::REVISION})"
%p
GitLab Shell
%span.float-right
......
......@@ -20,7 +20,7 @@
- else
%p
Download the Google Authenticator application from App Store or Google Play Store and scan this code.
More information is available in the #{link_to('documentation', help_page_path('profile/two_factor_authentication'))}.
More information is available in the #{link_to('documentation', help_page_path('user/profile/account/two_factor_authentication'))}.
.row.append-bottom-10
.col-md-4
= raw @qr_code
......
......@@ -2,6 +2,7 @@
- render_error = viewer.render_error
- rich_type = viewer.type == :rich ? viewer.partial_name : nil
- load_async = local_assigns.fetch(:load_async, viewer.load_async? && render_error.nil?)
- external_embed = local_assigns.fetch(:external_embed, false)
- viewer_url = local_assigns.fetch(:viewer_url) { url_for(params.merge(viewer: viewer.type, format: :json)) } if load_async
.blob-viewer{ data: { type: viewer.type, rich_type: rich_type, url: viewer_url }, class: ('hidden' if hidden) }
......@@ -9,6 +10,8 @@
= render 'projects/blob/render_error', viewer: viewer
- elsif load_async
= render viewer.loading_partial_path, viewer: viewer
- elsif external_embed
= render 'projects/blob/viewers/highlight_embed', blob: viewer.blob
- else
- viewer.prepare!
......
.file-content.code.js-syntax-highlight
.line-numbers
- if blob.data.present?
- blob.data.each_line.each_with_index do |_, index|
%span.diff-line-num= index + 1
.blob-content{ data: { blob_id: blob.id } }
= highlight(blob.path, blob.data, repository: nil, plain: blob.no_highlighting?)
.js-autodevops-banner.banner-callout.banner-non-empty-state.append-bottom-20{ data: { uid: 'auto_devops_settings_dismissed', project_path: project_path(@project) } }
.js-autodevops-banner.banner-callout.banner-non-empty-state.append-bottom-20.prepend-top-10{ data: { uid: 'auto_devops_settings_dismissed', project_path: project_path(@project) } }
.banner-graphic
= custom_icon('icon_autodevops')
.prepend-top-10.prepend-left-10.append-bottom-10
%h5= s_('AutoDevOps|Auto DevOps (Beta)')
.banner-body.prepend-left-10.append-bottom-10
%h5.banner-title= s_('AutoDevOps|Auto DevOps (Beta)')
%p= s_('AutoDevOps|It will automatically build, test, and deploy your application based on a predefined CI/CD configuration.')
%p
- link = link_to(s_('AutoDevOps|Auto DevOps documentation'), help_page_path('topics/autodevops/index.md'), target: '_blank', rel: 'noopener noreferrer')
= s_('AutoDevOps|Learn more in the %{link_to_documentation}').html_safe % { link_to_documentation: link }
.prepend-top-10
.banner-buttons
= link_to s_('AutoDevOps|Enable in settings'), project_settings_ci_cd_path(@project, anchor: 'js-general-pipeline-settings'), class: 'btn js-close-callout'
%button.btn-transparent.banner-close.close.js-close-callout{ type: 'button',
......
......@@ -8,7 +8,7 @@
%strong
= link_to group.full_name, group_path(group)
.cgray
Joined #{time_ago_with_tooltip(group.created_at)}
Given access #{time_ago_with_tooltip(group_link.created_at)}
- if group_link.expires?
·
%span{ class: ('text-warning' if group_link.expires_soon?) }
......
......@@ -29,7 +29,7 @@
Requested
= time_ago_with_tooltip(member.requested_at)
- else
Joined #{time_ago_with_tooltip(member.created_at)}
Given access #{time_ago_with_tooltip(member.created_at)}
- if member.expires?
·
%span{ class: "#{"text-warning" if member.expires_soon?} has-tooltip", title: member.expires_at.to_time.in_time_zone.to_s(:medium) }
......
.banner-callout.compact.milestone-deprecation-message.js-milestone-deprecation-message.prepend-top-20
.banner-graphic= image_tag 'illustrations/milestone_removing-page.svg'
.banner-body.prepend-left-10.append-right-10
%h5.banner-title.prepend-top-0= _('This page will be removed in a future release.')
%p.milestone-banner-text= _('Use group milestones to manage issues from multiple projects in the same milestone.')
= button_tag _('Promote these project milestones into a group milestone.'), class: 'btn btn-link js-popover-link text-align-left milestone-banner-link'
.milestone-banner-buttons.prepend-top-20= link_to _('Learn more'), help_page_url('user/project/milestones/index', anchor: 'promoting-project-milestones-to-group-milestones'), class: 'btn btn-default', target: '_blank'
%template.js-milestone-deprecation-message-template
.milestone-popover-body
%ol.milestone-popover-instructions-list.append-bottom-0
%li= _('Click any <strong>project name</strong> in the project list below to navigate to the project milestone.').html_safe
%li= _('Click the <strong>Promote</strong> button in the top right corner to promote it to a group milestone.').html_safe
.milestone-popover-footer= link_to _('Learn more'), help_page_url('user/project/milestones/index', anchor: 'promoting-project-milestones-to-group-milestones'), class: 'btn btn-link prepend-left-0', target: '_blank'
- page_title @milestone.title
- page_title milestone.title
- @breadcrumb_link = dashboard_milestone_path(milestone.safe_title, title: milestone.title)
- group = local_assigns[:group]
- is_dynamic_milestone = milestone.legacy_group_milestone? || milestone.dashboard_milestone?
.detail-page-header
%a.btn.btn-secondary.btn-grouped.float-right.d-block.d-sm-none.js-sidebar-toggle{ href: "#" }
......@@ -31,21 +32,23 @@
- else
= link_to 'Reopen Milestone', group_milestone_route(milestone, {state_event: :activate }), method: :put, class: "btn btn-grouped btn-reopen"
= render 'shared/milestones/deprecation_message' if is_dynamic_milestone
.detail-page-description.milestone-detail
%h2.title
= markdown_field(milestone, :title)
- if @milestone.group_milestone? && @milestone.description.present?
- if milestone.group_milestone? && milestone.description.present?
%div
.description
.wiki
= markdown_field(@milestone, :description)
= markdown_field(milestone, :description)
- if milestone.complete?(current_user) && milestone.active?
.alert.alert-success.prepend-top-default
- close_msg = group ? 'You may close the milestone now.' : 'Navigate to the project to close the milestone.'
%span All issues for this milestone are closed. #{close_msg}
- if @milestone.legacy_group_milestone? || @milestone.dashboard_milestone?
- if is_dynamic_milestone
.table-holder
%table.table
%thead
......@@ -68,7 +71,7 @@
Open
%td
= ms.expires_at
- elsif @milestone.group_milestone?
- elsif milestone.group_milestone?
%br
View
= link_to 'Issues', issues_group_path(@group, milestone_title: milestone.title)
......
- blob = @snippet.blob
.gitlab-embed-snippets
.js-file-title.file-title-flex-parent
.file-header-content
= external_snippet_icon('doc_text')
%strong.file-title-name
%a.gitlab-embedded-snippets-title{ href: url_for(only_path: false, overwrite_params: nil) }
= blob.name
%small
= number_to_human_size(blob.raw_size)
%a.gitlab-logo{ href: url_for(only_path: false, overwrite_params: nil), title: 'view on gitlab' }
on &nbsp;
%span.logo-text
GitLab
.file-actions.hidden-xs
.btn-group{ role: "group" }<
= embedded_snippet_raw_button
= embedded_snippet_download_button
%article.file-holder.snippet-file-content
= render 'projects/blob/viewer', viewer: @snippet.blob.simple_viewer, load_async: false, external_embed: true
......@@ -19,11 +19,32 @@
%h2.snippet-title.prepend-top-0.append-bottom-0
= markdown_field(@snippet, :title)
- if @snippet.updated_at != @snippet.created_at
= edited_time_ago_with_tooltip(@snippet, placement: 'bottom', html_class: 'snippet-edited-ago', exclude_author: true)
- if @snippet.description.present?
.description
.wiki
= markdown_field(@snippet, :description)
%textarea.hidden.js-task-list-field
= @snippet.description
- if @snippet.updated_at != @snippet.created_at
= edited_time_ago_with_tooltip(@snippet, placement: 'bottom', html_class: 'snippet-edited-ago', exclude_author: true)
- if public_snippet?
.embed-snippet
.input-group
.input-group-btn
%button.btn.embed-toggle{ 'data-toggle': 'dropdown', type: 'button' }
%span.js-embed-action= _("Embed")
= sprite_icon('angle-down', size: 12)
%ul.dropdown-menu.dropdown-menu-selectable.embed-toggle-list
%li
%button.js-embed-btn.btn.btn-transparent.is-active{ type: 'button' }
%strong.embed-toggle-list-item= _("Embed")
%li
%button.js-share-btn.btn.btn-transparent{ type: 'button' }
%strong.embed-toggle-list-item= _("Share")
%input.js-snippet-url-area.snippet-embed-input.form-control{ type: "text", autocomplete: 'off', value: snippet_embed }
.input-group-btn
%button.js-clipboard-btn.btn.btn-default.has-tooltip{ title: "Copy to clipboard", 'data-clipboard-target': '#snippet-url-area' }
= sprite_icon('duplicate', size: 16)
.clearfix
document.write('#{escape_javascript(stylesheet_link_tag "#{stylesheet_url 'snippets'}")}');
document.write('#{escape_javascript(render 'shared/snippets/embed')}');
---
title: Do not use '-f' with 'rm' in gitlab-basics docs
merge_request: 18027
author: Elias Werberich
type: changed
---
title: Fix `joined` information on project members page
merge_request: 18290
author: Fabian Schneider
type: fixed
---
title: Now `rake cache:clear` will also clear pipeline status cache
merge_request: 18257
author:
type: fixed
---
title: Adds Embedded Snippets Support
merge_request: 15695
author: haseebeqx
type: added
---
title: Validate project path prior to hitting the database.
merge_request: 18322
author:
type: performance
---
title: git SHA is now displayed alongside the GitLab version on the Admin Dashboard
merge_request:
author:
type: added
---
title: Replace the `project/commits/comments.feature` spinach test with an rspec analog
merge_request: 18356
author: "@blackst0ne"
type: other
---
title: Add deprecation message to dynamic milestone pages
merge_request: 17505
author:
type: added
---
title: Add documentation for Pipelines failure reasons
merge_request: 18352
author:
type: other
---
title: 'API: add languages of project GET /projects/:id/languages'
merge_request: 17770
author: Roger Rüttimann
type: added
---
title: Ignore project internal references in group context
merge_request:
author:
type: fixed
---
title: Replacing gollum libraries for gitlab custom libs
merge_request: 18343
author:
type: other
---
title: Removed alert box in IDE when redirecting to new merge request
merge_request:
author:
type: fixed
......@@ -113,6 +113,7 @@ module Gitlab
config.assets.precompile << "performance_bar.css"
config.assets.precompile << "lib/ace.js"
config.assets.precompile << "test.css"
config.assets.precompile << "snippets.css"
config.assets.precompile << "locale/**/app.js"
# Import gitlab-svgs directly from vendored directory
......
deprecator = ActiveSupport::Deprecation.new('11.0', 'GitLab')
if Gitlab.com? || Rails.env.development?
ActiveSupport::Deprecation.deprecate_methods(Gitlab::GitalyClient::StorageSettings, :legacy_disk_path, deprecator: deprecator)
end
......@@ -7,139 +7,6 @@ module Gollum
end
require "gollum-lib"
module Gollum
class Committer
# Patch for UTF-8 path
def method_missing(name, *args)
index.send(name, *args)
end
end
class Wiki
def pages(treeish = nil, limit: nil)
tree_list((treeish || @ref), limit: limit)
end
def tree_list(ref, limit: nil)
if (sha = @access.ref_to_sha(ref))
commit = @access.commit(sha)
tree_map_for(sha).inject([]) do |list, entry|
next list unless @page_class.valid_page_name?(entry.name)
list << entry.page(self, commit)
break list if limit && list.size >= limit
list
end
else
[]
end
end
# Remove if https://github.com/gollum/gollum-lib/pull/292 has been merged
def update_page(page, name, format, data, commit = {})
name = name ? ::File.basename(name) : page.name
format ||= page.format
dir = ::File.dirname(page.path)
dir = '' if dir == '.'
filename = (rename = page.name != name) ? Gollum::Page.cname(name) : page.filename_stripped
multi_commit = !!commit[:committer]
committer = multi_commit ? commit[:committer] : Committer.new(self, commit)
if !rename && page.format == format
committer.add(page.path, normalize(data))
else
committer.delete(page.path)
committer.add_to_index(dir, filename, format, data)
end
committer.after_commit do |index, _sha|
@access.refresh
index.update_working_dir(dir, page.filename_stripped, page.format)
index.update_working_dir(dir, filename, format)
end
multi_commit ? committer : committer.commit
end
# Remove if https://github.com/gollum/gollum-lib/pull/292 has been merged
def rename_page(page, rename, commit = {})
return false if page.nil?
return false if rename.nil? || rename.empty?
(target_dir, target_name) = ::File.split(rename)
(source_dir, source_name) = ::File.split(page.path)
source_name = page.filename_stripped
# File.split gives us relative paths with ".", commiter.add_to_index doesn't like that.
target_dir = '' if target_dir == '.'
source_dir = '' if source_dir == '.'
target_dir = target_dir.gsub(/^\//, '') # rubocop:disable Style/RegexpLiteral
# if the rename is a NOOP, abort
if source_dir == target_dir && source_name == target_name
return false
end
multi_commit = !!commit[:committer]
committer = multi_commit ? commit[:committer] : Committer.new(self, commit)
# This piece only works for multi_commit
# If we are in a commit batch and one of the previous operations
# has updated the page, any information we ask to the page can be outdated.
# Therefore, we should ask first to the current committer tree to see if
# there is any updated change.
raw_data = raw_data_in_committer(committer, source_dir, page.filename) ||
raw_data_in_committer(committer, source_dir, "#{target_name}.#{Page.format_to_ext(page.format)}") ||
page.raw_data
committer.delete(page.path)
committer.add_to_index(target_dir, target_name, page.format, raw_data)
committer.after_commit do |index, _sha|
@access.refresh
index.update_working_dir(source_dir, source_name, page.format)
index.update_working_dir(target_dir, target_name, page.format)
end
multi_commit ? committer : committer.commit
end
# Remove if https://github.com/gollum/gollum-lib/pull/292 has been merged
def raw_data_in_committer(committer, dir, filename)
data = nil
[*dir.split(::File::SEPARATOR), filename].each do |key|
data = data ? data[key] : committer.tree[key]
break unless data
end
data
end
end
module Git
class Git
def tree_entry(commit, path)
pathname = Pathname.new(path)
tmp_entry = nil
pathname.each_filename do |dir|
tmp_entry = if tmp_entry.nil?
commit.tree[dir]
else
@repo.lookup(tmp_entry[:oid])[dir]
end
return nil unless tmp_entry
end
tmp_entry
end
end
end
end
Rails.application.configure do
config.after_initialize do
Gollum::Page.per_page = Kaminari.config.default_per_page
......
......@@ -183,7 +183,7 @@ instant how code changes impact your production environment.
### Git and GitLab
- [Git](topics/git/index.md): Getting started with Git, branching strategies, Git LFS, advanced use.
- [Git cheatsheet](https://gitlab.com/gitlab-com/marketing/raw/master/design/print/git-cheatsheet/print-pdf/git-cheatsheet.pdf): Download a PDF describing the most used Git operations.
- [Git cheatsheet](https://about.gitlab.com/images/press/git-cheat-sheet.pdf): Download a PDF describing the most used Git operations.
- [GitLab Flow](workflow/gitlab_flow.md): explore the best of Git with the GitLab Flow strategy.
## Administrator documentation
......
......@@ -915,6 +915,29 @@ Example response:
}
```
## Languages
Get languages used in a project with percentage value.
```
GET /projects/:id/languages
```
```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/5/languages"
```
Example response:
```json
{
"Ruby": 66.69,
"JavaScript": 22.98,
"HTML": 7.91,
"CoffeeScript": 2.42
}
```
## Archive a project
Archives the project if the user is either admin or the project owner of this project. This action is
......
......@@ -15,7 +15,7 @@ Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `action` | string | no | The action to be filtered. Can be `assigned`, `mentioned`, `build_failed`, `marked`, or `approval_required`. |
| `action` | string | no | The action to be filtered. Can be `assigned`, `mentioned`, `build_failed`, `marked`, `approval_required`, `unmergeable` or `directly_addressed`. |
| `author_id` | integer | no | The ID of an author |
| `project_id` | integer | no | The ID of a project |
| `state` | string | no | The state of the todo. Can be either `pending` or `done` |
......
......@@ -101,12 +101,12 @@ In order to do that, follow the steps:
--registration-token REGISTRATION_TOKEN \
--executor docker \
--description "My Docker Runner" \
--docker-image "docker:latest" \
--docker-image "docker:stable" \
--docker-privileged
```
The above command will register a new Runner to use the special
`docker:latest` image which is provided by Docker. **Notice that it's using
`docker:stable` image which is provided by Docker. **Notice that it's using
the `privileged` mode to start the build and service containers.** If you
want to use [docker-in-docker] mode, you always have to use `privileged = true`
in your Docker containers.
......@@ -120,7 +120,7 @@ In order to do that, follow the steps:
executor = "docker"
[runners.docker]
tls_verify = false
image = "docker:latest"
image = "docker:stable"
privileged = true
disable_cache = false
volumes = ["/cache"]
......@@ -132,7 +132,7 @@ In order to do that, follow the steps:
`docker:dind` service):
```yaml
image: docker:latest
image: docker:stable
# When using dind, it's wise to use the overlayfs driver for
# improved performance.
......@@ -201,12 +201,12 @@ In order to do that, follow the steps:
--registration-token REGISTRATION_TOKEN \
--executor docker \
--description "My Docker Runner" \
--docker-image "docker:latest" \
--docker-image "docker:stable" \
--docker-volumes /var/run/docker.sock:/var/run/docker.sock
```
The above command will register a new Runner to use the special
`docker:latest` image which is provided by Docker. **Notice that it's using
`docker:stable` image which is provided by Docker. **Notice that it's using
the Docker daemon of the Runner itself, and any containers spawned by docker
commands will be siblings of the Runner rather than children of the runner.**
This may have complications and limitations that are unsuitable for your workflow.
......@@ -220,7 +220,7 @@ In order to do that, follow the steps:
executor = "docker"
[runners.docker]
tls_verify = false
image = "docker:latest"
image = "docker:stable"
privileged = false
disable_cache = false
volumes = ["/var/run/docker.sock:/var/run/docker.sock", "/cache"]
......@@ -232,7 +232,7 @@ In order to do that, follow the steps:
include the `docker:dind` service as when using the Docker in Docker executor):
```yaml
image: docker:latest
image: docker:stable
before_script:
- docker info
......@@ -286,7 +286,7 @@ any image that's used with the `--cache-from` argument must first be pulled
Here's a simple `.gitlab-ci.yml` file showing how Docker caching can be utilized:
```yaml
image: docker:latest
image: docker:stable
services:
- docker:dind
......@@ -388,7 +388,7 @@ could look like:
```yaml
build:
image: docker:latest
image: docker:stable
services:
- docker:dind
stage: build
......@@ -434,7 +434,7 @@ when needed. Changes to `master` also get tagged as `latest` and deployed using
an application-specific deploy script:
```yaml
image: docker:latest
image: docker:stable
services:
- docker:dind
......
......@@ -86,7 +86,7 @@ services](#accessing-the-services).
### How the health check of services works
Services are designed to provide additional functionality which is **network accessible**.
It may be a database like MySQL, or Redis, and even `docker:dind` which
It may be a database like MySQL, or Redis, and even `docker:stable-dind` which
allows you to use Docker in Docker. It can be practically anything that is
required for the CI/CD job to proceed and is accessed by network.
......
......@@ -17,7 +17,7 @@ performance:
variables:
URL: https://example.com
services:
- docker:dind
- docker:stable-dind
script:
- mkdir gitlab-exporter
- wget -O ./gitlab-exporter/index.js https://gitlab.com/gitlab-org/gl-performance/raw/master/index.js
......@@ -94,7 +94,7 @@ performance:
stage: performance
image: docker:git
services:
- docker:dind
- docker:stable-dind
dependencies:
- review
script:
......
......@@ -73,6 +73,21 @@ cancel the job, retry it, or erase the job trace.
![Pipelines example](img/pipelines.png)
## Seeing the failure reason for jobs
> [Introduced][ce-5742] in GitLab 10.7.
When a pipeline fails or is allowed to fail, there are several places where you
can quickly check the reason it failed:
- **In the pipeline graph** present on the pipeline detail view.
- **In the pipeline widgets** present in the merge requests and commit pages.
- **In the job views** present in the global and detailed views of a job.
In any case, if you hover over the failed job you can see the reason it failed.
![Pipeline detail](img/job_failure_reason.png)
## Pipeline graphs
> [Introduced][ce-5742] in GitLab 8.11.
......@@ -263,4 +278,5 @@ runners will not use regular runners, they must be tagged accordingly.
[ce-6242]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6242
[ce-7931]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7931
[ce-9760]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9760
[ce-17782]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17782
[regexp]: https://gitlab.com/gitlab-org/gitlab-ce/blob/2f3dc314f42dbd79813e6251792853bc231e69dd/app/models/commit_status.rb#L99
......@@ -308,7 +308,9 @@ except master.
## `only` and `except` (complex)
> Introduced in GitLab 10.0
> `refs` and `kubernetes` policies introduced in GitLab 10.0
> `variables` policy introduced in 10.7
CAUTION: **Warning:**
This an _alpha_ feature, and it it subject to change at any time without
......
......@@ -71,7 +71,7 @@ rm NAME-OF-FILE
### Remove a directory and all of its contents
```
rm -rf NAME-OF-DIRECTORY
rm -r NAME-OF-DIRECTORY
```
### View history in the command line
......
......@@ -80,7 +80,7 @@ runners:
image: ubuntu:16.04
## Run all containers with the privileged flag enabled
## This will allow the docker:dind image to run if you need to run Docker
## This will allow the docker:stable-dind image to run if you need to run Docker
## commands. Please read the docs before turning this on:
## ref: https://docs.gitlab.com/runner/executors/kubernetes.html#using-docker-dind
##
......@@ -147,7 +147,7 @@ enable privileged mode in `values.yaml`:
```yaml
runners:
## Run all containers with the privileged flag enabled
## This will allow the docker:dind image to run if you need to run Docker
## This will allow the docker:stable-dind image to run if you need to run Docker
## commands. Please read the docs before turning this on:
## ref: https://docs.gitlab.com/runner/executors/kubernetes.html#using-docker-dind
##
......
......@@ -383,12 +383,12 @@ into your project to enable staging and canary deployments, and more.
### Custom buildpacks
If the automatic buildpack detection fails for your project, or if you want to
use a custom buildpack, you can override the buildpack using a project variable
or a `.buildpack` file in your project:
use a custom buildpack, you can override the buildpack(s) using a project variable
or a `.buildpacks` file in your project:
- **Project variable** - Create a project variable `BUILDPACK_URL` with the URL
of the buildpack to use.
- **`.buildpack` file** - Add a file in your project's repo called `.buildpack`
- **`.buildpacks` file** - Add a file in your project's repo called `.buildpacks`
and add the URL of the buildpack to use on a line in the file. If you want to
use multiple buildpacks, you can enter them in, one on each line.
......
......@@ -15,8 +15,8 @@ in the table below.
Once you have configured and enabled Custom Issue Tracker Service you'll see a link on the GitLab project pages that takes you to that custom issue tracker.
## Referencing issues
Issues are referenced with `#<ID>`, where `<ID>` is a number (example `#143`).
So with the example above, `#143` would refer to `https://customissuetracker.com/project-name/143`.
\ No newline at end of file
- Issues are referenced with `ANYTHING-<ID>`, where `ANYTHING` can be any string and `<ID>` is a number used in the target project of the custom integration (example `PROJECT-143`).
- `ANYTHING` is a placeholder to differentiate against GitLab issues, which are referenced with `#<ID>`. You can use a project name or project key to replace it for example.
- So with the example above, `PROJECT-143` would refer to `https://customissuetracker.com/project-name/143`.
\ No newline at end of file
@project_commits
Feature: Project Commits Comments
Background:
Given I sign in as a user
And I own project "Shop"
And I visit project commit page
@javascript
Scenario: I can comment on a commit
Given I leave a comment like "XML attached"
Then I should see a comment saying "XML attached"
@javascript
Scenario: I can't cancel the main form
Then I should not see the cancel comment button
@javascript
Scenario: I can preview with text
Given I write a comment like ":+1: Nice"
Then The comment preview tab should be display rendered Markdown
@javascript
Scenario: I preview a comment
Given I preview a comment text like "Bug fixed :smile:"
Then I should see the comment preview
And I should not see the comment text field
@javascript
Scenario: I can edit after preview
Given I preview a comment text like "Bug fixed :smile:"
Then I should see the comment write tab
@javascript
Scenario: I have a reset form after posting from preview
Given I preview a comment text like "Bug fixed :smile:"
And I submit the comment
Then I should see an empty comment text field
And I should not see the comment preview
@javascript
Scenario: I can delete a comment
Given I leave a comment like "XML attached"
Then I should see a comment saying "XML attached"
And I delete a comment
Then I should not see a comment saying "XML attached"
@javascript
Scenario: I can edit a comment with +1
Given I leave a comment like "XML attached"
And I edit the last comment with a +1
Then I should see +1 in the description
......@@ -6,70 +6,12 @@ module SharedNote
wait_for_requests if javascript_test?
end
step 'I delete a comment' do
page.within('.main-notes-list') do
note = find('.note')
note.hover
find('.more-actions').click
find('.more-actions .dropdown-menu li', match: :first)
accept_confirm { find(".js-note-delete").click }
end
end
step 'I haven\'t written any comment text' do
page.within(".js-main-target-form") do
fill_in "note[note]", with: ""
end
end
step 'I leave a comment like "XML attached"' do
page.within(".js-main-target-form") do
fill_in "note[note]", with: "XML attached"
click_button "Comment"
end
wait_for_requests
end
step 'I preview a comment text like "Bug fixed :smile:"' do
page.within(".js-main-target-form") do
fill_in "note[note]", with: "Bug fixed :smile:"
find('.js-md-preview-button').click
end
end
step 'I submit the comment' do
page.within(".js-main-target-form") do
click_button "Comment"
end
wait_for_requests
end
step 'I write a comment like ":+1: Nice"' do
page.within(".js-main-target-form") do
fill_in 'note[note]', with: ':+1: Nice'
end
end
step 'I should not see a comment saying "XML attached"' do
expect(page).not_to have_css(".note")
end
step 'I should not see the cancel comment button' do
page.within(".js-main-target-form") do
should_not have_link("Cancel")
end
end
step 'I should not see the comment preview' do
page.within(".js-main-target-form") do
expect(find('.js-md-preview')).not_to be_visible
end
end
step 'The comment preview tab should say there is nothing to do' do
page.within(".js-main-target-form") do
find('.js-md-preview-button').click
......@@ -77,71 +19,7 @@ module SharedNote
end
end
step 'I should not see the comment text field' do
page.within(".js-main-target-form") do
expect(find('.js-note-text')).not_to be_visible
end
end
step 'I should see a comment saying "XML attached"' do
page.within(".note") do
expect(page).to have_content("XML attached")
end
end
step 'I should see an empty comment text field' do
page.within(".js-main-target-form") do
expect(page).to have_field("note[note]", with: "")
end
end
step 'I should see the comment write tab' do
page.within(".js-main-target-form") do
expect(page).to have_css('.js-md-write-button', visible: true)
end
end
step 'The comment preview tab should be display rendered Markdown' do
page.within(".js-main-target-form") do
find('.js-md-preview-button').click
expect(find('.js-md-preview')).to have_css('gl-emoji', visible: true)
end
end
step 'I should see the comment preview' do
page.within(".js-main-target-form") do
expect(page).to have_css('.js-md-preview', visible: true)
end
end
step 'I should see no notes at all' do
expect(page).not_to have_css('.note')
end
# Markdown
step 'I edit the last comment with a +1' do
page.within(".main-notes-list") do
note = find('.note')
note.hover
note.find('.js-note-edit').click
end
page.find('.current-note-edit-form textarea')
page.within(".current-note-edit-form") do
fill_in 'note[note]', with: '+1 Awesome!'
click_button 'Save comment'
end
wait_for_requests
end
step 'I should see +1 in the description' do
page.within(".note") do
expect(page).to have_content("+1 Awesome!")
end
wait_for_requests
end
end
......@@ -21,13 +21,7 @@ Capybara.register_driver :chrome do |app|
options.add_argument("no-sandbox")
# Run headless by default unless CHROME_HEADLESS specified
unless ENV['CHROME_HEADLESS'] =~ /^(false|no|0)$/i
options.add_argument("headless")
# Chrome documentation says this flag is needed for now
# https://developers.google.com/web/updates/2017/04/headless-chrome#cli
options.add_argument("disable-gpu")
end
options.add_argument("headless") unless ENV['CHROME_HEADLESS'] =~ /^(false|no|0)$/i
# Disable /dev/shm use in CI. See https://gitlab.com/gitlab-org/gitlab-ee/issues/4252
options.add_argument("disable-dev-shm-usage") if ENV['CI'] || ENV['CI_SERVER']
......
......@@ -103,9 +103,9 @@ module API
end
def find_project(id)
if id =~ /^\d+$/
if id.is_a?(Integer) || id =~ /^\d+$/
Project.find_by(id: id)
else
elsif id.include?("/")
Project.find_by_full_path(id)
end
end
......
......@@ -338,6 +338,11 @@ module API
end
end
desc 'Get languages in project repository'
get ':id/languages' do
user_project.repository.languages.map { |language| language.values_at(:label, :value) }.to_h
end
desc 'Remove a project'
delete ":id" do
authorize! :remove_project, user_project
......
......@@ -56,29 +56,29 @@ module Banzai
# Implement in child class
# Example: project.merge_requests.find
def find_object(project, id)
def find_object(parent_object, id)
end
# Override if the link reference pattern produces a different ID (global
# ID vs internal ID, for instance) to the regular reference pattern.
def find_object_from_link(project, id)
find_object(project, id)
def find_object_from_link(parent_object, id)
find_object(parent_object, id)
end
# Implement in child class
# Example: project_merge_request_url
def url_for_object(object, project)
def url_for_object(object, parent_object)
end
def find_object_cached(project, id)
cached_call(:banzai_find_object, id, path: [object_class, project.id]) do
find_object(project, id)
def find_object_cached(parent_object, id)
cached_call(:banzai_find_object, id, path: [object_class, parent_object.id]) do
find_object(parent_object, id)
end
end
def find_object_from_link_cached(project, id)
cached_call(:banzai_find_object_from_link, id, path: [object_class, project.id]) do
find_object_from_link(project, id)
def find_object_from_link_cached(parent_object, id)
cached_call(:banzai_find_object_from_link, id, path: [object_class, parent_object.id]) do
find_object_from_link(parent_object, id)
end
end
......@@ -88,9 +88,9 @@ module Banzai
end
end
def url_for_object_cached(object, project)
cached_call(:banzai_url_for_object, object, path: [object_class, project.id]) do
url_for_object(object, project)
def url_for_object_cached(object, parent_object)
cached_call(:banzai_url_for_object, object, path: [object_class, parent_object.id]) do
url_for_object(object, parent_object)
end
end
......
......@@ -23,6 +23,8 @@ module Banzai
end
def find_object(project, id)
return unless project.is_a?(Project)
range = CommitRange.new(id, project)
range.valid_commits? ? range : nil
......
......@@ -17,6 +17,8 @@ module Banzai
end
def find_object(project, id)
return unless project.is_a?(Project)
if project && project.valid_repo?
# n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/43894
Gitlab::GitalyClient.allow_n_plus_1_calls { project.commit(id) }
......
......@@ -8,8 +8,8 @@ module Banzai
Label
end
def find_object(project, id)
find_labels(project).find(id)
def find_object(parent_object, id)
find_labels(parent_object).find(id)
end
def self.references_in(text, pattern = Label.reference_pattern)
......
......@@ -12,10 +12,14 @@ module Banzai
# 'regular' references, we need to use the global ID to disambiguate
# between group and project milestones.
def find_object(project, id)
return unless project.is_a?(Project)
find_milestone_with_finder(project, id: id)
end
def find_object_from_link(project, iid)
return unless project.is_a?(Project)
find_milestone_with_finder(project, iid: iid)
end
......@@ -40,7 +44,7 @@ module Banzai
project_path = full_project_path(namespace_ref, project_ref)
project = parent_from_ref(project_path)
return unless project
return unless project && project.is_a?(Project)
milestone_params = milestone_params(milestone_id, milestone_name)
......
......@@ -12,6 +12,8 @@ module Banzai
end
def find_object(project, id)
return unless project.is_a?(Project)
project.snippets.find_by(id: id)
end
......
......@@ -29,6 +29,8 @@ module Banzai
end
def find_object(project, id)
return unless project.is_a?(Project)
range = CommitRange.new(id, project)
range.valid_commits? ? range : nil
......
......@@ -3,13 +3,18 @@ require_dependency 'gitlab/git'
module Gitlab
COM_URL = 'https://gitlab.com'.freeze
APP_DIRS_PATTERN = %r{^/?(app|config|ee|lib|spec|\(\w*\))}
SUBDOMAIN_REGEX = %r{\Ahttps://[a-z0-9]+\.gitlab\.com\z}
def self.com?
# Check `staging?` as well to keep parity with gitlab.com
Gitlab.config.gitlab.url == COM_URL || staging?
# Check `gl_subdomain?` as well to keep parity with gitlab.com
Gitlab.config.gitlab.url == COM_URL || gl_subdomain?
end
def self.staging?
Gitlab.config.gitlab.url == 'https://staging.gitlab.com'
def self.gl_subdomain?
SUBDOMAIN_REGEX === Gitlab.config.gitlab.url
end
def self.dev_env_or_com?
Rails.env.test? || Rails.env.development? || com?
end
end
......@@ -40,7 +40,7 @@ module Gitlab
end
def self.cache_key_for_project(project)
"projects/#{project.id}/pipeline_status"
"#{Gitlab::Redis::Cache::CACHE_NAMESPACE}:projects/#{project.id}/pipeline_status"
end
def self.update_for_pipeline(pipeline)
......
......@@ -75,9 +75,6 @@ module Gitlab
end
end
# Full path to repo
attr_reader :path
# Directory name of repo
attr_reader :name
......@@ -96,14 +93,13 @@ module Gitlab
@relative_path = relative_path
@gl_repository = gl_repository
storage_path = Gitlab.config.repositories.storages[@storage].legacy_disk_path
@gitlab_projects = Gitlab::Git::GitlabProjects.new(
storage,
relative_path,
global_hooks_path: Gitlab.config.gitlab_shell.hooks_path,
logger: Rails.logger
)
@path = File.join(storage_path, @relative_path)
@name = @relative_path.split("/").last
end
......@@ -111,6 +107,12 @@ module Gitlab
path == other.path
end
def path
@path ||= File.join(
Gitlab.config.repositories.storages[@storage].legacy_disk_path, @relative_path
)
end
# Default branch in the repository
def root_ref
@root_ref ||= gitaly_migrate(:root_ref) do |is_enabled|
......@@ -139,12 +141,12 @@ module Gitlab
end
def exists?
Gitlab::GitalyClient.migrate(:repository_exists, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |enabled|
Gitlab::GitalyClient.migrate(:repository_exists, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |enabled|
if enabled
gitaly_repository_client.exists?
else
circuit_breaker.perform do
File.exist?(File.join(@path, 'refs'))
File.exist?(File.join(path, 'refs'))
end
end
end
......@@ -1018,7 +1020,7 @@ module Gitlab
if is_enabled
gitaly_repository_client.info_attributes
else
attributes_path = File.join(File.expand_path(@path), 'info', 'attributes')
attributes_path = File.join(File.expand_path(path), 'info', 'attributes')
if File.exist?(attributes_path)
File.read(attributes_path)
......@@ -1468,7 +1470,7 @@ module Gitlab
return [] if empty? || safe_query.blank?
args = %W(ls-tree --full-tree -r #{ref || root_ref} --name-status | #{safe_query})
args = %W(ls-tree -r --name-status --full-tree #{ref || root_ref} -- #{safe_query})
run_git(args).first.lines.map(&:strip)
end
......
......@@ -6,17 +6,22 @@ namespace :cache do
desc "GitLab | Clear redis cache"
task redis: :environment do
Gitlab::Redis::Cache.with do |redis|
cursor = REDIS_SCAN_START_STOP
loop do
cursor, keys = redis.scan(
cursor,
match: "#{Gitlab::Redis::Cache::CACHE_NAMESPACE}*",
count: REDIS_CLEAR_BATCH_SIZE
)
cache_key_pattern = %W[#{Gitlab::Redis::Cache::CACHE_NAMESPACE}*
projects/*/pipeline_status]
redis.del(*keys) if keys.any?
cache_key_pattern.each do |match|
cursor = REDIS_SCAN_START_STOP
loop do
cursor, keys = redis.scan(
cursor,
match: match,
count: REDIS_CLEAR_BATCH_SIZE
)
break if cursor == REDIS_SCAN_START_STOP
redis.del(*keys) if keys.any?
break if cursor == REDIS_SCAN_START_STOP
end
end
end
end
......
......@@ -16,7 +16,7 @@
"webpack-prod": "NODE_ENV=production webpack --config config/webpack.config.js"
},
"dependencies": {
"@gitlab-org/gitlab-svgs": "^1.17.0",
"@gitlab-org/gitlab-svgs": "^1.18.0",
"autosize": "^4.0.0",
"axios": "^0.17.1",
"babel-core": "^6.26.0",
......
......@@ -72,11 +72,10 @@ describe Admin::ApplicationSettingsController do
expect(ApplicationSetting.current.restricted_visibility_levels).to eq([10, 20])
end
it 'falls back to defaults when settings are omitted' do
put :update, application_setting: {}
it 'updates the restricted_visibility_levels when empty array is passed' do
put :update, application_setting: { restricted_visibility_levels: [] }
expect(response).to redirect_to(admin_application_settings_path)
expect(ApplicationSetting.current.default_project_visibility).to eq(Gitlab::VisibilityLevel::PRIVATE)
expect(ApplicationSetting.current.restricted_visibility_levels).to be_empty
end
end
......
......@@ -2,10 +2,15 @@ require 'spec_helper'
require 'rainbow/ext/string'
describe 'seed production settings' do
include StubENV
let(:settings_file) { Rails.root.join('db/fixtures/production/010_settings.rb') }
let(:settings) { Gitlab::CurrentSettings.current_application_settings }
before do
# It's important to set this variable so that we don't save a memoized
# (supposed to be) in-memory record in `Gitlab::CurrentSettings.in_memory_application_settings`
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
end
context 'GITLAB_SHARED_RUNNERS_REGISTRATION_TOKEN is set in the environment' do
before do
stub_env('GITLAB_SHARED_RUNNERS_REGISTRATION_TOKEN', '013456789')
......
......@@ -206,7 +206,7 @@ FactoryBot.define do
options do
{
image: { name: 'ruby:2.1', entrypoint: '/bin/sh' },
services: ['postgres', { name: 'docker:dind', entrypoint: '/bin/sh', command: 'sleep 30', alias: 'docker' }],
services: ['postgres', { name: 'docker:stable-dind', entrypoint: '/bin/sh', command: 'sleep 30', alias: 'docker' }],
after_script: %w(ls date),
artifacts: {
name: 'artifacts_file',
......
......@@ -32,6 +32,29 @@ feature 'Admin updates settings' do
expect(find('#application_setting_visibility_level_20')).not_to be_checked
end
scenario 'Modify import sources' do
expect(Gitlab::CurrentSettings.import_sources).not_to be_empty
page.within('.as-visibility-access') do
Gitlab::ImportSources.options.map do |name, _|
uncheck name
end
click_button 'Save changes'
end
expect(page).to have_content "Application settings saved successfully"
expect(Gitlab::CurrentSettings.import_sources).to be_empty
page.within('.as-visibility-access') do
check "Repo by URL"
click_button 'Save changes'
end
expect(page).to have_content "Application settings saved successfully"
expect(Gitlab::CurrentSettings.import_sources).to eq(['git'])
end
scenario 'Change Visibility and Access Controls' do
page.within('.as-visibility-access') do
uncheck 'Project export enabled'
......@@ -62,6 +85,26 @@ feature 'Admin updates settings' do
expect(page).to have_content "Application settings saved successfully"
end
scenario 'Modify oauth providers' do
expect(Gitlab::CurrentSettings.disabled_oauth_sign_in_sources).to be_empty
page.within('.as-signin') do
uncheck 'Google'
click_button 'Save changes'
end
expect(page).to have_content "Application settings saved successfully"
expect(Gitlab::CurrentSettings.disabled_oauth_sign_in_sources).to include('google_oauth2')
page.within('.as-signin') do
check "Google"
click_button 'Save changes'
end
expect(page).to have_content "Application settings saved successfully"
expect(Gitlab::CurrentSettings.disabled_oauth_sign_in_sources).not_to include('google_oauth2')
end
scenario 'Change Help page' do
page.within('.as-help-page') do
fill_in 'Help page text', with: 'Example text'
......@@ -211,16 +254,6 @@ feature 'Admin updates settings' do
expect(find('#service_push_channel').value).to eq '#test_channel'
end
context 'sign-in restrictions', :js do
it 'de-activates oauth sign-in source' do
page.within('.as-signin') do
find('input#application_setting_enabled_oauth_sign_in_sources_[value=gitlab]').send_keys(:return)
expect(find('.btn', text: 'GitLab.com')).not_to have_css('.active')
end
end
end
scenario 'Change Keys settings' do
page.within('.as-visibility-access') do
select 'Are forbidden', from: 'RSA SSH keys'
......
......@@ -108,4 +108,18 @@ feature 'Milestone' do
expect(page).to have_selector('.js-delete-milestone-button', count: 0)
end
end
feature 'deprecation popover', :js do
it 'opens deprecation popover' do
milestone = create(:milestone, project: project)
visit group_milestone_path(group, milestone, title: milestone.title)
expect(page).to have_selector('.milestone-deprecation-message')
find('.milestone-deprecation-message .js-popover-link').click
expect(page).to have_selector('.milestone-deprecation-message .popover')
end
end
end
require "spec_helper"
describe "User comments on commit", :js do
include Spec::Support::Helpers::Features::NotesHelpers
include RepoHelpers
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
COMMENT_TEXT = "XML attached".freeze
before do
sign_in(user)
project.add_developer(user)
visit(project_commit_path(project, sample_commit.id))
end
context "when adding new comment" do
it "adds comment" do
EMOJI = ":+1:".freeze
page.within(".js-main-target-form") do
expect(page).not_to have_link("Cancel")
fill_in("note[note]", with: "#{COMMENT_TEXT} #{EMOJI}")
# Check on `Preview` tab
click_link("Preview")
expect(find(".js-md-preview")).to have_content(COMMENT_TEXT).and have_css("gl-emoji")
expect(page).not_to have_css(".js-note-text")
# Check on `Write` tab
click_link("Write")
expect(page).to have_field("note[note]", with: "#{COMMENT_TEXT} #{EMOJI}")
# Submit comment from the `Preview` tab to get rid of a separate `it` block
# which would specially tests if everything gets cleared from the note form.
click_link("Preview")
click_button("Comment")
end
wait_for_requests
page.within(".note") do
expect(page).to have_content(COMMENT_TEXT).and have_css("gl-emoji")
end
page.within(".js-main-target-form") do
expect(page).to have_field("note[note]", with: "").and have_no_css(".js-md-preview")
end
end
end
context "when editing comment" do
before do
add_note(COMMENT_TEXT)
end
it "edits comment" do
NEW_COMMENT_TEXT = "+1 Awesome!".freeze
page.within(".main-notes-list") do
note = find(".note")
note.hover
note.find(".js-note-edit").click
end
page.find(".current-note-edit-form textarea")
page.within(".current-note-edit-form") do
fill_in("note[note]", with: NEW_COMMENT_TEXT)
click_button("Save comment")
end
wait_for_requests
page.within(".note") do
expect(page).to have_content(NEW_COMMENT_TEXT)
end
end
end
context "when deleting comment" do
before do
add_note(COMMENT_TEXT)
end
it "deletes comment" do
page.within(".note") do
expect(page).to have_content(COMMENT_TEXT)
end
page.within(".main-notes-list") do
note = find(".note")
note.hover
find(".more-actions").click
find(".more-actions .dropdown-menu li", match: :first)
accept_confirm { find(".js-note-delete").click }
end
expect(page).not_to have_css(".note")
end
end
end
require 'spec_helper'
describe 'Embedded Snippets' do
let(:snippet) { create(:personal_snippet, :public, file_name: 'random_dir.rb', content: content) }
let(:content) { "require 'fileutils'\nFileUtils.mkdir_p 'some/random_dir'\n" }
it 'loads snippet', :js do
script_url = "http://#{Capybara.current_session.server.host}:#{Capybara.current_session.server.port}/#{snippet_path(snippet, format: 'js')}"
embed_body = "<html><body><script src=\"#{script_url}\"></script></body></html>"
rack_app = proc do
['200', { 'Content-Type' => 'text/html' }, [embed_body]]
end
server = Capybara::Server.new(rack_app)
server.boot
visit("http://#{server.host}:#{server.port}/embedded_snippet.html")
expect(page).to have_content("random_dir.rb")
expect(page).to have_content("require 'fileutils'")
expect(page).to have_link('Open raw')
expect(page).to have_link('Download')
end
end
......@@ -162,4 +162,11 @@ describe IconsHelper do
expect(file_type_icon_class('file', 0, 'CHANGELOG')).to eq 'file-text-o'
end
end
describe '#external_snippet_icon' do
it 'returns external snippet icon' do
expect(external_snippet_icon('download').to_s)
.to eq("<span class=\"gl-snippet-icon gl-snippet-icon-download\"></span>")
end
end
end
require 'spec_helper'
describe SnippetsHelper do
include IconsHelper
describe '#embedded_snippet_raw_button' do
it 'gives view raw button of embedded snippets for project snippets' do
@snippet = create(:project_snippet, :public)
expect(embedded_snippet_raw_button.to_s).to eq("<a class=\"btn\" target=\"_blank\" rel=\"noopener noreferrer\" title=\"Open raw\" href=\"#{raw_project_snippet_url(@snippet.project, @snippet)}\">#{external_snippet_icon('doc_code')}</a>")
end
it 'gives view raw button of embedded snippets for personal snippets' do
@snippet = create(:personal_snippet, :public)
expect(embedded_snippet_raw_button.to_s).to eq("<a class=\"btn\" target=\"_blank\" rel=\"noopener noreferrer\" title=\"Open raw\" href=\"#{raw_snippet_url(@snippet)}\">#{external_snippet_icon('doc_code')}</a>")
end
end
describe '#embedded_snippet_download_button' do
it 'gives download button of embedded snippets for project snippets' do
@snippet = create(:project_snippet, :public)
expect(embedded_snippet_download_button.to_s).to eq("<a class=\"btn\" target=\"_blank\" title=\"Download\" rel=\"noopener noreferrer\" href=\"#{raw_project_snippet_url(@snippet.project, @snippet, inline: false)}\">#{external_snippet_icon('download')}</a>")
end
it 'gives download button of embedded snippets for personal snippets' do
@snippet = create(:personal_snippet, :public)
expect(embedded_snippet_download_button.to_s).to eq("<a class=\"btn\" target=\"_blank\" title=\"Download\" rel=\"noopener noreferrer\" href=\"#{raw_snippet_url(@snippet, inline: false)}\">#{external_snippet_icon('download')}</a>")
end
end
end
require 'spec_helper'
describe 'gollum' do
let(:project) { create(:project) }
let(:user) { project.owner }
let(:wiki) { ProjectWiki.new(project, user) }
let(:gollum_wiki) { Gollum::Wiki.new(wiki.repository.path) }
before do
create_page(page_name, 'content1')
end
after do
destroy_page(page_name)
end
context 'with simple paths' do
let(:page_name) { 'page1' }
it 'returns the entry hash if it matches the file name' do
expect(tree_entry(page_name)).not_to be_nil
end
it 'returns nil if the path does not fit completely' do
expect(tree_entry("foo/#{page_name}")).to be_nil
end
end
context 'with complex paths' do
let(:page_name) { '/foo/bar/page2' }
it 'returns the entry hash if it matches the file name' do
expect(tree_entry(page_name)).not_to be_nil
end
it 'returns nil if the path does not fit completely' do
expect(tree_entry("foo1/bar/page2")).to be_nil
expect(tree_entry("foo/bar1/page2")).to be_nil
end
end
def tree_entry(name)
gollum_wiki.repo.git.tree_entry(wiki_commits[0].commit, name + '.md')
end
def wiki_commits
gollum_wiki.repo.commits
end
def commit_details
Gitlab::Git::Wiki::CommitDetails.new(user.name, user.email, "test commit")
end
def create_page(name, content)
wiki.wiki.write_page(name, :markdown, content, commit_details)
end
def destroy_page(name)
page = wiki.find_page(name).page
wiki.delete_page(page, "test commit")
end
end
......@@ -3,12 +3,11 @@ import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import {
getSelector,
togglePopover,
dismiss,
mouseleave,
mouseenter,
inserted,
} from '~/feature_highlight/feature_highlight_helper';
import { togglePopover } from '~/shared/popover';
import getSetTimeoutPromise from 'spec/helpers/set_timeout_promise_helper';
describe('feature highlight helper', () => {
......@@ -19,110 +18,6 @@ describe('feature highlight helper', () => {
});
});
describe('togglePopover', () => {
describe('togglePopover(true)', () => {
it('returns true when popover is shown', () => {
const context = {
hasClass: () => false,
popover: () => {},
toggleClass: () => {},
};
expect(togglePopover.call(context, true)).toEqual(true);
});
it('returns false when popover is already shown', () => {
const context = {
hasClass: () => true,
};
expect(togglePopover.call(context, true)).toEqual(false);
});
it('shows popover', (done) => {
const context = {
hasClass: () => false,
popover: () => {},
toggleClass: () => {},
};
spyOn(context, 'popover').and.callFake((method) => {
expect(method).toEqual('show');
done();
});
togglePopover.call(context, true);
});
it('adds disable-animation and js-popover-show class', (done) => {
const context = {
hasClass: () => false,
popover: () => {},
toggleClass: () => {},
};
spyOn(context, 'toggleClass').and.callFake((classNames, show) => {
expect(classNames).toEqual('disable-animation js-popover-show');
expect(show).toEqual(true);
done();
});
togglePopover.call(context, true);
});
});
describe('togglePopover(false)', () => {
it('returns true when popover is hidden', () => {
const context = {
hasClass: () => true,
popover: () => {},
toggleClass: () => {},
};
expect(togglePopover.call(context, false)).toEqual(true);
});
it('returns false when popover is already hidden', () => {
const context = {
hasClass: () => false,
};
expect(togglePopover.call(context, false)).toEqual(false);
});
it('hides popover', (done) => {
const context = {
hasClass: () => true,
popover: () => {},
toggleClass: () => {},
};
spyOn(context, 'popover').and.callFake((method) => {
expect(method).toEqual('hide');
done();
});
togglePopover.call(context, false);
});
it('removes disable-animation and js-popover-show class', (done) => {
const context = {
hasClass: () => true,
popover: () => {},
toggleClass: () => {},
};
spyOn(context, 'toggleClass').and.callFake((classNames, show) => {
expect(classNames).toEqual('disable-animation js-popover-show');
expect(show).toEqual(false);
done();
});
togglePopover.call(context, false);
});
});
});
describe('dismiss', () => {
let mock;
const context = {
......@@ -163,56 +58,6 @@ describe('feature highlight helper', () => {
});
});
describe('mouseleave', () => {
it('calls hide popover if .popover:hover is false', () => {
const fakeJquery = {
length: 0,
};
spyOn($.fn, 'init').and.callFake(selector => (selector === '.popover:hover' ? fakeJquery : $.fn));
spyOn(togglePopover, 'call');
mouseleave();
expect(togglePopover.call).toHaveBeenCalledWith(jasmine.any(Object), false);
});
it('does not call hide popover if .popover:hover is true', () => {
const fakeJquery = {
length: 1,
};
spyOn($.fn, 'init').and.callFake(selector => (selector === '.popover:hover' ? fakeJquery : $.fn));
spyOn(togglePopover, 'call');
mouseleave();
expect(togglePopover.call).not.toHaveBeenCalledWith(false);
});
});
describe('mouseenter', () => {
const context = {};
it('shows popover', () => {
spyOn(togglePopover, 'call').and.returnValue(false);
mouseenter.call(context);
expect(togglePopover.call).toHaveBeenCalledWith(jasmine.any(Object), true);
});
it('registers mouseleave event if popover is showed', (done) => {
spyOn(togglePopover, 'call').and.returnValue(true);
spyOn($.fn, 'on').and.callFake((eventName) => {
expect(eventName).toEqual('mouseleave');
done();
});
mouseenter.call(context);
});
it('does not register mouseleave event if popover is not showed', () => {
spyOn(togglePopover, 'call').and.returnValue(false);
const spy = spyOn($.fn, 'on').and.callFake(() => {});
mouseenter.call(context);
expect(spy).not.toHaveBeenCalled();
});
});
describe('inserted', () => {
it('registers click event callback', (done) => {
const context = {
......
import $ from 'jquery';
import * as featureHighlightHelper from '~/feature_highlight/feature_highlight_helper';
import * as featureHighlight from '~/feature_highlight/feature_highlight';
import * as popover from '~/shared/popover';
import axios from '~/lib/utils/axios_utils';
import MockAdapter from 'axios-mock-adapter';
......@@ -29,7 +29,6 @@ describe('feature highlight', () => {
mock = new MockAdapter(axios);
mock.onGet('/test').reply(200);
spyOn(window, 'addEventListener');
spyOn(window, 'removeEventListener');
featureHighlight.setupFeatureHighlightPopover('test', 0);
});
......@@ -45,14 +44,14 @@ describe('feature highlight', () => {
});
it('setup mouseenter', () => {
const toggleSpy = spyOn(featureHighlightHelper.togglePopover, 'call');
const toggleSpy = spyOn(popover.togglePopover, 'call');
$(selector).trigger('mouseenter');
expect(toggleSpy).toHaveBeenCalledWith(jasmine.any(Object), true);
});
it('setup debounced mouseleave', (done) => {
const toggleSpy = spyOn(featureHighlightHelper.togglePopover, 'call');
const toggleSpy = spyOn(popover.togglePopover, 'call');
$(selector).trigger('mouseleave');
// Even though we've set the debounce to 0ms, setTimeout is needed for the debounce
......@@ -64,12 +63,7 @@ describe('feature highlight', () => {
it('setup show.bs.popover', () => {
$(selector).trigger('show.bs.popover');
expect(window.addEventListener).toHaveBeenCalledWith('scroll', jasmine.any(Function));
});
it('setup hide.bs.popover', () => {
$(selector).trigger('hide.bs.popover');
expect(window.removeEventListener).toHaveBeenCalledWith('scroll', jasmine.any(Function));
expect(window.addEventListener).toHaveBeenCalledWith('scroll', jasmine.any(Function), { once: true });
});
it('removes disabled attribute', () => {
......@@ -85,7 +79,7 @@ describe('feature highlight', () => {
it('toggles when clicked', () => {
$(selector).trigger('mouseenter');
const popoverId = $(selector).attr('aria-describedby');
const toggleSpy = spyOn(featureHighlightHelper.togglePopover, 'call');
const toggleSpy = spyOn(popover.togglePopover, 'call');
$(`#${popoverId} .dismiss-feature-highlight`).click();
......
......@@ -27,7 +27,7 @@ describe('RepoLoadingFile', () => {
const lines = [...container.querySelectorAll(':scope > div')];
expect(container).toBeTruthy();
expect(lines.length).toEqual(6);
expect(lines.length).toEqual(3);
assertLines(lines);
});
}
......
......@@ -133,10 +133,7 @@ describe('IDE commit module actions', () => {
store
.dispatch('commit/checkCommitStatus')
.then(() => {
expect(service.getBranchData).toHaveBeenCalledWith(
'abcproject',
'master',
);
expect(service.getBranchData).toHaveBeenCalledWith('abcproject', 'master');
done();
})
......@@ -230,9 +227,7 @@ describe('IDE commit module actions', () => {
branch,
})
.then(() => {
expect(
store.state.projects.abcproject.branches.master.workingReference,
).toBe(data.id);
expect(store.state.projects.abcproject.branches.master.workingReference).toBe(data.id);
})
.then(done)
.catch(done.fail);
......@@ -317,26 +312,7 @@ describe('IDE commit module actions', () => {
branch,
})
.then(() => {
expect(router.push).toHaveBeenCalledWith(
`/project/abcproject/blob/master/${f.path}`,
);
})
.then(done)
.catch(done.fail);
});
it('resets stores commit actions', done => {
store.state.commit.commitAction = consts.COMMIT_TO_NEW_BRANCH;
store
.dispatch('commit/updateFilesAfterCommit', {
data,
branch,
})
.then(() => {
expect(store.state.commit.commitAction).not.toBe(
consts.COMMIT_TO_NEW_BRANCH,
);
expect(router.push).toHaveBeenCalledWith(`/project/abcproject/blob/master/${f.path}`);
})
.then(done)
.catch(done.fail);
......@@ -448,33 +424,60 @@ describe('IDE commit module actions', () => {
store
.dispatch('commit/commitChanges')
.then(() => {
expect(store.state.openFiles[0].lastCommit.message).toBe(
'test message',
);
expect(store.state.openFiles[0].lastCommit.message).toBe('test message');
done();
})
.catch(done.fail);
});
it('redirects to new merge request page', done => {
spyOn(eventHub, '$on');
store.state.commit.commitAction = '3';
it('resets stores commit actions', done => {
store.state.commit.commitAction = consts.COMMIT_TO_NEW_BRANCH;
store
.dispatch('commit/commitChanges')
.then(() => {
expect(urlUtils.visitUrl).toHaveBeenCalledWith(
`webUrl/merge_requests/new?merge_request[source_branch]=${
store.getters['commit/newBranchName']
}&merge_request[target_branch]=master`,
);
done();
expect(store.state.commit.commitAction).not.toBe(consts.COMMIT_TO_NEW_BRANCH);
})
.then(done)
.catch(done.fail);
});
describe('merge request', () => {
it('redirects to new merge request page', done => {
spyOn(eventHub, '$on');
store.state.commit.commitAction = '3';
store
.dispatch('commit/commitChanges')
.then(() => {
expect(urlUtils.visitUrl).toHaveBeenCalledWith(
`webUrl/merge_requests/new?merge_request[source_branch]=${
store.getters['commit/newBranchName']
}&merge_request[target_branch]=master`,
);
done();
})
.catch(done.fail);
});
it('resets changed files before redirecting', done => {
spyOn(eventHub, '$on');
store.state.commit.commitAction = '3';
store
.dispatch('commit/commitChanges')
.then(() => {
expect(store.state.changedFiles.length).toBe(0);
done();
})
.catch(done.fail);
});
});
});
describe('failed', () => {
......
export const pipelineWithStages = {
id: 20333396,
user: {
id: 128633,
name: 'Rémy Coutable',
username: 'rymai',
state: 'active',
avatar_url:
'https://secure.gravatar.com/avatar/263da227929cc0035cb0eba512bcf81a?s=80\u0026d=identicon',
web_url: 'https://gitlab.com/rymai',
path: '/rymai',
},
active: true,
coverage: '58.24',
source: 'push',
created_at: '2018-04-11T14:04:53.881Z',
updated_at: '2018-04-11T14:05:00.792Z',
path: '/gitlab-org/gitlab-ee/pipelines/20333396',
flags: {
latest: true,
stuck: false,
auto_devops: false,
yaml_errors: false,
retryable: false,
cancelable: true,
failure_reason: false,
},
details: {
status: {
icon: 'status_running',
text: 'running',
label: 'running',
group: 'running',
has_details: true,
details_path: '/gitlab-org/gitlab-ee/pipelines/20333396',
favicon:
'https://assets.gitlab-static.net/assets/ci_favicons/favicon_status_running-2eb56be2871937954b2ba6d6f4ee9fdf7e5e1c146ac45f7be98119ccaca1aca9.ico',
},
duration: null,
finished_at: null,
stages: [
{
name: 'build',
title: 'build: skipped',
status: {
icon: 'status_skipped',
text: 'skipped',
label: 'skipped',
group: 'skipped',
has_details: true,
details_path: '/gitlab-org/gitlab-ee/pipelines/20333396#build',
favicon:
'https://assets.gitlab-static.net/assets/ci_favicons/favicon_status_skipped-a2eee568a5bffdb494050c7b62dde241de9189280836288ac8923d369f16222d.ico',
},
path: '/gitlab-org/gitlab-ee/pipelines/20333396#build',
dropdown_path: '/gitlab-org/gitlab-ee/pipelines/20333396/stage.json?stage=build',
},
{
name: 'prepare',
title: 'prepare: passed',
status: {
icon: 'status_success',
text: 'passed',
label: 'passed',
group: 'success',
has_details: true,
details_path: '/gitlab-org/gitlab-ee/pipelines/20333396#prepare',
favicon:
'https://assets.gitlab-static.net/assets/ci_favicons/favicon_status_success-26f59841becbef8c6fe414e9e74471d8bfd6a91b5855c19fe7f5923a40a7da47.ico',
},
path: '/gitlab-org/gitlab-ee/pipelines/20333396#prepare',
dropdown_path: '/gitlab-org/gitlab-ee/pipelines/20333396/stage.json?stage=prepare',
},
{
name: 'test',
title: 'test: running',
status: {
icon: 'status_running',
text: 'running',
label: 'running',
group: 'running',
has_details: true,
details_path: '/gitlab-org/gitlab-ee/pipelines/20333396#test',
favicon:
'https://assets.gitlab-static.net/assets/ci_favicons/favicon_status_running-2eb56be2871937954b2ba6d6f4ee9fdf7e5e1c146ac45f7be98119ccaca1aca9.ico',
},
path: '/gitlab-org/gitlab-ee/pipelines/20333396#test',
dropdown_path: '/gitlab-org/gitlab-ee/pipelines/20333396/stage.json?stage=test',
},
{
name: 'post-test',
title: 'post-test: created',
status: {
icon: 'status_created',
text: 'created',
label: 'created',
group: 'created',
has_details: true,
details_path: '/gitlab-org/gitlab-ee/pipelines/20333396#post-test',
favicon:
'https://assets.gitlab-static.net/assets/ci_favicons/favicon_status_created-e997aa0b7db73165df8a9d6803932b18d7b7cc37d604d2d96e378fea2dba9c5f.ico',
},
path: '/gitlab-org/gitlab-ee/pipelines/20333396#post-test',
dropdown_path: '/gitlab-org/gitlab-ee/pipelines/20333396/stage.json?stage=post-test',
},
{
name: 'pages',
title: 'pages: created',
status: {
icon: 'status_created',
text: 'created',
label: 'created',
group: 'created',
has_details: true,
details_path: '/gitlab-org/gitlab-ee/pipelines/20333396#pages',
favicon:
'https://assets.gitlab-static.net/assets/ci_favicons/favicon_status_created-e997aa0b7db73165df8a9d6803932b18d7b7cc37d604d2d96e378fea2dba9c5f.ico',
},
path: '/gitlab-org/gitlab-ee/pipelines/20333396#pages',
dropdown_path: '/gitlab-org/gitlab-ee/pipelines/20333396/stage.json?stage=pages',
},
{
name: 'post-cleanup',
title: 'post-cleanup: created',
status: {
icon: 'status_created',
text: 'created',
label: 'created',
group: 'created',
has_details: true,
details_path: '/gitlab-org/gitlab-ee/pipelines/20333396#post-cleanup',
favicon:
'https://assets.gitlab-static.net/assets/ci_favicons/favicon_status_created-e997aa0b7db73165df8a9d6803932b18d7b7cc37d604d2d96e378fea2dba9c5f.ico',
},
path: '/gitlab-org/gitlab-ee/pipelines/20333396#post-cleanup',
dropdown_path: '/gitlab-org/gitlab-ee/pipelines/20333396/stage.json?stage=post-cleanup',
},
],
artifacts: [
{
name: 'gitlab:assets:compile',
expired: false,
expire_at: '2018-05-12T14:22:54.730Z',
path: '/gitlab-org/gitlab-ee/-/jobs/62411438/artifacts/download',
keep_path: '/gitlab-org/gitlab-ee/-/jobs/62411438/artifacts/keep',
browse_path: '/gitlab-org/gitlab-ee/-/jobs/62411438/artifacts/browse',
},
{
name: 'rspec-mysql 12 28',
expired: false,
expire_at: '2018-05-12T14:22:45.136Z',
path: '/gitlab-org/gitlab-ee/-/jobs/62411397/artifacts/download',
keep_path: '/gitlab-org/gitlab-ee/-/jobs/62411397/artifacts/keep',
browse_path: '/gitlab-org/gitlab-ee/-/jobs/62411397/artifacts/browse',
},
{
name: 'rspec-mysql 6 28',
expired: false,
expire_at: '2018-05-12T14:22:41.523Z',
path: '/gitlab-org/gitlab-ee/-/jobs/62411391/artifacts/download',
keep_path: '/gitlab-org/gitlab-ee/-/jobs/62411391/artifacts/keep',
browse_path: '/gitlab-org/gitlab-ee/-/jobs/62411391/artifacts/browse',
},
{
name: 'rspec-pg geo 0 1',
expired: false,
expire_at: '2018-05-12T14:22:13.287Z',
path: '/gitlab-org/gitlab-ee/-/jobs/62411353/artifacts/download',
keep_path: '/gitlab-org/gitlab-ee/-/jobs/62411353/artifacts/keep',
browse_path: '/gitlab-org/gitlab-ee/-/jobs/62411353/artifacts/browse',
},
{
name: 'rspec-mysql 0 28',
expired: false,
expire_at: '2018-05-12T14:22:06.834Z',
path: '/gitlab-org/gitlab-ee/-/jobs/62411385/artifacts/download',
keep_path: '/gitlab-org/gitlab-ee/-/jobs/62411385/artifacts/keep',
browse_path: '/gitlab-org/gitlab-ee/-/jobs/62411385/artifacts/browse',
},
{
name: 'spinach-mysql 0 2',
expired: false,
expire_at: '2018-05-12T14:21:51.409Z',
path: '/gitlab-org/gitlab-ee/-/jobs/62411423/artifacts/download',
keep_path: '/gitlab-org/gitlab-ee/-/jobs/62411423/artifacts/keep',
browse_path: '/gitlab-org/gitlab-ee/-/jobs/62411423/artifacts/browse',
},
{
name: 'karma',
expired: false,
expire_at: '2018-05-12T14:21:20.934Z',
path: '/gitlab-org/gitlab-ee/-/jobs/62411440/artifacts/download',
keep_path: '/gitlab-org/gitlab-ee/-/jobs/62411440/artifacts/keep',
browse_path: '/gitlab-org/gitlab-ee/-/jobs/62411440/artifacts/browse',
},
{
name: 'spinach-pg 0 2',
expired: false,
expire_at: '2018-05-12T14:20:01.028Z',
path: '/gitlab-org/gitlab-ee/-/jobs/62411419/artifacts/download',
keep_path: '/gitlab-org/gitlab-ee/-/jobs/62411419/artifacts/keep',
browse_path: '/gitlab-org/gitlab-ee/-/jobs/62411419/artifacts/browse',
},
{
name: 'spinach-pg 1 2',
expired: false,
expire_at: '2018-05-12T14:19:04.336Z',
path: '/gitlab-org/gitlab-ee/-/jobs/62411421/artifacts/download',
keep_path: '/gitlab-org/gitlab-ee/-/jobs/62411421/artifacts/keep',
browse_path: '/gitlab-org/gitlab-ee/-/jobs/62411421/artifacts/browse',
},
{
name: 'sast',
expired: null,
expire_at: null,
path: '/gitlab-org/gitlab-ee/-/jobs/62411442/artifacts/download',
browse_path: '/gitlab-org/gitlab-ee/-/jobs/62411442/artifacts/browse',
},
{
name: 'codequality',
expired: false,
expire_at: '2018-04-18T14:16:24.484Z',
path: '/gitlab-org/gitlab-ee/-/jobs/62411441/artifacts/download',
keep_path: '/gitlab-org/gitlab-ee/-/jobs/62411441/artifacts/keep',
browse_path: '/gitlab-org/gitlab-ee/-/jobs/62411441/artifacts/browse',
},
{
name: 'cache gems',
expired: null,
expire_at: null,
path: '/gitlab-org/gitlab-ee/-/jobs/62411447/artifacts/download',
browse_path: '/gitlab-org/gitlab-ee/-/jobs/62411447/artifacts/browse',
},
{
name: 'dependency_scanning',
expired: null,
expire_at: null,
path: '/gitlab-org/gitlab-ee/-/jobs/62411443/artifacts/download',
browse_path: '/gitlab-org/gitlab-ee/-/jobs/62411443/artifacts/browse',
},
{
name: 'compile-assets',
expired: false,
expire_at: '2018-04-18T14:12:07.638Z',
path: '/gitlab-org/gitlab-ee/-/jobs/62411334/artifacts/download',
keep_path: '/gitlab-org/gitlab-ee/-/jobs/62411334/artifacts/keep',
browse_path: '/gitlab-org/gitlab-ee/-/jobs/62411334/artifacts/browse',
},
{
name: 'setup-test-env',
expired: false,
expire_at: '2018-04-18T14:10:27.024Z',
path: '/gitlab-org/gitlab-ee/-/jobs/62411336/artifacts/download',
keep_path: '/gitlab-org/gitlab-ee/-/jobs/62411336/artifacts/keep',
browse_path: '/gitlab-org/gitlab-ee/-/jobs/62411336/artifacts/browse',
},
{
name: 'retrieve-tests-metadata',
expired: false,
expire_at: '2018-05-12T14:06:35.926Z',
path: '/gitlab-org/gitlab-ee/-/jobs/62411333/artifacts/download',
keep_path: '/gitlab-org/gitlab-ee/-/jobs/62411333/artifacts/keep',
browse_path: '/gitlab-org/gitlab-ee/-/jobs/62411333/artifacts/browse',
},
],
manual_actions: [
{
name: 'package-and-qa',
path: '/gitlab-org/gitlab-ee/-/jobs/62411330/play',
playable: true,
},
{
name: 'review-docs-deploy',
path: '/gitlab-org/gitlab-ee/-/jobs/62411332/play',
playable: true,
},
],
},
ref: {
name: 'master',
path: '/gitlab-org/gitlab-ee/commits/master',
tag: false,
branch: true,
},
commit: {
id: 'e6a2885c503825792cb8a84a8731295e361bd059',
short_id: 'e6a2885c',
title: "Merge branch 'ce-to-ee-2018-04-11' into 'master'",
created_at: '2018-04-11T14:04:39.000Z',
parent_ids: [
'5d9b5118f6055f72cff1a82b88133609912f2c1d',
'6fdc6ee76a8062fe41b1a33f7c503334a6ebdc02',
],
message:
"Merge branch 'ce-to-ee-2018-04-11' into 'master'\n\nCE upstream - 2018-04-11 12:26 UTC\n\nSee merge request gitlab-org/gitlab-ee!5326",
author_name: 'Rémy Coutable',
author_email: 'remy@rymai.me',
authored_date: '2018-04-11T14:04:39.000Z',
committer_name: 'Rémy Coutable',
committer_email: 'remy@rymai.me',
committed_date: '2018-04-11T14:04:39.000Z',
author: {
id: 128633,
name: 'Rémy Coutable',
username: 'rymai',
state: 'active',
avatar_url:
'https://secure.gravatar.com/avatar/263da227929cc0035cb0eba512bcf81a?s=80\u0026d=identicon',
web_url: 'https://gitlab.com/rymai',
path: '/rymai',
},
author_gravatar_url:
'https://secure.gravatar.com/avatar/263da227929cc0035cb0eba512bcf81a?s=80\u0026d=identicon',
commit_url:
'https://gitlab.com/gitlab-org/gitlab-ee/commit/e6a2885c503825792cb8a84a8731295e361bd059',
commit_path: '/gitlab-org/gitlab-ee/commit/e6a2885c503825792cb8a84a8731295e361bd059',
},
cancel_path: '/gitlab-org/gitlab-ee/pipelines/20333396/cancel',
triggered_by: null,
triggered: [],
};
export const stageReply = {
html:
'\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="karma - failed \u0026lt;br\u0026gt; (script failure)" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62402048"\u003e\u003cspan class="ci-status-icon ci-status-icon-failed"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_failed"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003ekarma\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62402048/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="codequality - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398081"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003ecodequality\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398081/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="db:check-schema-pg - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398066"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003edb:check-schema-pg\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398066/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="db:migrate:reset-mysql - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398065"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003edb:migrate:reset-mysql\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398065/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="db:migrate:reset-pg - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398064"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003edb:migrate:reset-pg\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398064/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="db:rollback-mysql - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398070"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003edb:rollback-mysql\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398070/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="db:rollback-pg - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398069"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003edb:rollback-pg\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398069/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="dependency_scanning - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398083"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003edependency_scanning\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398083/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="docs lint - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398061"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003edocs lint\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398061/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="downtime_check - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398062"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003edowntime_check\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398062/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="ee_compat_check - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398063"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003eee_compat_check\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398063/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="gitlab:assets:compile - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398075"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003egitlab:assets:compile\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398075/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="gitlab:setup-mysql - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398073"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003egitlab:setup-mysql\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398073/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="gitlab:setup-pg - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398071"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003egitlab:setup-pg\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398071/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="gitlab_git_test - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398086"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003egitlab_git_test\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398086/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="migration:path-mysql - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398068"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003emigration:path-mysql\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398068/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="migration:path-pg - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398067"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003emigration:path-pg\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398067/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="qa:internal - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398084"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003eqa:internal\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398084/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="qa:selectors - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398085"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003eqa:selectors\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398085/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-mysql 0 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398020"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-mysql 0 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398020/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-mysql 1 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398022"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-mysql 1 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398022/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-mysql 10 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398033"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-mysql 10 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398033/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-mysql 11 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398034"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-mysql 11 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398034/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-mysql 12 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398035"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-mysql 12 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398035/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-mysql 13 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398036"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-mysql 13 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398036/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-mysql 14 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398037"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-mysql 14 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398037/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-mysql 15 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398038"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-mysql 15 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398038/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-mysql 16 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398039"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-mysql 16 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398039/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-mysql 17 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398040"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-mysql 17 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398040/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-mysql 18 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398041"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-mysql 18 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398041/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-mysql 19 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398042"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-mysql 19 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398042/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-mysql 2 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398024"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-mysql 2 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398024/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-mysql 20 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398043"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-mysql 20 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398043/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-mysql 21 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398044"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-mysql 21 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398044/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-mysql 22 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398046"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-mysql 22 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398046/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-mysql 23 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398047"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-mysql 23 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398047/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-mysql 24 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398048"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-mysql 24 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398048/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-mysql 25 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398049"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-mysql 25 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398049/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-mysql 26 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398050"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-mysql 26 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398050/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-mysql 27 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398051"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-mysql 27 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398051/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-mysql 3 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398025"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-mysql 3 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398025/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-mysql 4 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398027"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-mysql 4 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398027/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-mysql 5 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398028"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-mysql 5 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398028/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-mysql 6 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398029"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-mysql 6 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398029/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-mysql 7 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398030"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-mysql 7 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398030/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-mysql 8 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398031"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-mysql 8 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398031/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-mysql 9 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398032"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-mysql 9 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398032/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-pg 0 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62397981"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-pg 0 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62397981/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-pg 1 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62397985"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-pg 1 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62397985/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-pg 10 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398000"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-pg 10 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398000/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-pg 11 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398001"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-pg 11 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398001/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-pg 12 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398002"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-pg 12 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398002/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-pg 13 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398003"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-pg 13 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398003/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-pg 14 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398004"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-pg 14 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398004/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-pg 15 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398006"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-pg 15 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398006/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-pg 16 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398007"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-pg 16 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398007/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-pg 17 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398008"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-pg 17 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398008/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-pg 18 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398009"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-pg 18 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398009/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-pg 19 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398010"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-pg 19 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398010/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-pg 2 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62397986"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-pg 2 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62397986/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-pg 20 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398012"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-pg 20 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398012/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-pg 21 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398013"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-pg 21 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398013/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-pg 22 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398014"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-pg 22 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398014/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-pg 23 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398015"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-pg 23 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398015/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-pg 24 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398016"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-pg 24 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398016/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-pg 25 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398017"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-pg 25 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398017/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-pg 26 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398018"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-pg 26 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398018/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-pg 27 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398019"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-pg 27 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398019/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-pg 3 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62397988"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-pg 3 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62397988/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-pg 4 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62397989"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-pg 4 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62397989/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-pg 5 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62397991"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-pg 5 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62397991/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-pg 6 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62397993"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-pg 6 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62397993/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-pg 7 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62397994"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-pg 7 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62397994/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-pg 8 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62397995"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-pg 8 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62397995/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="rspec-pg 9 28 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62397996"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003erspec-pg 9 28\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62397996/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="sast - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398082"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003esast\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398082/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="spinach-mysql 0 2 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398058"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003espinach-mysql 0 2\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398058/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="spinach-mysql 1 2 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398059"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003espinach-mysql 1 2\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398059/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="spinach-pg 0 2 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398053"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003espinach-pg 0 2\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398053/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="spinach-pg 1 2 - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398056"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003espinach-pg 1 2\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398056/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ca class="mini-pipeline-graph-dropdown-item" data-toggle="tooltip" data-title="static-analysis - passed" data-html="true" data-container="body" href="/gitlab-org/gitlab-ce/-/jobs/62398060"\u003e\u003cspan class="ci-status-icon ci-status-icon-success"\u003e\u003csvg\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#status_success"\u003e\u003c/use\u003e\u003c/svg\u003e\u003c/span\u003e\n\u003cspan class="ci-build-text"\u003estatic-analysis\u003c/span\u003e\n\u003c/a\u003e\u003ca class="ci-action-icon-wrapper js-ci-action-icon" data-toggle="tooltip" data-title="Retry" data-container="body" rel="nofollow" data-method="post" href="/gitlab-org/gitlab-ce/-/jobs/62398060/retry"\u003e\u003csvg class=" icon-action-retry"\u003e\u003cuse xlink:href="https://gitlab.com/assets/icons-fe86f87a3d244c952cc0ec8d7f88c5effefcbe454d751d8449d4a1a32aaaf9a0.svg#retry"\u003e\u003c/use\u003e\u003c/svg\u003e\n\u003c/a\u003e\n\u003c/li\u003e\n',
};
......@@ -4,6 +4,7 @@ import axios from '~/lib/utils/axios_utils';
import pipelinesComp from '~/pipelines/components/pipelines.vue';
import Store from '~/pipelines/stores/pipelines_store';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { pipelineWithStages, stageReply } from './mock_data';
describe('Pipelines', () => {
const jsonFixtureName = 'pipelines/pipelines.json';
......@@ -668,4 +669,79 @@ describe('Pipelines', () => {
});
});
});
describe('updates results when a staged is clicked', () => {
beforeEach(() => {
const copyPipeline = Object.assign({}, pipelineWithStages);
copyPipeline.id += 1;
mock
.onGet('twitter/flight/pipelines.json').reply(200, {
pipelines: [pipelineWithStages],
count: {
all: 1,
finished: 1,
pending: 0,
running: 0,
},
}, {
'POLL-INTERVAL': 100,
})
.onGet(pipelineWithStages.details.stages[0].dropdown_path)
.reply(200, stageReply);
vm = mountComponent(PipelinesComponent, {
store: new Store(),
hasGitlabCi: true,
canCreatePipeline: true,
...paths,
});
});
describe('when a request is being made', () => {
it('stops polling, cancels the request, fetches pipelines & restarts polling', (done) => {
spyOn(vm.poll, 'stop');
spyOn(vm.poll, 'restart');
spyOn(vm, 'getPipelines').and.returnValue(Promise.resolve());
spyOn(vm.service.cancelationSource, 'cancel').and.callThrough();
setTimeout(() => {
vm.isMakingRequest = true;
return vm.$nextTick()
.then(() => {
vm.$el.querySelector('.js-builds-dropdown-button').click();
})
.then(() => {
expect(vm.service.cancelationSource.cancel).toHaveBeenCalled();
expect(vm.poll.stop).toHaveBeenCalled();
setTimeout(() => {
expect(vm.getPipelines).toHaveBeenCalled();
expect(vm.poll.restart).toHaveBeenCalled();
done();
}, 0);
});
}, 0);
});
});
describe('when no request is being made', () => {
it('stops polling, fetches pipelines & restarts polling', (done) => {
spyOn(vm.poll, 'stop');
spyOn(vm.poll, 'restart');
spyOn(vm, 'getPipelines').and.returnValue(Promise.resolve());
setTimeout(() => {
vm.$el.querySelector('.js-builds-dropdown-button').click();
expect(vm.poll.stop).toHaveBeenCalled();
setTimeout(() => {
expect(vm.getPipelines).toHaveBeenCalled();
expect(vm.poll.restart).toHaveBeenCalled();
done();
}, 0);
}, 0);
});
});
});
});
......@@ -2,6 +2,7 @@ import Vue from 'vue';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import stage from '~/pipelines/components/stage.vue';
import eventHub from '~/pipelines/event_hub';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('Pipelines stage component', () => {
......@@ -43,13 +44,15 @@ describe('Pipelines stage component', () => {
mock.onGet('path.json').reply(200, { html: 'foo' });
});
it('should render the received data', done => {
it('should render the received data and emit `clickedDropdown` event', done => {
spyOn(eventHub, '$emit');
component.$el.querySelector('button').click();
setTimeout(() => {
expect(
component.$el.querySelector('.js-builds-dropdown-container ul').textContent.trim(),
).toEqual('foo');
expect(eventHub.$emit).toHaveBeenCalledWith('clickedDropdown');
done();
}, 0);
});
......
import $ from 'jquery';
import {
togglePopover,
mouseleave,
mouseenter,
} from '~/shared/popover';
describe('popover', () => {
describe('togglePopover', () => {
describe('togglePopover(true)', () => {
it('returns true when popover is shown', () => {
const context = {
hasClass: () => false,
popover: () => {},
toggleClass: () => {},
};
expect(togglePopover.call(context, true)).toEqual(true);
});
it('returns false when popover is already shown', () => {
const context = {
hasClass: () => true,
};
expect(togglePopover.call(context, true)).toEqual(false);
});
it('shows popover', (done) => {
const context = {
hasClass: () => false,
popover: () => {},
toggleClass: () => {},
};
spyOn(context, 'popover').and.callFake((method) => {
expect(method).toEqual('show');
done();
});
togglePopover.call(context, true);
});
it('adds disable-animation and js-popover-show class', (done) => {
const context = {
hasClass: () => false,
popover: () => {},
toggleClass: () => {},
};
spyOn(context, 'toggleClass').and.callFake((classNames, show) => {
expect(classNames).toEqual('disable-animation js-popover-show');
expect(show).toEqual(true);
done();
});
togglePopover.call(context, true);
});
});
describe('togglePopover(false)', () => {
it('returns true when popover is hidden', () => {
const context = {
hasClass: () => true,
popover: () => {},
toggleClass: () => {},
};
expect(togglePopover.call(context, false)).toEqual(true);
});
it('returns false when popover is already hidden', () => {
const context = {
hasClass: () => false,
};
expect(togglePopover.call(context, false)).toEqual(false);
});
it('hides popover', (done) => {
const context = {
hasClass: () => true,
popover: () => {},
toggleClass: () => {},
};
spyOn(context, 'popover').and.callFake((method) => {
expect(method).toEqual('hide');
done();
});
togglePopover.call(context, false);
});
it('removes disable-animation and js-popover-show class', (done) => {
const context = {
hasClass: () => true,
popover: () => {},
toggleClass: () => {},
};
spyOn(context, 'toggleClass').and.callFake((classNames, show) => {
expect(classNames).toEqual('disable-animation js-popover-show');
expect(show).toEqual(false);
done();
});
togglePopover.call(context, false);
});
});
});
describe('mouseleave', () => {
it('calls hide popover if .popover:hover is false', () => {
const fakeJquery = {
length: 0,
};
spyOn($.fn, 'init').and.callFake(selector => (selector === '.popover:hover' ? fakeJquery : $.fn));
spyOn(togglePopover, 'call');
mouseleave();
expect(togglePopover.call).toHaveBeenCalledWith(jasmine.any(Object), false);
});
it('does not call hide popover if .popover:hover is true', () => {
const fakeJquery = {
length: 1,
};
spyOn($.fn, 'init').and.callFake(selector => (selector === '.popover:hover' ? fakeJquery : $.fn));
spyOn(togglePopover, 'call');
mouseleave();
expect(togglePopover.call).not.toHaveBeenCalledWith(false);
});
});
describe('mouseenter', () => {
const context = {};
it('shows popover', () => {
spyOn(togglePopover, 'call').and.returnValue(false);
mouseenter.call(context);
expect(togglePopover.call).toHaveBeenCalledWith(jasmine.any(Object), true);
});
it('registers mouseleave event if popover is showed', (done) => {
spyOn(togglePopover, 'call').and.returnValue(true);
spyOn($.fn, 'on').and.callFake((eventName) => {
expect(eventName).toEqual('mouseleave');
done();
});
mouseenter.call(context);
});
it('does not register mouseleave event if popover is not showed', () => {
spyOn(togglePopover, 'call').and.returnValue(false);
const spy = spyOn($.fn, 'on').and.callFake(() => {});
mouseenter.call(context);
expect(spy).not.toHaveBeenCalled();
});
});
});
import findAndFollowLink from '~/shortcuts_dashboard_navigation';
import * as urlUtility from '~/lib/utils/url_utility';
describe('findAndFollowLink', () => {
it('visits a link when the selector exists', () => {
const href = '/some/path';
const locationSpy = spyOn(urlUtility, 'visitUrl');
setFixtures(`<a class="my-shortcut" href="${href}">link</a>`);
findAndFollowLink('.my-shortcut');
expect(locationSpy).toHaveBeenCalledWith(href);
});
it('does not throw an exception when the selector does not exist', () => {
const locationSpy = spyOn(urlUtility, 'visitUrl');
// this should not throw an exception
findAndFollowLink('.this-selector-does-not-exist');
expect(locationSpy).not.toHaveBeenCalled();
});
});
......@@ -51,8 +51,7 @@ const createComponent = () => {
const messages = {
loadingMetrics: 'Loading deployment statistics',
hasMetrics:
'<a href="/root/acets-review-apps/environments/15/metrics"> Memory </a> usage is <b> unchanged </b> at 0MB',
hasMetrics: 'Memory usage is unchanged at 0MB',
loadFailed: 'Failed to load deployment statistics',
metricsUnavailable: 'Deployment statistics are not available currently',
};
......
......@@ -14,8 +14,8 @@ describe('Skeleton loading container', () => {
vm.$destroy();
});
it('renders 6 skeleton lines by default', () => {
expect(vm.$el.querySelector('.skeleton-line-6')).not.toBeNull();
it('renders 3 skeleton lines by default', () => {
expect(vm.$el.querySelector('.skeleton-line-3')).not.toBeNull();
});
it('renders in full mode by default', () => {
......
......@@ -3,6 +3,48 @@ require 'spec_helper'
describe API::Helpers do
subject { Class.new.include(described_class).new }
describe '#find_project' do
let(:project) { create(:project) }
shared_examples 'project finder' do
context 'when project exists' do
it 'returns requested project' do
expect(subject.find_project(existing_id)).to eq(project)
end
it 'returns nil' do
expect(subject.find_project(non_existing_id)).to be_nil
end
end
end
context 'when ID is used as an argument' do
let(:existing_id) { project.id }
let(:non_existing_id) { (Project.maximum(:id) || 0) + 1 }
it_behaves_like 'project finder'
end
context 'when PATH is used as an argument' do
let(:existing_id) { project.full_path }
let(:non_existing_id) { 'something/else' }
it_behaves_like 'project finder'
context 'with an invalid PATH' do
let(:non_existing_id) { 'undefined' } # path without slash
it_behaves_like 'project finder'
it 'does not hit the database' do
expect(Project).not_to receive(:find_by_full_path)
subject.find_project(non_existing_id)
end
end
end
end
describe '#find_namespace' do
let(:namespace) { create(:namespace) }
......
......@@ -233,4 +233,20 @@ describe Banzai::Filter::CommitRangeReferenceFilter do
expect(reference_filter(act).to_html).to eq exp
end
end
context 'group context' do
let(:context) { { project: nil, group: create(:group) } }
it 'ignores internal references' do
exp = act = "See #{range.to_reference}"
expect(reference_filter(act, context).to_html).to eq exp
end
it 'links to a full-path reference' do
reference = "#{project.full_path}@#{commit1.short_id}...#{commit2.short_id}"
expect(reference_filter("See #{reference}", context).css('a').first.text).to eql(reference)
end
end
end
......@@ -238,4 +238,20 @@ describe Banzai::Filter::CommitReferenceFilter do
expect(doc.text).to eq("See (#{commit.reference_link_text(project)} (builds).patch)")
end
end
context 'group context' do
let(:context) { { project: nil, group: create(:group) } }
it 'ignores internal references' do
exp = act = "See #{commit.id}"
expect(reference_filter(act, context).to_html).to eq exp
end
it 'links to a valid reference' do
act = "See #{project.full_path}@#{commit.id}"
expect(reference_filter(act, context).css('a').first.text).to eql("#{project.full_path}@#{commit.short_id}")
end
end
end
......@@ -343,14 +343,22 @@ describe Banzai::Filter::MilestoneReferenceFilter do
end
context 'group context' do
let(:context) { { project: nil, group: create(:group) } }
let(:milestone) { create(:milestone, project: project) }
it 'links to a valid reference' do
milestone = create(:milestone, project: project)
reference = "#{project.full_path}%#{milestone.iid}"
result = reference_filter("See #{reference}", { project: nil, group: create(:group) } )
result = reference_filter("See #{reference}", context)
expect(result.css('a').first.attr('href')).to eq(urls.milestone_url(milestone))
end
it 'ignores internal references' do
exp = act = "See %#{milestone.iid}"
expect(reference_filter(act, context).to_html).to eq exp
end
end
context 'when milestone is open' do
......
......@@ -210,5 +210,11 @@ describe Banzai::Filter::SnippetReferenceFilter do
expect(result.css('a').first.attr('href')).to eq(urls.project_snippet_url(project, snippet))
end
it 'ignores internal references' do
exp = act = "See $#{snippet.id}"
expect(reference_filter(act, project: nil, group: create(:group)).to_html).to eq exp
end
end
end
......@@ -107,12 +107,9 @@ describe Banzai::ReferenceParser::CommitRangeParser do
describe '#find_object' do
let(:range) { double(:range) }
before do
expect(CommitRange).to receive(:new).and_return(range)
end
context 'when the range has valid commits' do
it 'returns the commit range' do
expect(CommitRange).to receive(:new).and_return(range)
expect(range).to receive(:valid_commits?).and_return(true)
expect(subject.find_object(project, '123..456')).to eq(range)
......@@ -121,10 +118,19 @@ describe Banzai::ReferenceParser::CommitRangeParser do
context 'when the range does not have any valid commits' do
it 'returns nil' do
expect(CommitRange).to receive(:new).and_return(range)
expect(range).to receive(:valid_commits?).and_return(false)
expect(subject.find_object(project, '123..456')).to be_nil
end
end
context 'group context' do
it 'returns nil' do
group = create(:group)
expect(subject.find_object(group, '123..456')).to be_nil
end
end
end
end
......@@ -3,7 +3,7 @@ require 'spec_helper'
describe Gitlab::Cache::Ci::ProjectPipelineStatus, :clean_gitlab_redis_cache do
let!(:project) { create(:project, :repository) }
let(:pipeline_status) { described_class.new(project) }
let(:cache_key) { "projects/#{project.id}/pipeline_status" }
let(:cache_key) { described_class.cache_key_for_project(project) }
describe '.load_for_project' do
it "loads the status" do
......
......@@ -14,6 +14,12 @@ describe Gitlab do
expect(described_class.com?).to eq true
end
it 'is true when on other gitlab subdomain' do
stub_config_setting(url: 'https://example.gitlab.com')
expect(described_class.com?).to eq true
end
it 'is false when not on GitLab.com' do
stub_config_setting(url: 'http://example.com')
......
# -*- coding: utf-8 -*-
require 'spec_helper'
shared_examples 'languages and percentages JSON response' do
let(:expected_languages) { project.repository.languages.map { |language| language.values_at(:label, :value)}.to_h }
it 'returns expected language values' do
get api("/projects/#{project.id}/languages", user)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to eq(expected_languages)
expect(json_response.count).to be > 1
end
end
describe API::Projects do
let(:user) { create(:user) }
let(:user2) { create(:user) }
......@@ -1694,6 +1706,42 @@ describe API::Projects do
end
end
describe 'GET /projects/:id/languages' do
context 'with an authorized user' do
it_behaves_like 'languages and percentages JSON response' do
let(:project) { project3 }
end
it 'returns not_found(404) for not existing project' do
get api("/projects/9999999999/languages", user)
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'with not authorized user' do
it 'returns not_found for existing but unauthorized project' do
get api("/projects/#{project3.id}/languages", user3)
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'without user' do
let(:project_public) { create(:project, :public, :repository) }
it_behaves_like 'languages and percentages JSON response' do
let(:project) { project_public }
end
it 'returns not_found for existing but unauthorized project' do
get api("/projects/#{project3.id}/languages", nil)
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
describe 'DELETE /projects/:id' do
context 'when authenticated as user' do
it 'removes project' do
......
......@@ -406,7 +406,7 @@ describe API::Runner do
expect(json_response['image']).to eq({ 'name' => 'ruby:2.1', 'entrypoint' => '/bin/sh' })
expect(json_response['services']).to eq([{ 'name' => 'postgres', 'entrypoint' => nil,
'alias' => nil, 'command' => nil },
{ 'name' => 'docker:dind', 'entrypoint' => '/bin/sh',
{ 'name' => 'docker:stable-dind', 'entrypoint' => '/bin/sh',
'alias' => 'docker', 'command' => 'sleep 30' }])
expect(json_response['steps']).to eq(expected_steps)
expect(json_response['artifacts']).to eq(expected_artifacts)
......
......@@ -7,6 +7,16 @@ require 'selenium-webdriver'
# Give CI some extra time
timeout = (ENV['CI'] || ENV['CI_SERVER']) ? 60 : 30
# Define an error class for JS console messages
JSConsoleError = Class.new(StandardError)
# Filter out innocuous JS console messages
JS_CONSOLE_FILTER = Regexp.union([
'"[HMR] Waiting for update signal from WDS..."',
'"[WDS] Hot Module Replacement enabled."',
"Download the Vue Devtools extension"
])
Capybara.register_driver :chrome do |app|
capabilities = Selenium::WebDriver::Remote::Capabilities.chrome(
# This enables access to logs with `page.driver.manage.get_log(:browser)`
......@@ -25,13 +35,7 @@ Capybara.register_driver :chrome do |app|
options.add_argument("no-sandbox")
# Run headless by default unless CHROME_HEADLESS specified
unless ENV['CHROME_HEADLESS'] =~ /^(false|no|0)$/i
options.add_argument("headless")
# Chrome documentation says this flag is needed for now
# https://developers.google.com/web/updates/2017/04/headless-chrome#cli
options.add_argument("disable-gpu")
end
options.add_argument("headless") unless ENV['CHROME_HEADLESS'] =~ /^(false|no|0)$/i
# Disable /dev/shm use in CI. See https://gitlab.com/gitlab-org/gitlab-ee/issues/4252
options.add_argument("disable-dev-shm-usage") if ENV['CI'] || ENV['CI_SERVER']
......@@ -78,6 +82,15 @@ RSpec.configure do |config|
end
config.after(:example, :js) do |example|
# when a test fails, display any messages in the browser's console
if example.exception
console = page.driver.browser.manage.logs.get(:browser)&.reject { |log| log.message =~ JS_CONSOLE_FILTER }
if console.present?
message = "Unexpected browser console output:\n" + console.map(&:message).join("\n")
raise JSConsoleError, message
end
end
# prevent localStorage from introducing side effects based on test order
unless ['', 'about:blank', 'data:,'].include? Capybara.current_session.driver.browser.current_url
execute_script("localStorage.clear();")
......
require 'rake_helper'
describe 'clearing redis cache' do
before do
Rake.application.rake_require 'tasks/cache'
end
describe 'clearing pipeline status cache' do
let(:pipeline_status) { create(:ci_pipeline).project.pipeline_status }
before do
allow(pipeline_status).to receive(:loaded).and_return(nil)
end
it 'clears pipeline status cache' do
expect { run_rake_task('cache:clear:redis') }.to change { pipeline_status.has_cache? }
end
end
end
......@@ -18,4 +18,10 @@ describe 'admin/dashboard/index.html.haml' do
expect(rendered).to have_content 'GitLab Workhorse'
expect(rendered).to have_content Gitlab::Workhorse.version
end
it "includes revision of GitLab" do
render
expect(rendered).to have_content "#{Gitlab::VERSION} (#{Gitlab::REVISION})"
end
end
require 'spec_helper'
describe 'shared/milestones/_top.html.haml' do
set(:group) { create(:group) }
let(:project) { create(:project, group: group) }
let(:milestone) { create(:milestone, project: project) }
before do
allow(milestone).to receive(:milestones) { [] }
end
it 'renders a deprecation message for a legacy milestone' do
allow(milestone).to receive(:legacy_group_milestone?) { true }
render 'shared/milestones/top', milestone: milestone
expect(rendered).to have_css('.milestone-deprecation-message')
end
it 'renders a deprecation message for a dashboard milestone' do
allow(milestone).to receive(:dashboard_milestone?) { true }
render 'shared/milestones/top', milestone: milestone
expect(rendered).to have_css('.milestone-deprecation-message')
end
it 'does not render a deprecation message for a non-legacy and non-dashboard milestone' do
assign :group, group
render 'shared/milestones/top', milestone: milestone
expect(rendered).not_to have_css('.milestone-deprecation-message')
end
end
......@@ -7,7 +7,7 @@
image: "chef/chefdk"
services:
- docker:dind
- docker:stable-dind
variables:
DOCKER_HOST: "tcp://docker:2375"
......
......@@ -2,7 +2,7 @@
image: docker:latest
services:
- docker:dind
- docker:stable-dind
before_script:
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
......
......@@ -54,9 +54,9 @@
lodash "^4.2.0"
to-fast-properties "^2.0.0"
"@gitlab-org/gitlab-svgs@^1.17.0":
version "1.17.0"
resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-svgs/-/gitlab-svgs-1.17.0.tgz#d0c74d9e44c127ccfad16941f352088b86f86c89"
"@gitlab-org/gitlab-svgs@^1.18.0":
version "1.18.0"
resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-svgs/-/gitlab-svgs-1.18.0.tgz#7829f0e6de0647dace54c1fcd597ee3424afb233"
"@types/jquery@^2.0.40":
version "2.0.48"
......
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