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: ...@@ -721,7 +721,7 @@ codequality:
tags: [] tags: []
before_script: [] before_script: []
services: services:
- docker:dind - docker:stable-dind
variables: variables:
SETUP_DB: "false" SETUP_DB: "false"
DOCKER_DRIVER: overlay2 DOCKER_DRIVER: overlay2
......
...@@ -82,16 +82,9 @@ gem 'net-ldap' ...@@ -82,16 +82,9 @@ gem 'net-ldap'
# Git Wiki # Git Wiki
# Required manually in config/initializers/gollum.rb to control load order # Required manually in config/initializers/gollum.rb to control load order
# Before updating this gem, check if gem 'gitlab-gollum-lib', '~> 4.2'
# 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 gem 'gitlab-gollum-rugged_adapter', '~> 0.4.4', require: false
# 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
# Language detection # Language detection
gem 'github-linguist', '~> 5.3.3', require: 'linguist' gem 'github-linguist', '~> 5.3.3', require: 'linguist'
......
...@@ -298,11 +298,22 @@ GEM ...@@ -298,11 +298,22 @@ GEM
escape_utils (~> 1.1.0) escape_utils (~> 1.1.0)
mime-types (>= 1.19) mime-types (>= 1.19)
rugged (>= 0.25.1) rugged (>= 0.25.1)
github-markup (1.6.1) github-markup (1.7.0)
gitlab-flowdock-git-hook (1.0.1) gitlab-flowdock-git-hook (1.0.1)
flowdock (~> 0.7) flowdock (~> 0.7)
gitlab-grit (>= 2.4.1) gitlab-grit (>= 2.4.1)
multi_json 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) gitlab-grit (2.8.2)
charlock_holmes (~> 0.6) charlock_holmes (~> 0.6)
diff-lcs (~> 1.1) diff-lcs (~> 1.1)
...@@ -325,17 +336,6 @@ GEM ...@@ -325,17 +336,6 @@ GEM
activesupport (>= 4.2, < 5.2) activesupport (>= 4.2, < 5.2)
gollum-grit_adapter (1.0.1) gollum-grit_adapter (1.0.1)
gitlab-grit (~> 2.7, >= 2.7.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) gon (6.1.0)
actionpack (>= 3.0) actionpack (>= 3.0)
json json
...@@ -590,7 +590,7 @@ GEM ...@@ -590,7 +590,7 @@ GEM
orm_adapter (0.5.0) orm_adapter (0.5.0)
os (0.9.6) os (0.9.6)
parallel (1.12.1) parallel (1.12.1)
parser (2.5.0.5) parser (2.5.1.0)
ast (~> 2.4.0) ast (~> 2.4.0)
parslet (1.5.0) parslet (1.5.0)
blankslate (~> 2.0) blankslate (~> 2.0)
...@@ -910,7 +910,7 @@ GEM ...@@ -910,7 +910,7 @@ GEM
state_machines-activerecord (0.5.1) state_machines-activerecord (0.5.1)
activerecord (>= 4.1, < 6.0) activerecord (>= 4.1, < 6.0)
state_machines-activemodel (>= 0.5.0) state_machines-activemodel (>= 0.5.0)
stringex (2.7.1) stringex (2.8.4)
sys-filesystem (1.1.6) sys-filesystem (1.1.6)
ffi ffi
sysexits (1.2.0) sysexits (1.2.0)
...@@ -1067,12 +1067,12 @@ DEPENDENCIES ...@@ -1067,12 +1067,12 @@ DEPENDENCIES
gitaly-proto (~> 0.94.0) gitaly-proto (~> 0.94.0)
github-linguist (~> 5.3.3) github-linguist (~> 5.3.3)
gitlab-flowdock-git-hook (~> 1.0.1) 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-markup (~> 1.6.2)
gitlab-styles (~> 2.3) gitlab-styles (~> 2.3)
gitlab_omniauth-ldap (~> 2.0.4) gitlab_omniauth-ldap (~> 2.0.4)
goldiloader (~> 2.0) goldiloader (~> 2.0)
gollum-lib (~> 4.2)
gollum-rugged_adapter (~> 0.4.4)
gon (~> 6.1.0) gon (~> 6.1.0)
google-api-client (~> 0.19.8) google-api-client (~> 0.19.8)
google-protobuf (= 3.5.1) google-protobuf (= 3.5.1)
......
import $ from 'jquery'; import $ from 'jquery';
import _ from 'underscore';
import { import {
getSelector, getSelector,
togglePopover,
inserted, inserted,
mouseenter,
mouseleave,
} from './feature_highlight_helper'; } from './feature_highlight_helper';
import {
togglePopover,
mouseenter,
debouncedMouseleave,
} from '../shared/popover';
export function setupFeatureHighlightPopover(id, debounceTimeout = 300) { export function setupFeatureHighlightPopover(id, debounceTimeout = 300) {
const $selector = $(getSelector(id)); const $selector = $(getSelector(id));
const $parent = $selector.parent(); const $parent = $selector.parent();
const $popoverContent = $parent.siblings('.feature-highlight-popover-content'); const $popoverContent = $parent.siblings('.feature-highlight-popover-content');
const hideOnScroll = togglePopover.bind($selector, false); const hideOnScroll = togglePopover.bind($selector, false);
const debouncedMouseleave = _.debounce(mouseleave, debounceTimeout);
$selector $selector
// Setup popover // Setup popover
...@@ -29,13 +29,10 @@ export function setupFeatureHighlightPopover(id, debounceTimeout = 300) { ...@@ -29,13 +29,10 @@ export function setupFeatureHighlightPopover(id, debounceTimeout = 300) {
`, `,
}) })
.on('mouseenter', mouseenter) .on('mouseenter', mouseenter)
.on('mouseleave', debouncedMouseleave) .on('mouseleave', debouncedMouseleave(debounceTimeout))
.on('inserted.bs.popover', inserted) .on('inserted.bs.popover', inserted)
.on('show.bs.popover', () => { .on('show.bs.popover', () => {
window.addEventListener('scroll', hideOnScroll); window.addEventListener('scroll', hideOnScroll, { once: true });
})
.on('hide.bs.popover', () => {
window.removeEventListener('scroll', hideOnScroll);
}) })
// Display feature highlight // Display feature highlight
.removeAttr('disabled'); .removeAttr('disabled');
......
...@@ -3,20 +3,10 @@ import axios from '../lib/utils/axios_utils'; ...@@ -3,20 +3,10 @@ import axios from '../lib/utils/axios_utils';
import { __ } from '../locale'; import { __ } from '../locale';
import Flash from '../flash'; import Flash from '../flash';
import LazyLoader from '../lazy_loader'; import LazyLoader from '../lazy_loader';
import { togglePopover } from '../shared/popover';
export const getSelector = highlightId => `.js-feature-highlight[data-highlight=${highlightId}]`; 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) { export function dismiss(highlightId) {
axios.post(this.attr('data-dismiss-endpoint'), { axios.post(this.attr('data-dismiss-endpoint'), {
feature_name: highlightId, feature_name: highlightId,
...@@ -27,23 +17,6 @@ export function dismiss(highlightId) { ...@@ -27,23 +17,6 @@ export function dismiss(highlightId) {
this.hide(); 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() { export function inserted() {
const popoverId = this.getAttribute('aria-describedby'); const popoverId = this.getAttribute('aria-describedby');
const highlightId = this.dataset.highlight; const highlightId = this.dataset.highlight;
......
...@@ -37,9 +37,9 @@ export const setLastCommitMessage = ({ rootState, commit }, data) => { ...@@ -37,9 +37,9 @@ export const setLastCommitMessage = ({ rootState, commit }, data) => {
const commitMsg = sprintf( const commitMsg = sprintf(
__('Your changes have been committed. Commit %{commitId} %{commitStats}'), __('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 data.short_id
}" class="commit-sha">${data.short_id}</a>`, }</a>`,
commitStats, commitStats,
}, },
false, false,
...@@ -54,9 +54,7 @@ export const checkCommitStatus = ({ rootState }) => ...@@ -54,9 +54,7 @@ export const checkCommitStatus = ({ rootState }) =>
.then(({ data }) => { .then(({ data }) => {
const { id } = data.commit; const { id } = data.commit;
const selectedBranch = const selectedBranch =
rootState.projects[rootState.currentProjectId].branches[ rootState.projects[rootState.currentProjectId].branches[rootState.currentBranchId];
rootState.currentBranchId
];
if (selectedBranch.workingReference !== id) { if (selectedBranch.workingReference !== id) {
return true; return true;
...@@ -135,32 +133,15 @@ export const updateFilesAfterCommit = ( ...@@ -135,32 +133,15 @@ export const updateFilesAfterCommit = (
if (state.commitAction === consts.COMMIT_TO_NEW_BRANCH) { if (state.commitAction === consts.COMMIT_TO_NEW_BRANCH) {
router.push( router.push(
`/project/${rootState.currentProjectId}/blob/${branch}/${ `/project/${rootState.currentProjectId}/blob/${branch}/${rootGetters.activeFile.path}`,
rootGetters.activeFile.path
}`,
); );
} }
dispatch('updateCommitAction', consts.COMMIT_TO_CURRENT_BRANCH);
}; };
export const commitChanges = ({ export const commitChanges = ({ commit, state, getters, dispatch, rootState }) => {
commit,
state,
getters,
dispatch,
rootState,
}) => {
const newBranch = state.commitAction !== consts.COMMIT_TO_CURRENT_BRANCH; const newBranch = state.commitAction !== consts.COMMIT_TO_CURRENT_BRANCH;
const payload = createCommitPayload( const payload = createCommitPayload(getters.branchName, newBranch, state, rootState);
getters.branchName, const getCommitStatus = newBranch ? Promise.resolve(false) : dispatch('checkCommitStatus');
newBranch,
state,
rootState,
);
const getCommitStatus = newBranch
? Promise.resolve(false)
: dispatch('checkCommitStatus');
commit(types.UPDATE_LOADING, true); commit(types.UPDATE_LOADING, true);
...@@ -182,28 +163,29 @@ export const commitChanges = ({ ...@@ -182,28 +163,29 @@ export const commitChanges = ({
if (!data.short_id) { if (!data.short_id) {
flash(data.message, 'alert', document, null, false, true); flash(data.message, 'alert', document, null, false, true);
return; return null;
} }
dispatch('setLastCommitMessage', data); dispatch('setLastCommitMessage', data);
dispatch('updateCommitMessage', ''); dispatch('updateCommitMessage', '');
return dispatch('updateFilesAfterCommit', {
if (state.commitAction === consts.COMMIT_TO_NEW_BRANCH_MR) { data,
dispatch( branch: getters.branchName,
'redirectToUrl', })
createNewMergeRequestUrl( .then(() => {
rootState.projects[rootState.currentProjectId].web_url, if (state.commitAction === consts.COMMIT_TO_NEW_BRANCH_MR) {
getters.branchName, dispatch(
rootState.currentBranchId, 'redirectToUrl',
), createNewMergeRequestUrl(
{ root: true }, rootState.projects[rootState.currentProjectId].web_url,
); getters.branchName,
} else { rootState.currentBranchId,
dispatch('updateFilesAfterCommit', { ),
data, { root: true },
branch: getters.branchName, );
}); }
} })
.then(() => dispatch('updateCommitAction', consts.COMMIT_TO_CURRENT_BRANCH));
}) })
.catch(err => { .catch(err => {
let errMsg = __('Error committing changes. Please try again.'); let errMsg = __('Error committing changes. Please try again.');
......
...@@ -7,11 +7,7 @@ import flash from './flash'; ...@@ -7,11 +7,7 @@ import flash from './flash';
import BlobForkSuggestion from './blob/blob_fork_suggestion'; import BlobForkSuggestion from './blob/blob_fork_suggestion';
import initChangesDropdown from './init_changes_dropdown'; import initChangesDropdown from './init_changes_dropdown';
import bp from './breakpoints'; import bp from './breakpoints';
import { import { parseUrlPathname, handleLocationHash, isMetaClick } from './lib/utils/common_utils';
parseUrlPathname,
handleLocationHash,
isMetaClick,
} from './lib/utils/common_utils';
import { getLocationHash } from './lib/utils/url_utility'; import { getLocationHash } from './lib/utils/url_utility';
import initDiscussionTab from './image_diff/init_discussion_tab'; import initDiscussionTab from './image_diff/init_discussion_tab';
import Diff from './diff'; import Diff from './diff';
...@@ -69,11 +65,10 @@ import Notes from './notes'; ...@@ -69,11 +65,10 @@ import Notes from './notes';
let location = window.location; let location = window.location;
export default class MergeRequestTabs { export default class MergeRequestTabs {
constructor({ action, setUrl, stubLocation } = {}) { constructor({ action, setUrl, stubLocation } = {}) {
const mergeRequestTabs = document.querySelector('.js-tabs-affix'); const mergeRequestTabs = document.querySelector('.js-tabs-affix');
const navbar = document.querySelector('.navbar-gitlab'); const navbar = document.querySelector('.navbar-gitlab');
const peek = document.getElementById('peek'); const peek = document.getElementById('js-peek');
const paddingTop = 16; const paddingTop = 16;
this.diffsLoaded = false; this.diffsLoaded = false;
...@@ -109,8 +104,7 @@ export default class MergeRequestTabs { ...@@ -109,8 +104,7 @@ export default class MergeRequestTabs {
.on('shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', this.tabShown) .on('shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', this.tabShown)
.on('click', '.js-show-tab', this.showTab); .on('click', '.js-show-tab', this.showTab);
$('.merge-request-tabs a[data-toggle="tab"]') $('.merge-request-tabs a[data-toggle="tab"]').on('click', this.clickTab);
.on('click', this.clickTab);
} }
// Used in tests // Used in tests
...@@ -119,8 +113,7 @@ export default class MergeRequestTabs { ...@@ -119,8 +113,7 @@ export default class MergeRequestTabs {
.off('shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', this.tabShown) .off('shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', this.tabShown)
.off('click', '.js-show-tab', this.showTab); .off('click', '.js-show-tab', this.showTab);
$('.merge-request-tabs a[data-toggle="tab"]') $('.merge-request-tabs a[data-toggle="tab"]').off('click', this.clickTab);
.off('click', this.clickTab);
} }
destroyPipelinesView() { destroyPipelinesView() {
...@@ -183,10 +176,7 @@ export default class MergeRequestTabs { ...@@ -183,10 +176,7 @@ export default class MergeRequestTabs {
scrollToElement(container) { scrollToElement(container) {
if (location.hash) { if (location.hash) {
const offset = 0 - ( const offset = 0 - ($('.navbar-gitlab').outerHeight() + $('.js-tabs-affix').outerHeight());
$('.navbar-gitlab').outerHeight() +
$('.js-tabs-affix').outerHeight()
);
const $el = $(`${container} ${location.hash}:not(.match)`); const $el = $(`${container} ${location.hash}:not(.match)`);
if ($el.length) { if ($el.length) {
$.scrollTo($el[0], { offset }); $.scrollTo($el[0], { offset });
...@@ -240,9 +230,13 @@ export default class MergeRequestTabs { ...@@ -240,9 +230,13 @@ export default class MergeRequestTabs {
// Turbolinks' history. // Turbolinks' history.
// //
// See https://github.com/rails/turbolinks/issues/363 // See https://github.com/rails/turbolinks/issues/363
window.history.replaceState({ window.history.replaceState(
url: newState, {
}, document.title, newState); url: newState,
},
document.title,
newState,
);
return newState; return newState;
} }
...@@ -258,7 +252,8 @@ export default class MergeRequestTabs { ...@@ -258,7 +252,8 @@ export default class MergeRequestTabs {
this.toggleLoading(true); this.toggleLoading(true);
axios.get(`${source}.json`) axios
.get(`${source}.json`)
.then(({ data }) => { .then(({ data }) => {
document.querySelector('div#commits').innerHTML = data.html; document.querySelector('div#commits').innerHTML = data.html;
localTimeAgo($('.js-timeago', 'div#commits')); localTimeAgo($('.js-timeago', 'div#commits'));
...@@ -303,7 +298,8 @@ export default class MergeRequestTabs { ...@@ -303,7 +298,8 @@ export default class MergeRequestTabs {
this.toggleLoading(true); this.toggleLoading(true);
axios.get(`${urlPathname}.json${location.search}`) axios
.get(`${urlPathname}.json${location.search}`)
.then(({ data }) => { .then(({ data }) => {
const $container = $('#diffs'); const $container = $('#diffs');
$container.html(data.html); $container.html(data.html);
...@@ -332,8 +328,7 @@ export default class MergeRequestTabs { ...@@ -332,8 +328,7 @@ export default class MergeRequestTabs {
cancelButtons: $(el).find('.js-cancel-fork-suggestion-button'), cancelButtons: $(el).find('.js-cancel-fork-suggestion-button'),
suggestionSections: $(el).find('.js-file-fork-suggestion-section'), suggestionSections: $(el).find('.js-file-fork-suggestion-section'),
actionTextPieces: $(el).find('.js-file-fork-suggestion-section-action'), actionTextPieces: $(el).find('.js-file-fork-suggestion-section-action'),
}) }).init();
.init();
}); });
// Scroll any linked note into view // Scroll any linked note into view
...@@ -388,8 +383,7 @@ export default class MergeRequestTabs { ...@@ -388,8 +383,7 @@ export default class MergeRequestTabs {
resetViewContainer() { resetViewContainer() {
if (this.fixedLayoutPref !== null) { if (this.fixedLayoutPref !== null) {
$('.content-wrapper .container-fluid') $('.content-wrapper .container-fluid').toggleClass('container-limited', this.fixedLayoutPref);
.toggleClass('container-limited', this.fixedLayoutPref);
} }
} }
...@@ -438,12 +432,11 @@ export default class MergeRequestTabs { ...@@ -438,12 +432,11 @@ export default class MergeRequestTabs {
const $diffTabs = $('#diff-notes-app'); const $diffTabs = $('#diff-notes-app');
$tabs.off('affix.bs.affix affix-top.bs.affix') $tabs
.off('affix.bs.affix affix-top.bs.affix')
.affix({ .affix({
offset: { offset: {
top: () => ( top: () => $diffTabs.offset().top - $tabs.height() - $fixedNav.height(),
$diffTabs.offset().top - $tabs.height() - $fixedNav.height()
),
}, },
}) })
.on('affix.bs.affix', () => $diffTabs.css({ marginTop: $tabs.height() })) .on('affix.bs.affix', () => $diffTabs.css({ marginTop: $tabs.height() }))
......
import $ from 'jquery'; import $ from 'jquery';
import axios from './lib/utils/axios_utils'; import axios from './lib/utils/axios_utils';
import flash from './flash'; import flash from './flash';
import { mouseenter, debouncedMouseleave, togglePopover } from './shared/popover';
export default class Milestone { export default class Milestone {
constructor() { constructor() {
...@@ -43,4 +44,25 @@ export default class Milestone { ...@@ -43,4 +44,25 @@ export default class Milestone {
.catch(() => flash('Error loading milestone tab')); .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', () => { ...@@ -6,4 +6,6 @@ document.addEventListener('DOMContentLoaded', () => {
new Milestone(); // eslint-disable-line no-new new Milestone(); // eslint-disable-line no-new
new Sidebar(); // eslint-disable-line no-new new Sidebar(); // eslint-disable-line no-new
new MountMilestoneSidebar(); // eslint-disable-line no-new new MountMilestoneSidebar(); // eslint-disable-line no-new
Milestone.initDeprecationMessage();
}); });
import initMilestonesShow from '~/pages/milestones/shared/init_milestones_show'; 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 initNotes from '~/init_notes';
import ZenMode from '~/zen_mode'; import ZenMode from '~/zen_mode';
import LineHighlighter from '../../../../line_highlighter'; import LineHighlighter from '~/line_highlighter';
import BlobViewer from '../../../../blob/viewer'; import BlobViewer from '~/blob/viewer';
import snippetEmbed from '~/snippet/snippet_embed';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
new LineHighlighter(); // eslint-disable-line no-new new LineHighlighter(); // eslint-disable-line no-new
new BlobViewer(); // eslint-disable-line no-new new BlobViewer(); // eslint-disable-line no-new
initNotes(); initNotes();
new ZenMode(); // eslint-disable-line no-new new ZenMode(); // eslint-disable-line no-new
snippetEmbed();
}); });
import LineHighlighter from '../../../line_highlighter'; import LineHighlighter from '~/line_highlighter';
import BlobViewer from '../../../blob/viewer'; import BlobViewer from '~/blob/viewer';
import ZenMode from '../../../zen_mode'; import ZenMode from '~/zen_mode';
import initNotes from '../../../init_notes'; import initNotes from '~/init_notes';
import snippetEmbed from '~/snippet/snippet_embed';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
new LineHighlighter(); // eslint-disable-line no-new new LineHighlighter(); // eslint-disable-line no-new
new BlobViewer(); // eslint-disable-line no-new new BlobViewer(); // eslint-disable-line no-new
initNotes(); initNotes();
new ZenMode(); // eslint-disable-line no-new new ZenMode(); // eslint-disable-line no-new
snippetEmbed();
}); });
<script> <script>
import $ from 'jquery';
/** /**
* Renders each stage of the pipeline mini graph. * Renders each stage of the pipeline mini graph.
...@@ -13,8 +12,11 @@ ...@@ -13,8 +12,11 @@
* 3. Merge request widget * 3. Merge request widget
* 4. Commit widget * 4. Commit widget
*/ */
import axios from '../../lib/utils/axios_utils';
import $ from 'jquery';
import Flash from '../../flash'; 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 Icon from '../../vue_shared/components/icon.vue';
import LoadingIcon from '../../vue_shared/components/loading_icon.vue'; import LoadingIcon from '../../vue_shared/components/loading_icon.vue';
import tooltip from '../../vue_shared/directives/tooltip'; import tooltip from '../../vue_shared/directives/tooltip';
...@@ -82,6 +84,7 @@ ...@@ -82,6 +84,7 @@
methods: { methods: {
onClickStage() { onClickStage() {
if (!this.isDropdownOpen()) { if (!this.isDropdownOpen()) {
eventHub.$emit('clickedDropdown');
this.isLoading = true; this.isLoading = true;
this.fetchJobs(); 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'; ...@@ -7,6 +7,7 @@ import SvgBlankState from '../components/blank_state.vue';
import LoadingIcon from '../../vue_shared/components/loading_icon.vue'; import LoadingIcon from '../../vue_shared/components/loading_icon.vue';
import PipelinesTableComponent from '../components/pipelines_table.vue'; import PipelinesTableComponent from '../components/pipelines_table.vue';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
import { CANCEL_REQUEST } from '../constants';
export default { export default {
components: { components: {
...@@ -52,34 +53,58 @@ export default { ...@@ -52,34 +53,58 @@ export default {
}); });
eventHub.$on('postAction', this.postAction); eventHub.$on('postAction', this.postAction);
eventHub.$on('clickedDropdown', this.updateTable);
}, },
beforeDestroy() { beforeDestroy() {
eventHub.$off('postAction', this.postAction); eventHub.$off('postAction', this.postAction);
eventHub.$off('clickedDropdown', this.updateTable);
}, },
destroyed() { destroyed() {
this.poll.stop(); this.poll.stop();
}, },
methods: { 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() { fetchPipelines() {
if (!this.isMakingRequest) { if (!this.isMakingRequest) {
this.isLoading = true; this.isLoading = true;
this.service.getPipelines(this.requestData) this.getPipelines();
.then(response => this.successCallback(response))
.catch(() => this.errorCallback());
} }
}, },
getPipelines() {
return this.service.getPipelines(this.requestData)
.then(response => this.successCallback(response))
.catch((error) => this.errorCallback(error));
},
setCommonData(pipelines) { setCommonData(pipelines) {
this.store.storePipelines(pipelines); this.store.storePipelines(pipelines);
this.isLoading = false; this.isLoading = false;
this.updateGraphDropdown = true; this.updateGraphDropdown = true;
this.hasMadeRequest = 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() { errorCallback(error) {
this.hasError = true;
this.isLoading = false;
this.updateGraphDropdown = false;
this.hasMadeRequest = true; this.hasMadeRequest = true;
this.isLoading = false;
if (error && error.message && error.message !== CANCEL_REQUEST) {
this.hasError = true;
this.updateGraphDropdown = false;
}
}, },
setIsMakingRequest(isMakingRequest) { setIsMakingRequest(isMakingRequest) {
this.isMakingRequest = isMakingRequest; this.isMakingRequest = isMakingRequest;
......
...@@ -19,8 +19,13 @@ export default class PipelinesService { ...@@ -19,8 +19,13 @@ export default class PipelinesService {
getPipelines(data = {}) { getPipelines(data = {}) {
const { scope, page } = data; const { scope, page } = data;
const CancelToken = axios.CancelToken;
this.cancelationSource = CancelToken.source();
return axios.get(this.endpoint, { return axios.get(this.endpoint, {
params: { scope, page }, 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. * Helper function that finds the href of the fiven selector and updates the location.
* *
* @param {String} selector * @param {String} selector
*/ */
export default (selector) => { export default function findAndFollowLink(selector) {
const link = document.querySelector(selector).getAttribute('href'); const element = document.querySelector(selector);
const link = element && element.getAttribute('href');
if (link) { 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 { ...@@ -146,8 +146,8 @@ export default {
</p> </p>
<p <p
v-if="shouldShowMemoryGraph" v-if="shouldShowMemoryGraph"
class="usage-info js-usage-info"> class="usage-info js-usage-info"
{{ memoryChangeMessage }} v-html="memoryChangeMessage">
</p> </p>
<p <p
v-if="shouldShowLoadFailure" v-if="shouldShowLoadFailure"
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
lines: { lines: {
type: Number, type: Number,
required: false, required: false,
default: 6, default: 3,
}, },
}, },
computed: { computed: {
......
...@@ -37,7 +37,11 @@ ...@@ -37,7 +37,11 @@
/* /*
* Code highlight * 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. * Styles for JS behaviors.
......
...@@ -187,12 +187,9 @@ a { ...@@ -187,12 +187,9 @@ a {
animation: fadeInFull $fade-in-duration 1; animation: fadeInFull $fade-in-duration 1;
} }
.animation-container { .animation-container {
background: $repo-editor-grey;
height: 40px; height: 40px;
overflow: hidden; overflow: hidden;
position: relative;
&.animation-container-small { &.animation-container-small {
height: 12px; height: 12px;
...@@ -205,60 +202,43 @@ a { ...@@ -205,60 +202,43 @@ a {
} }
} }
&::before { [class^="skeleton-line-"] {
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%;
position: relative; position: relative;
} background-color: $theme-gray-100;
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;
height: 10px; height: 10px;
} overflow: hidden;
.skeleton-line-3 { &:not(:last-of-type) {
left: 0; margin-bottom: 4px;
top: 23px; }
}
.skeleton-line-4 { &::after {
left: 0; content: ' ';
top: 38px; 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 { $skeleton-line-widths: (
left: 200px; 156px,
top: 28px; 235px,
height: 10px; 200px,
} );
.skeleton-line-6 { @for $count from 1 through length($skeleton-line-widths) {
top: 14px; .skeleton-line-#{$count} {
left: 230px; width: nth($skeleton-line-widths, $count);
height: 10px;
} }
} }
......
.banner-callout { .banner-callout {
display: flex; display: flex;
position: relative; position: relative;
flex-wrap: wrap; align-items: start;
.banner-close { .banner-close {
position: absolute; position: absolute;
...@@ -16,10 +16,25 @@ ...@@ -16,10 +16,25 @@
} }
.banner-graphic { .banner-graphic {
margin: 20px auto; margin: 0 $gl-padding $gl-padding 0;
} }
&.banner-non-empty-state { &.banner-non-empty-state {
border-bottom: 1px solid $border-color; 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 @@ ...@@ -423,25 +423,43 @@
} }
} }
.btn-link.btn-secondary-hover-link { .btn-link {
color: $gl-text-color-secondary; padding: 0;
background-color: transparent;
color: $blue-600;
font-weight: normal;
border-radius: 0;
border-color: transparent;
&:hover, &:hover,
&:active, &:active,
&:focus { &:focus {
color: $gl-link-color; color: $blue-800;
text-decoration: none; text-decoration: underline;
background-color: transparent;
border-color: transparent;
} }
}
.btn-link.btn-primary-hover-link { &.btn-secondary-hover-link {
color: inherit; color: $gl-text-color-secondary;
&:hover, &:hover,
&:active, &:active,
&:focus { &:focus {
color: $gl-link-color; color: $gl-link-color;
text-decoration: none; text-decoration: none;
}
}
&.btn-primary-hover-link {
color: inherit;
&:hover,
&:active,
&:focus {
color: $gl-link-color;
text-decoration: none;
}
} }
} }
......
...@@ -481,7 +481,8 @@ ...@@ -481,7 +481,8 @@
.dropdown-menu-selectable { .dropdown-menu-selectable {
li { li {
a { a,
button {
padding: 8px 40px; padding: 8px 40px;
position: relative; position: relative;
......
...@@ -29,8 +29,10 @@ ...@@ -29,8 +29,10 @@
} }
.snippet-title { .snippet-title {
font-size: 24px; color: $gl-text-color;
font-size: 2em;
font-weight: $gl-font-weight-bold; font-weight: $gl-font-weight-bold;
min-height: $header-height;
} }
.snippet-edited-ago { .snippet-edited-ago {
...@@ -46,3 +48,26 @@ ...@@ -46,3 +48,26 @@
.snippet-scope-menu .btn-new { .snippet-scope-menu .btn-new {
margin-top: 15px; 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; ...@@ -713,20 +713,6 @@ $color-high-score: $green-400;
$color-average-score: $orange-400; $color-average-score: $orange-400;
$color-low-score: $red-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 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 { .code.white {
// Line numbers @import "white_base";
.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; }
} }
/* 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 @@ ...@@ -194,3 +194,38 @@
.issuable-row { .issuable-row {
background-color: $white-light; 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 @@ ...@@ -429,6 +429,7 @@
.projects-sidebar { .projects-sidebar {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 100%;
.context-header { .context-header {
width: auto; width: auto;
...@@ -438,8 +439,8 @@ ...@@ -438,8 +439,8 @@
.multi-file-commit-panel-inner { .multi-file-commit-panel-inner {
display: flex; display: flex;
flex: 1;
flex-direction: column; flex-direction: column;
height: 100%;
} }
.multi-file-commit-panel-inner-scroll { .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 ...@@ -57,22 +57,17 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
def application_setting_params def application_setting_params
params[:application_setting] ||= {} params[:application_setting] ||= {}
import_sources = params[:application_setting][:import_sources]
if import_sources.nil? if params[:application_setting].key?(:enabled_oauth_sign_in_sources)
params[:application_setting][:import_sources] = [] enabled_oauth_sign_in_sources = params[:application_setting].delete(:enabled_oauth_sign_in_sources)
else enabled_oauth_sign_in_sources&.delete("")
import_sources.map! do |source|
source.to_str
end
end
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) -
params[:application_setting][:disabled_oauth_sign_in_sources] = Array(enabled_oauth_sign_in_sources)
AuthHelper.button_based_providers.map(&:to_s) - end
Array(enabled_oauth_sign_in_sources)
params[:application_setting][:import_sources]&.delete("")
params[:application_setting][:restricted_visibility_levels]&.delete("") params[:application_setting][:restricted_visibility_levels]&.delete("")
params.delete(:domain_blacklist_raw) if params[:domain_blacklist_file] params.delete(:domain_blacklist_raw) if params[:domain_blacklist_file]
......
...@@ -17,6 +17,10 @@ module SnippetsActions ...@@ -17,6 +17,10 @@ module SnippetsActions
end end
# rubocop:enable Gitlab/ModuleWithInstanceVariables # rubocop:enable Gitlab/ModuleWithInstanceVariables
def js_request?
request.format.js?
end
private private
def convert_line_endings(content) def convert_line_endings(content)
......
...@@ -5,6 +5,8 @@ class Projects::SnippetsController < Projects::ApplicationController ...@@ -5,6 +5,8 @@ class Projects::SnippetsController < Projects::ApplicationController
include SnippetsActions include SnippetsActions
include RendersBlob include RendersBlob
skip_before_action :verify_authenticity_token, only: [:show], if: :js_request?
before_action :check_snippets_available! before_action :check_snippets_available!
before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :toggle_award_emoji, :mark_as_spam] before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :toggle_award_emoji, :mark_as_spam]
...@@ -71,6 +73,7 @@ class Projects::SnippetsController < Projects::ApplicationController ...@@ -71,6 +73,7 @@ class Projects::SnippetsController < Projects::ApplicationController
format.json do format.json do
render_blob_json(blob) render_blob_json(blob)
end end
format.js { render 'shared/snippets/show'}
end end
end end
......
...@@ -6,6 +6,8 @@ class SnippetsController < ApplicationController ...@@ -6,6 +6,8 @@ class SnippetsController < ApplicationController
include RendersBlob include RendersBlob
include PreviewMarkdown include PreviewMarkdown
skip_before_action :verify_authenticity_token, only: [:show], if: :js_request?
before_action :snippet, only: [:show, :edit, :destroy, :update, :raw] before_action :snippet, only: [:show, :edit, :destroy, :update, :raw]
# Allow read snippet # Allow read snippet
...@@ -77,6 +79,8 @@ class SnippetsController < ApplicationController ...@@ -77,6 +79,8 @@ class SnippetsController < ApplicationController
format.json do format.json do
render_blob_json(blob) render_blob_json(blob)
end end
format.js { render 'shared/snippets/show' }
end end
end end
......
...@@ -74,10 +74,12 @@ module ApplicationSettingsHelper ...@@ -74,10 +74,12 @@ module ApplicationSettingsHelper
css_class = 'btn' css_class = 'btn'
css_class << ' active' unless disabled css_class << ' active' unless disabled
checkbox_name = 'application_setting[enabled_oauth_sign_in_sources][]' 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 label_tag(checkbox_name, class: css_class) do
check_box_tag(checkbox_name, source, !disabled, 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 end
end end
......
...@@ -43,6 +43,10 @@ module IconsHelper ...@@ -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) content_tag(:svg, content_tag(:use, "", { "xlink:href" => "#{sprite_icon_path}##{icon_name}" } ), class: css_classes.empty? ? nil : css_classes)
end end
def external_snippet_icon(name)
content_tag(:span, "", class: "gl-snippet-icon gl-snippet-icon-#{name}")
end
def audit_icon(names, options = {}) def audit_icon(names, options = {})
case names case names
when "standard" when "standard"
......
...@@ -101,4 +101,39 @@ module SnippetsHelper ...@@ -101,4 +101,39 @@ module SnippetsHelper
# Return snippet with chunk array # Return snippet with chunk array
{ snippet_object: snippet, snippet_chunks: snippet_chunks } { snippet_object: snippet, snippet_chunks: snippet_chunks }
end 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 end
...@@ -23,7 +23,8 @@ ...@@ -23,7 +23,8 @@
must be used to authenticate. must be used to authenticate.
- if omniauth_enabled? && button_based_providers.any? - if omniauth_enabled? && button_based_providers.any?
.form-group.row .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 .col-sm-10
.btn-group{ data: { toggle: 'buttons' } } .btn-group{ data: { toggle: 'buttons' } }
- oauth_providers_checkboxes.each do |source| - oauth_providers_checkboxes.each do |source|
......
...@@ -32,6 +32,7 @@ ...@@ -32,6 +32,7 @@
.form-group.row .form-group.row
= f.label :import_sources, class: 'col-form-label col-sm-2' = f.label :import_sources, class: 'col-form-label col-sm-2'
.col-sm-10 .col-sm-10
= hidden_field_tag 'application_setting[import_sources][]'
- import_sources_checkboxes('import-sources-help').each do |source| - import_sources_checkboxes('import-sources-help').each do |source|
.form-check= source .form-check= source
%span.form-text.text-muted#import-sources-help %span.form-text.text-muted#import-sources-help
......
...@@ -126,6 +126,7 @@ ...@@ -126,6 +126,7 @@
GitLab GitLab
%span.float-right %span.float-right
= Gitlab::VERSION = Gitlab::VERSION
= "(#{Gitlab::REVISION})"
%p %p
GitLab Shell GitLab Shell
%span.float-right %span.float-right
......
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
- else - else
%p %p
Download the Google Authenticator application from App Store or Google Play Store and scan this code. 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 .row.append-bottom-10
.col-md-4 .col-md-4
= raw @qr_code = raw @qr_code
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
- render_error = viewer.render_error - render_error = viewer.render_error
- rich_type = viewer.type == :rich ? viewer.partial_name : nil - rich_type = viewer.type == :rich ? viewer.partial_name : nil
- load_async = local_assigns.fetch(:load_async, viewer.load_async? && render_error.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 - 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) } .blob-viewer{ data: { type: viewer.type, rich_type: rich_type, url: viewer_url }, class: ('hidden' if hidden) }
...@@ -9,6 +10,8 @@ ...@@ -9,6 +10,8 @@
= render 'projects/blob/render_error', viewer: viewer = render 'projects/blob/render_error', viewer: viewer
- elsif load_async - elsif load_async
= render viewer.loading_partial_path, viewer: viewer = render viewer.loading_partial_path, viewer: viewer
- elsif external_embed
= render 'projects/blob/viewers/highlight_embed', blob: viewer.blob
- else - else
- viewer.prepare! - 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 .banner-graphic
= custom_icon('icon_autodevops') = custom_icon('icon_autodevops')
.prepend-top-10.prepend-left-10.append-bottom-10 .banner-body.prepend-left-10.append-bottom-10
%h5= s_('AutoDevOps|Auto DevOps (Beta)') %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= s_('AutoDevOps|It will automatically build, test, and deploy your application based on a predefined CI/CD configuration.')
%p %p
- link = link_to(s_('AutoDevOps|Auto DevOps documentation'), help_page_path('topics/autodevops/index.md'), target: '_blank', rel: 'noopener noreferrer') - 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 } = 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' = 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', %button.btn-transparent.banner-close.close.js-close-callout{ type: 'button',
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
%strong %strong
= link_to group.full_name, group_path(group) = link_to group.full_name, group_path(group)
.cgray .cgray
Joined #{time_ago_with_tooltip(group.created_at)} Given access #{time_ago_with_tooltip(group_link.created_at)}
- if group_link.expires? - if group_link.expires?
· ·
%span{ class: ('text-warning' if group_link.expires_soon?) } %span{ class: ('text-warning' if group_link.expires_soon?) }
......
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
Requested Requested
= time_ago_with_tooltip(member.requested_at) = time_ago_with_tooltip(member.requested_at)
- else - else
Joined #{time_ago_with_tooltip(member.created_at)} Given access #{time_ago_with_tooltip(member.created_at)}
- if member.expires? - 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) } %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) - @breadcrumb_link = dashboard_milestone_path(milestone.safe_title, title: milestone.title)
- group = local_assigns[:group] - group = local_assigns[:group]
- is_dynamic_milestone = milestone.legacy_group_milestone? || milestone.dashboard_milestone?
.detail-page-header .detail-page-header
%a.btn.btn-secondary.btn-grouped.float-right.d-block.d-sm-none.js-sidebar-toggle{ href: "#" } %a.btn.btn-secondary.btn-grouped.float-right.d-block.d-sm-none.js-sidebar-toggle{ href: "#" }
...@@ -31,21 +32,23 @@ ...@@ -31,21 +32,23 @@
- else - else
= link_to 'Reopen Milestone', group_milestone_route(milestone, {state_event: :activate }), method: :put, class: "btn btn-grouped btn-reopen" = 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 .detail-page-description.milestone-detail
%h2.title %h2.title
= markdown_field(milestone, :title) = markdown_field(milestone, :title)
- if @milestone.group_milestone? && @milestone.description.present? - if milestone.group_milestone? && milestone.description.present?
%div %div
.description .description
.wiki .wiki
= markdown_field(@milestone, :description) = markdown_field(milestone, :description)
- if milestone.complete?(current_user) && milestone.active? - if milestone.complete?(current_user) && milestone.active?
.alert.alert-success.prepend-top-default .alert.alert-success.prepend-top-default
- close_msg = group ? 'You may close the milestone now.' : 'Navigate to the project to close the milestone.' - 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} %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-holder
%table.table %table.table
%thead %thead
...@@ -68,7 +71,7 @@ ...@@ -68,7 +71,7 @@
Open Open
%td %td
= ms.expires_at = ms.expires_at
- elsif @milestone.group_milestone? - elsif milestone.group_milestone?
%br %br
View View
= link_to 'Issues', issues_group_path(@group, milestone_title: milestone.title) = 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 @@ ...@@ -19,11 +19,32 @@
%h2.snippet-title.prepend-top-0.append-bottom-0 %h2.snippet-title.prepend-top-0.append-bottom-0
= markdown_field(@snippet, :title) = 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? - if @snippet.description.present?
.description .description
.wiki .wiki
= markdown_field(@snippet, :description) = markdown_field(@snippet, :description)
%textarea.hidden.js-task-list-field %textarea.hidden.js-task-list-field
= @snippet.description = @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 ...@@ -113,6 +113,7 @@ module Gitlab
config.assets.precompile << "performance_bar.css" config.assets.precompile << "performance_bar.css"
config.assets.precompile << "lib/ace.js" config.assets.precompile << "lib/ace.js"
config.assets.precompile << "test.css" config.assets.precompile << "test.css"
config.assets.precompile << "snippets.css"
config.assets.precompile << "locale/**/app.js" config.assets.precompile << "locale/**/app.js"
# Import gitlab-svgs directly from vendored directory # 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 ...@@ -7,139 +7,6 @@ module Gollum
end end
require "gollum-lib" 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 Rails.application.configure do
config.after_initialize do config.after_initialize do
Gollum::Page.per_page = Kaminari.config.default_per_page Gollum::Page.per_page = Kaminari.config.default_per_page
......
...@@ -183,7 +183,7 @@ instant how code changes impact your production environment. ...@@ -183,7 +183,7 @@ instant how code changes impact your production environment.
### Git and GitLab ### Git and GitLab
- [Git](topics/git/index.md): Getting started with Git, branching strategies, Git LFS, advanced use. - [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. - [GitLab Flow](workflow/gitlab_flow.md): explore the best of Git with the GitLab Flow strategy.
## Administrator documentation ## Administrator documentation
......
...@@ -915,6 +915,29 @@ Example response: ...@@ -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 ## Archive a project
Archives the project if the user is either admin or the project owner of this project. This action is Archives the project if the user is either admin or the project owner of this project. This action is
......
...@@ -15,7 +15,7 @@ Parameters: ...@@ -15,7 +15,7 @@ Parameters:
| Attribute | Type | Required | Description | | 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 | | `author_id` | integer | no | The ID of an author |
| `project_id` | integer | no | The ID of a project | | `project_id` | integer | no | The ID of a project |
| `state` | string | no | The state of the todo. Can be either `pending` or `done` | | `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: ...@@ -101,12 +101,12 @@ In order to do that, follow the steps:
--registration-token REGISTRATION_TOKEN \ --registration-token REGISTRATION_TOKEN \
--executor docker \ --executor docker \
--description "My Docker Runner" \ --description "My Docker Runner" \
--docker-image "docker:latest" \ --docker-image "docker:stable" \
--docker-privileged --docker-privileged
``` ```
The above command will register a new Runner to use the special 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 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` want to use [docker-in-docker] mode, you always have to use `privileged = true`
in your Docker containers. in your Docker containers.
...@@ -120,7 +120,7 @@ In order to do that, follow the steps: ...@@ -120,7 +120,7 @@ In order to do that, follow the steps:
executor = "docker" executor = "docker"
[runners.docker] [runners.docker]
tls_verify = false tls_verify = false
image = "docker:latest" image = "docker:stable"
privileged = true privileged = true
disable_cache = false disable_cache = false
volumes = ["/cache"] volumes = ["/cache"]
...@@ -132,7 +132,7 @@ In order to do that, follow the steps: ...@@ -132,7 +132,7 @@ In order to do that, follow the steps:
`docker:dind` service): `docker:dind` service):
```yaml ```yaml
image: docker:latest image: docker:stable
# When using dind, it's wise to use the overlayfs driver for # When using dind, it's wise to use the overlayfs driver for
# improved performance. # improved performance.
...@@ -201,12 +201,12 @@ In order to do that, follow the steps: ...@@ -201,12 +201,12 @@ In order to do that, follow the steps:
--registration-token REGISTRATION_TOKEN \ --registration-token REGISTRATION_TOKEN \
--executor docker \ --executor docker \
--description "My Docker Runner" \ --description "My Docker Runner" \
--docker-image "docker:latest" \ --docker-image "docker:stable" \
--docker-volumes /var/run/docker.sock:/var/run/docker.sock --docker-volumes /var/run/docker.sock:/var/run/docker.sock
``` ```
The above command will register a new Runner to use the special 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 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.** 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. This may have complications and limitations that are unsuitable for your workflow.
...@@ -220,7 +220,7 @@ In order to do that, follow the steps: ...@@ -220,7 +220,7 @@ In order to do that, follow the steps:
executor = "docker" executor = "docker"
[runners.docker] [runners.docker]
tls_verify = false tls_verify = false
image = "docker:latest" image = "docker:stable"
privileged = false privileged = false
disable_cache = false disable_cache = false
volumes = ["/var/run/docker.sock:/var/run/docker.sock", "/cache"] volumes = ["/var/run/docker.sock:/var/run/docker.sock", "/cache"]
...@@ -232,7 +232,7 @@ In order to do that, follow the steps: ...@@ -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): include the `docker:dind` service as when using the Docker in Docker executor):
```yaml ```yaml
image: docker:latest image: docker:stable
before_script: before_script:
- docker info - docker info
...@@ -286,7 +286,7 @@ any image that's used with the `--cache-from` argument must first be pulled ...@@ -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: Here's a simple `.gitlab-ci.yml` file showing how Docker caching can be utilized:
```yaml ```yaml
image: docker:latest image: docker:stable
services: services:
- docker:dind - docker:dind
...@@ -388,7 +388,7 @@ could look like: ...@@ -388,7 +388,7 @@ could look like:
```yaml ```yaml
build: build:
image: docker:latest image: docker:stable
services: services:
- docker:dind - docker:dind
stage: build stage: build
...@@ -434,7 +434,7 @@ when needed. Changes to `master` also get tagged as `latest` and deployed using ...@@ -434,7 +434,7 @@ when needed. Changes to `master` also get tagged as `latest` and deployed using
an application-specific deploy script: an application-specific deploy script:
```yaml ```yaml
image: docker:latest image: docker:stable
services: services:
- docker:dind - docker:dind
......
...@@ -86,7 +86,7 @@ services](#accessing-the-services). ...@@ -86,7 +86,7 @@ services](#accessing-the-services).
### How the health check of services works ### How the health check of services works
Services are designed to provide additional functionality which is **network accessible**. 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 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. required for the CI/CD job to proceed and is accessed by network.
......
...@@ -17,7 +17,7 @@ performance: ...@@ -17,7 +17,7 @@ performance:
variables: variables:
URL: https://example.com URL: https://example.com
services: services:
- docker:dind - docker:stable-dind
script: script:
- mkdir gitlab-exporter - mkdir gitlab-exporter
- wget -O ./gitlab-exporter/index.js https://gitlab.com/gitlab-org/gl-performance/raw/master/index.js - wget -O ./gitlab-exporter/index.js https://gitlab.com/gitlab-org/gl-performance/raw/master/index.js
...@@ -94,7 +94,7 @@ performance: ...@@ -94,7 +94,7 @@ performance:
stage: performance stage: performance
image: docker:git image: docker:git
services: services:
- docker:dind - docker:stable-dind
dependencies: dependencies:
- review - review
script: script:
......
...@@ -73,6 +73,21 @@ cancel the job, retry it, or erase the job trace. ...@@ -73,6 +73,21 @@ cancel the job, retry it, or erase the job trace.
![Pipelines example](img/pipelines.png) ![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 ## Pipeline graphs
> [Introduced][ce-5742] in GitLab 8.11. > [Introduced][ce-5742] in GitLab 8.11.
...@@ -263,4 +278,5 @@ runners will not use regular runners, they must be tagged accordingly. ...@@ -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-6242]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6242
[ce-7931]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7931 [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-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 [regexp]: https://gitlab.com/gitlab-org/gitlab-ce/blob/2f3dc314f42dbd79813e6251792853bc231e69dd/app/models/commit_status.rb#L99
...@@ -308,7 +308,9 @@ except master. ...@@ -308,7 +308,9 @@ except master.
## `only` and `except` (complex) ## `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:** CAUTION: **Warning:**
This an _alpha_ feature, and it it subject to change at any time without This an _alpha_ feature, and it it subject to change at any time without
......
...@@ -71,7 +71,7 @@ rm NAME-OF-FILE ...@@ -71,7 +71,7 @@ rm NAME-OF-FILE
### Remove a directory and all of its contents ### Remove a directory and all of its contents
``` ```
rm -rf NAME-OF-DIRECTORY rm -r NAME-OF-DIRECTORY
``` ```
### View history in the command line ### View history in the command line
......
...@@ -80,7 +80,7 @@ runners: ...@@ -80,7 +80,7 @@ runners:
image: ubuntu:16.04 image: ubuntu:16.04
## Run all containers with the privileged flag enabled ## 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: ## commands. Please read the docs before turning this on:
## ref: https://docs.gitlab.com/runner/executors/kubernetes.html#using-docker-dind ## ref: https://docs.gitlab.com/runner/executors/kubernetes.html#using-docker-dind
## ##
...@@ -147,7 +147,7 @@ enable privileged mode in `values.yaml`: ...@@ -147,7 +147,7 @@ enable privileged mode in `values.yaml`:
```yaml ```yaml
runners: runners:
## Run all containers with the privileged flag enabled ## 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: ## commands. Please read the docs before turning this on:
## ref: https://docs.gitlab.com/runner/executors/kubernetes.html#using-docker-dind ## 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. ...@@ -383,12 +383,12 @@ into your project to enable staging and canary deployments, and more.
### Custom buildpacks ### Custom buildpacks
If the automatic buildpack detection fails for your project, or if you want to 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 use a custom buildpack, you can override the buildpack(s) using a project variable
or a `.buildpack` file in your project: or a `.buildpacks` file in your project:
- **Project variable** - Create a project variable `BUILDPACK_URL` with the URL - **Project variable** - Create a project variable `BUILDPACK_URL` with the URL
of the buildpack to use. 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 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. use multiple buildpacks, you can enter them in, one on each line.
......
...@@ -15,8 +15,8 @@ in the table below. ...@@ -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. 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 ## Referencing issues
Issues are referenced with `#<ID>`, where `<ID>` is a number (example `#143`). - 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`).
So with the example above, `#143` would refer to `https://customissuetracker.com/project-name/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.
\ No newline at end of file - 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 ...@@ -6,70 +6,12 @@ module SharedNote
wait_for_requests if javascript_test? wait_for_requests if javascript_test?
end 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 step 'I haven\'t written any comment text' do
page.within(".js-main-target-form") do page.within(".js-main-target-form") do
fill_in "note[note]", with: "" fill_in "note[note]", with: ""
end end
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 step 'The comment preview tab should say there is nothing to do' do
page.within(".js-main-target-form") do page.within(".js-main-target-form") do
find('.js-md-preview-button').click find('.js-md-preview-button').click
...@@ -77,71 +19,7 @@ module SharedNote ...@@ -77,71 +19,7 @@ module SharedNote
end end
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 step 'I should see no notes at all' do
expect(page).not_to have_css('.note') expect(page).not_to have_css('.note')
end 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 end
...@@ -21,13 +21,7 @@ Capybara.register_driver :chrome do |app| ...@@ -21,13 +21,7 @@ Capybara.register_driver :chrome do |app|
options.add_argument("no-sandbox") options.add_argument("no-sandbox")
# Run headless by default unless CHROME_HEADLESS specified # Run headless by default unless CHROME_HEADLESS specified
unless ENV['CHROME_HEADLESS'] =~ /^(false|no|0)$/i options.add_argument("headless") 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
# Disable /dev/shm use in CI. See https://gitlab.com/gitlab-org/gitlab-ee/issues/4252 # 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'] options.add_argument("disable-dev-shm-usage") if ENV['CI'] || ENV['CI_SERVER']
......
...@@ -103,9 +103,9 @@ module API ...@@ -103,9 +103,9 @@ module API
end end
def find_project(id) def find_project(id)
if id =~ /^\d+$/ if id.is_a?(Integer) || id =~ /^\d+$/
Project.find_by(id: id) Project.find_by(id: id)
else elsif id.include?("/")
Project.find_by_full_path(id) Project.find_by_full_path(id)
end end
end end
......
...@@ -338,6 +338,11 @@ module API ...@@ -338,6 +338,11 @@ module API
end end
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' desc 'Remove a project'
delete ":id" do delete ":id" do
authorize! :remove_project, user_project authorize! :remove_project, user_project
......
...@@ -56,29 +56,29 @@ module Banzai ...@@ -56,29 +56,29 @@ module Banzai
# Implement in child class # Implement in child class
# Example: project.merge_requests.find # Example: project.merge_requests.find
def find_object(project, id) def find_object(parent_object, id)
end end
# Override if the link reference pattern produces a different ID (global # Override if the link reference pattern produces a different ID (global
# ID vs internal ID, for instance) to the regular reference pattern. # ID vs internal ID, for instance) to the regular reference pattern.
def find_object_from_link(project, id) def find_object_from_link(parent_object, id)
find_object(project, id) find_object(parent_object, id)
end end
# Implement in child class # Implement in child class
# Example: project_merge_request_url # Example: project_merge_request_url
def url_for_object(object, project) def url_for_object(object, parent_object)
end end
def find_object_cached(project, id) def find_object_cached(parent_object, id)
cached_call(:banzai_find_object, id, path: [object_class, project.id]) do cached_call(:banzai_find_object, id, path: [object_class, parent_object.id]) do
find_object(project, id) find_object(parent_object, id)
end end
end end
def find_object_from_link_cached(project, id) def find_object_from_link_cached(parent_object, id)
cached_call(:banzai_find_object_from_link, id, path: [object_class, project.id]) do cached_call(:banzai_find_object_from_link, id, path: [object_class, parent_object.id]) do
find_object_from_link(project, id) find_object_from_link(parent_object, id)
end end
end end
...@@ -88,9 +88,9 @@ module Banzai ...@@ -88,9 +88,9 @@ module Banzai
end end
end end
def url_for_object_cached(object, project) def url_for_object_cached(object, parent_object)
cached_call(:banzai_url_for_object, object, path: [object_class, project.id]) do cached_call(:banzai_url_for_object, object, path: [object_class, parent_object.id]) do
url_for_object(object, project) url_for_object(object, parent_object)
end end
end end
......
...@@ -23,6 +23,8 @@ module Banzai ...@@ -23,6 +23,8 @@ module Banzai
end end
def find_object(project, id) def find_object(project, id)
return unless project.is_a?(Project)
range = CommitRange.new(id, project) range = CommitRange.new(id, project)
range.valid_commits? ? range : nil range.valid_commits? ? range : nil
......
...@@ -17,6 +17,8 @@ module Banzai ...@@ -17,6 +17,8 @@ module Banzai
end end
def find_object(project, id) def find_object(project, id)
return unless project.is_a?(Project)
if project && project.valid_repo? if project && project.valid_repo?
# n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/43894 # n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/43894
Gitlab::GitalyClient.allow_n_plus_1_calls { project.commit(id) } Gitlab::GitalyClient.allow_n_plus_1_calls { project.commit(id) }
......
...@@ -8,8 +8,8 @@ module Banzai ...@@ -8,8 +8,8 @@ module Banzai
Label Label
end end
def find_object(project, id) def find_object(parent_object, id)
find_labels(project).find(id) find_labels(parent_object).find(id)
end end
def self.references_in(text, pattern = Label.reference_pattern) def self.references_in(text, pattern = Label.reference_pattern)
......
...@@ -12,10 +12,14 @@ module Banzai ...@@ -12,10 +12,14 @@ module Banzai
# 'regular' references, we need to use the global ID to disambiguate # 'regular' references, we need to use the global ID to disambiguate
# between group and project milestones. # between group and project milestones.
def find_object(project, id) def find_object(project, id)
return unless project.is_a?(Project)
find_milestone_with_finder(project, id: id) find_milestone_with_finder(project, id: id)
end end
def find_object_from_link(project, iid) def find_object_from_link(project, iid)
return unless project.is_a?(Project)
find_milestone_with_finder(project, iid: iid) find_milestone_with_finder(project, iid: iid)
end end
...@@ -40,7 +44,7 @@ module Banzai ...@@ -40,7 +44,7 @@ module Banzai
project_path = full_project_path(namespace_ref, project_ref) project_path = full_project_path(namespace_ref, project_ref)
project = parent_from_ref(project_path) project = parent_from_ref(project_path)
return unless project return unless project && project.is_a?(Project)
milestone_params = milestone_params(milestone_id, milestone_name) milestone_params = milestone_params(milestone_id, milestone_name)
......
...@@ -12,6 +12,8 @@ module Banzai ...@@ -12,6 +12,8 @@ module Banzai
end end
def find_object(project, id) def find_object(project, id)
return unless project.is_a?(Project)
project.snippets.find_by(id: id) project.snippets.find_by(id: id)
end end
......
...@@ -29,6 +29,8 @@ module Banzai ...@@ -29,6 +29,8 @@ module Banzai
end end
def find_object(project, id) def find_object(project, id)
return unless project.is_a?(Project)
range = CommitRange.new(id, project) range = CommitRange.new(id, project)
range.valid_commits? ? range : nil range.valid_commits? ? range : nil
......
...@@ -3,13 +3,18 @@ require_dependency 'gitlab/git' ...@@ -3,13 +3,18 @@ require_dependency 'gitlab/git'
module Gitlab module Gitlab
COM_URL = 'https://gitlab.com'.freeze COM_URL = 'https://gitlab.com'.freeze
APP_DIRS_PATTERN = %r{^/?(app|config|ee|lib|spec|\(\w*\))} APP_DIRS_PATTERN = %r{^/?(app|config|ee|lib|spec|\(\w*\))}
SUBDOMAIN_REGEX = %r{\Ahttps://[a-z0-9]+\.gitlab\.com\z}
def self.com? def self.com?
# Check `staging?` as well to keep parity with gitlab.com # Check `gl_subdomain?` as well to keep parity with gitlab.com
Gitlab.config.gitlab.url == COM_URL || staging? Gitlab.config.gitlab.url == COM_URL || gl_subdomain?
end end
def self.staging? def self.gl_subdomain?
Gitlab.config.gitlab.url == 'https://staging.gitlab.com' SUBDOMAIN_REGEX === Gitlab.config.gitlab.url
end
def self.dev_env_or_com?
Rails.env.test? || Rails.env.development? || com?
end end
end end
...@@ -40,7 +40,7 @@ module Gitlab ...@@ -40,7 +40,7 @@ module Gitlab
end end
def self.cache_key_for_project(project) def self.cache_key_for_project(project)
"projects/#{project.id}/pipeline_status" "#{Gitlab::Redis::Cache::CACHE_NAMESPACE}:projects/#{project.id}/pipeline_status"
end end
def self.update_for_pipeline(pipeline) def self.update_for_pipeline(pipeline)
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment