Commit 07f6ab2b authored by Marin Jankovski's avatar Marin Jankovski

Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce

parents 72111fad 27b98bea
...@@ -2,7 +2,6 @@ source 'https://rubygems.org' ...@@ -2,7 +2,6 @@ source 'https://rubygems.org'
gem 'rails', '4.2.8' gem 'rails', '4.2.8'
gem 'rails-deprecated_sanitizer', '~> 1.0.3' gem 'rails-deprecated_sanitizer', '~> 1.0.3'
gem 'bootsnap', '~> 1.0.0'
# Responders respond_to and respond_with # Responders respond_to and respond_with
gem 'responders', '~> 2.0' gem 'responders', '~> 2.0'
...@@ -123,6 +122,7 @@ gem 'asciidoctor', '~> 1.5.2' ...@@ -123,6 +122,7 @@ gem 'asciidoctor', '~> 1.5.2'
gem 'asciidoctor-plantuml', '0.0.7' gem 'asciidoctor-plantuml', '0.0.7'
gem 'rouge', '~> 2.0' gem 'rouge', '~> 2.0'
gem 'truncato', '~> 0.7.8' gem 'truncato', '~> 0.7.8'
gem 'bootstrap_form', '~> 2.7.0'
# See https://groups.google.com/forum/#!topic/ruby-security-ann/aSbgDiwb24s # See https://groups.google.com/forum/#!topic/ruby-security-ann/aSbgDiwb24s
# and https://groups.google.com/forum/#!topic/ruby-security-ann/Dy7YiKb_pMM # and https://groups.google.com/forum/#!topic/ruby-security-ann/Dy7YiKb_pMM
......
...@@ -83,11 +83,10 @@ GEM ...@@ -83,11 +83,10 @@ GEM
bindata (2.3.5) bindata (2.3.5)
binding_of_caller (0.7.2) binding_of_caller (0.7.2)
debug_inspector (>= 0.0.1) debug_inspector (>= 0.0.1)
bootsnap (1.0.0)
msgpack (~> 1.0)
bootstrap-sass (3.3.6) bootstrap-sass (3.3.6)
autoprefixer-rails (>= 5.2.1) autoprefixer-rails (>= 5.2.1)
sass (>= 3.3.4) sass (>= 3.3.4)
bootstrap_form (2.7.0)
brakeman (3.6.1) brakeman (3.6.1)
browser (2.2.0) browser (2.2.0)
builder (3.2.3) builder (3.2.3)
...@@ -353,7 +352,7 @@ GEM ...@@ -353,7 +352,7 @@ GEM
grape-entity (0.6.0) grape-entity (0.6.0)
activesupport activesupport
multi_json (>= 1.3.2) multi_json (>= 1.3.2)
grpc (1.2.5) grpc (1.4.0)
google-protobuf (~> 3.1) google-protobuf (~> 3.1)
googleauth (~> 0.5.1) googleauth (~> 0.5.1)
haml (4.0.7) haml (4.0.7)
...@@ -464,7 +463,6 @@ GEM ...@@ -464,7 +463,6 @@ GEM
minitest (5.7.0) minitest (5.7.0)
mmap2 (2.2.6) mmap2 (2.2.6)
mousetrap-rails (1.4.6) mousetrap-rails (1.4.6)
msgpack (1.1.0)
multi_json (1.12.1) multi_json (1.12.1)
multi_xml (0.6.0) multi_xml (0.6.0)
multipart-post (2.0.0) multipart-post (2.0.0)
...@@ -928,8 +926,8 @@ DEPENDENCIES ...@@ -928,8 +926,8 @@ DEPENDENCIES
benchmark-ips (~> 2.3.0) benchmark-ips (~> 2.3.0)
better_errors (~> 2.1.0) better_errors (~> 2.1.0)
binding_of_caller (~> 0.7.2) binding_of_caller (~> 0.7.2)
bootsnap (~> 1.0.0)
bootstrap-sass (~> 3.3.0) bootstrap-sass (~> 3.3.0)
bootstrap_form (~> 2.7.0)
brakeman (~> 3.6.0) brakeman (~> 3.6.0)
browser (~> 2.2) browser (~> 2.2)
bullet (~> 5.5.0) bullet (~> 5.5.0)
......
This diff is collapsed.
...@@ -17,7 +17,7 @@ export default { ...@@ -17,7 +17,7 @@ export default {
methods: { methods: {
submit(e) { submit(e) {
e.preventDefault(); e.preventDefault();
if (this.title.trim() === '') return; if (this.title.trim() === '') return Promise.resolve();
this.error = false; this.error = false;
...@@ -29,7 +29,10 @@ export default { ...@@ -29,7 +29,10 @@ export default {
assignees: [], assignees: [],
}); });
this.list.newIssue(issue) eventHub.$emit(`scroll-board-list-${this.list.id}`);
this.cancel();
return this.list.newIssue(issue)
.then(() => { .then(() => {
// Need this because our jQuery very kindly disables buttons on ALL form submissions // Need this because our jQuery very kindly disables buttons on ALL form submissions
$(this.$refs.submitButton).enable(); $(this.$refs.submitButton).enable();
...@@ -47,9 +50,6 @@ export default { ...@@ -47,9 +50,6 @@ export default {
// Show error message // Show error message
this.error = true; this.error = true;
}); });
eventHub.$emit(`scroll-board-list-${this.list.id}`);
this.cancel();
}, },
cancel() { cancel() {
this.title = ''; this.title = '';
......
...@@ -112,8 +112,7 @@ class List { ...@@ -112,8 +112,7 @@ class List {
.then((resp) => { .then((resp) => {
const data = resp.json(); const data = resp.json();
issue.id = data.iid; issue.id = data.iid;
})
.then(() => {
if (this.issuesSize > 1) { if (this.issuesSize > 1) {
const moveBeforeIid = this.issues[1].id; const moveBeforeIid = this.issues[1].id;
gl.boardService.moveIssue(issue.id, null, null, null, moveBeforeIid); gl.boardService.moveIssue(issue.id, null, null, null, moveBeforeIid);
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
import playIconSvg from 'icons/_icon_play.svg'; import playIconSvg from 'icons/_icon_play.svg';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
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';
export default { export default {
props: { props: {
...@@ -12,6 +13,10 @@ export default { ...@@ -12,6 +13,10 @@ export default {
}, },
}, },
directives: {
tooltip,
},
components: { components: {
loadingIcon, loadingIcon,
}, },
...@@ -33,8 +38,6 @@ export default { ...@@ -33,8 +38,6 @@ export default {
onClickAction(endpoint) { onClickAction(endpoint) {
this.isLoading = true; this.isLoading = true;
$(this.$refs.tooltip).tooltip('destroy');
eventHub.$emit('postAction', endpoint); eventHub.$emit('postAction', endpoint);
}, },
...@@ -53,11 +56,11 @@ export default { ...@@ -53,11 +56,11 @@ export default {
class="btn-group" class="btn-group"
role="group"> role="group">
<button <button
v-tooltip
type="button" type="button"
class="dropdown btn btn-default dropdown-new js-dropdown-play-icon-container has-tooltip" class="dropdown btn btn-default dropdown-new js-dropdown-play-icon-container"
data-container="body" data-container="body"
data-toggle="dropdown" data-toggle="dropdown"
ref="tooltip"
:title="title" :title="title"
:aria-label="title" :aria-label="title"
:disabled="isLoading"> :disabled="isLoading">
......
<script> <script>
import tooltip from '../../vue_shared/directives/tooltip';
/** /**
* Renders the external url link in environments table. * Renders the external url link in environments table.
*/ */
...@@ -10,6 +12,10 @@ export default { ...@@ -10,6 +12,10 @@ export default {
}, },
}, },
directives: {
tooltip,
},
computed: { computed: {
title() { title() {
return 'Open'; return 'Open';
...@@ -19,7 +25,8 @@ export default { ...@@ -19,7 +25,8 @@ export default {
</script> </script>
<template> <template>
<a <a
class="btn external-url has-tooltip" v-tooltip
class="btn external-url"
data-container="body" data-container="body"
target="_blank" target="_blank"
rel="noopener noreferrer nofollow" rel="noopener noreferrer nofollow"
......
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
/** /**
* Renders the Monitoring (Metrics) link in environments table. * Renders the Monitoring (Metrics) link in environments table.
*/ */
import tooltip from '../../vue_shared/directives/tooltip';
export default { export default {
props: { props: {
monitoringUrl: { monitoringUrl: {
...@@ -10,6 +12,10 @@ export default { ...@@ -10,6 +12,10 @@ export default {
}, },
}, },
directives: {
tooltip,
},
computed: { computed: {
title() { title() {
return 'Monitoring'; return 'Monitoring';
...@@ -19,7 +25,8 @@ export default { ...@@ -19,7 +25,8 @@ export default {
</script> </script>
<template> <template>
<a <a
class="btn monitoring-url has-tooltip hidden-xs hidden-sm" v-tooltip
class="btn monitoring-url hidden-xs hidden-sm"
data-container="body" data-container="body"
rel="noopener noreferrer nofollow" rel="noopener noreferrer nofollow"
:href="monitoringUrl" :href="monitoringUrl"
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
*/ */
import eventHub from '../event_hub'; import eventHub from '../event_hub';
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';
export default { export default {
props: { props: {
...@@ -14,6 +15,10 @@ export default { ...@@ -14,6 +15,10 @@ export default {
}, },
}, },
directives: {
tooltip,
},
data() { data() {
return { return {
isLoading: false, isLoading: false,
...@@ -46,8 +51,9 @@ export default { ...@@ -46,8 +51,9 @@ export default {
</script> </script>
<template> <template>
<button <button
v-tooltip
type="button" type="button"
class="btn stop-env-link has-tooltip hidden-xs hidden-sm" class="btn stop-env-link hidden-xs hidden-sm"
data-container="body" data-container="body"
@click="onClick" @click="onClick"
:disabled="isLoading" :disabled="isLoading"
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
* Used in environments table. * Used in environments table.
*/ */
import terminalIconSvg from 'icons/_icon_terminal.svg'; import terminalIconSvg from 'icons/_icon_terminal.svg';
import tooltip from '../../vue_shared/directives/tooltip';
export default { export default {
props: { props: {
...@@ -14,6 +15,10 @@ export default { ...@@ -14,6 +15,10 @@ export default {
}, },
}, },
directives: {
tooltip,
},
data() { data() {
return { return {
terminalIconSvg, terminalIconSvg,
...@@ -29,7 +34,8 @@ export default { ...@@ -29,7 +34,8 @@ export default {
</script> </script>
<template> <template>
<a <a
class="btn terminal-button has-tooltip hidden-xs hidden-sm" v-tooltip
class="btn terminal-button hidden-xs hidden-sm"
data-container="body" data-container="body"
:title="title" :title="title"
:aria-label="title" :aria-label="title"
......
...@@ -40,6 +40,10 @@ class FilteredSearchManager { ...@@ -40,6 +40,10 @@ class FilteredSearchManager {
return []; return [];
}) })
.then((searches) => { .then((searches) => {
if (!searches) {
return;
}
// Put any searches that may have come in before // Put any searches that may have come in before
// we fetched the saved searches ahead of the already saved ones // we fetched the saved searches ahead of the already saved ones
const resultantSearches = this.recentSearchesStore.setRecentSearches( const resultantSearches = this.recentSearchesStore.setRecentSearches(
......
...@@ -47,8 +47,8 @@ export default class GroupsStore { ...@@ -47,8 +47,8 @@ export default class GroupsStore {
// Map groups to an object // Map groups to an object
groups.map((group) => { groups.map((group) => {
mappedGroups[group.id] = group; mappedGroups[`id${group.id}`] = group;
mappedGroups[group.id].subGroups = {}; mappedGroups[`id${group.id}`].subGroups = {};
return group; return group;
}); });
...@@ -56,26 +56,27 @@ export default class GroupsStore { ...@@ -56,26 +56,27 @@ export default class GroupsStore {
const currentGroup = mappedGroups[key]; const currentGroup = mappedGroups[key];
if (currentGroup.parentId) { if (currentGroup.parentId) {
// If the group is not at the root level, add it to its parent array of subGroups. // If the group is not at the root level, add it to its parent array of subGroups.
const findParentGroup = mappedGroups[currentGroup.parentId]; const findParentGroup = mappedGroups[`id${currentGroup.parentId}`];
if (findParentGroup) { if (findParentGroup) {
mappedGroups[currentGroup.parentId].subGroups[currentGroup.id] = currentGroup; mappedGroups[`id${currentGroup.parentId}`].subGroups[`id${currentGroup.id}`] = currentGroup;
mappedGroups[currentGroup.parentId].isOpen = true; // Expand group if it has subgroups mappedGroups[`id${currentGroup.parentId}`].isOpen = true; // Expand group if it has subgroups
} else if (parentGroup && parentGroup.id === currentGroup.parentId) { } else if (parentGroup && parentGroup.id === currentGroup.parentId) {
tree[currentGroup.id] = currentGroup; tree[`id${currentGroup.id}`] = currentGroup;
} else { } else {
// Means the groups hast no direct parent. // No parent found. We save it for later processing
// Save for later processing, we will add them to its corresponding base group
orphans.push(currentGroup); orphans.push(currentGroup);
// Add to tree to preserve original order
tree[`id${currentGroup.id}`] = currentGroup;
} }
} else { } else {
// If the group is at the root level, add it to first level elements array. // If the group is at the top level, add it to first level elements array.
tree[currentGroup.id] = currentGroup; tree[`id${currentGroup.id}`] = currentGroup;
} }
return key; return key;
}); });
// Hopefully this array will be empty for most cases
if (orphans.length) { if (orphans.length) {
orphans.map((orphan) => { orphans.map((orphan) => {
let found = false; let found = false;
...@@ -83,11 +84,23 @@ export default class GroupsStore { ...@@ -83,11 +84,23 @@ export default class GroupsStore {
Object.keys(tree).map((key) => { Object.keys(tree).map((key) => {
const group = tree[key]; const group = tree[key];
if (currentOrphan.fullPath.lastIndexOf(group.fullPath) === 0) {
if (
group &&
currentOrphan.fullPath.lastIndexOf(group.fullPath) === 0 &&
// Make sure the currently selected orphan is not the same as the group
// we are checking here otherwise it will end up in an infinite loop
currentOrphan.id !== group.id
) {
group.subGroups[currentOrphan.id] = currentOrphan; group.subGroups[currentOrphan.id] = currentOrphan;
group.isOpen = true; group.isOpen = true;
currentOrphan.isOrphan = true; currentOrphan.isOrphan = true;
found = true; found = true;
// Delete if group was put at the top level. If not the group will be displayed twice.
if (tree[`id${currentOrphan.id}`]) {
delete tree[`id${currentOrphan.id}`];
}
} }
return key; return key;
...@@ -95,7 +108,8 @@ export default class GroupsStore { ...@@ -95,7 +108,8 @@ export default class GroupsStore {
if (!found) { if (!found) {
currentOrphan.isOrphan = true; currentOrphan.isOrphan = true;
tree[currentOrphan.id] = currentOrphan;
tree[`id${currentOrphan.id}`] = currentOrphan;
} }
return orphan; return orphan;
...@@ -140,7 +154,7 @@ export default class GroupsStore { ...@@ -140,7 +154,7 @@ export default class GroupsStore {
// eslint-disable-next-line class-methods-use-this // eslint-disable-next-line class-methods-use-this
removeGroup(group, collection) { removeGroup(group, collection) {
Vue.delete(collection, group.id); Vue.delete(collection, `id${group.id}`);
} }
// eslint-disable-next-line class-methods-use-this // eslint-disable-next-line class-methods-use-this
......
...@@ -47,7 +47,8 @@ ...@@ -47,7 +47,8 @@
ref="textarea" ref="textarea"
slot="textarea" slot="textarea"
placeholder="Write a comment or drag your files here..." placeholder="Write a comment or drag your files here..."
@keydown.meta.enter="updateIssuable"> @keydown.meta.enter="updateIssuable"
@keydown.ctrl.enter="updateIssuable">
</textarea> </textarea>
</markdown-field> </markdown-field>
</div> </div>
......
<script> <script>
import tooltipMixin from '../../../vue_shared/mixins/tooltip'; import tooltip from '../../../vue_shared/directives/tooltip';
export default { export default {
mixins: [ directives: {
tooltipMixin, tooltip,
], },
props: { props: {
formState: { formState: {
type: Object, type: Object,
...@@ -71,9 +71,9 @@ ...@@ -71,9 +71,9 @@
data-placeholder="Move to a different project" /> data-placeholder="Move to a different project" />
</div> </div>
<span <span
v-tooltip
data-placement="auto top" data-placement="auto top"
title="Moving an issue will copy the discussion to a different project and close it here. All participants will be notified of the new location." title="Moving an issue will copy the discussion to a different project and close it here. All participants will be notified of the new location.">
ref="tooltip">
<i <i
class="fa fa-question-circle" class="fa fa-question-circle"
aria-hidden="true"> aria-hidden="true">
......
...@@ -26,6 +26,7 @@ ...@@ -26,6 +26,7 @@
placeholder="Issue title" placeholder="Issue title"
aria-label="Issue title" aria-label="Issue title"
v-model="formState.title" v-model="formState.title"
@keydown.meta.enter="updateIssuable" /> @keydown.meta.enter="updateIssuable"
@keydown.ctrl.enter="updateIssuable" />
</fieldset> </fieldset>
</template> </template>
...@@ -86,18 +86,25 @@ ...@@ -86,18 +86,25 @@
// This is required to handle non-unicode characters in hash // This is required to handle non-unicode characters in hash
hash = decodeURIComponent(hash); hash = decodeURIComponent(hash);
var fixedTabs = document.querySelector('.js-tabs-affix');
var fixedNav = document.querySelector('.navbar-gitlab');
var adjustment = 0;
if (fixedNav) adjustment -= fixedNav.offsetHeight;
// scroll to user-generated markdown anchor if we cannot find a match // scroll to user-generated markdown anchor if we cannot find a match
if (document.getElementById(hash) === null) { if (document.getElementById(hash) === null) {
var target = document.getElementById('user-content-' + hash); var target = document.getElementById('user-content-' + hash);
if (target && target.scrollIntoView) { if (target && target.scrollIntoView) {
target.scrollIntoView(true); target.scrollIntoView(true);
window.scrollBy(0, adjustment);
} }
} else { } else {
// only adjust for fixedTabs when not targeting user-generated content // only adjust for fixedTabs when not targeting user-generated content
var fixedTabs = document.querySelector('.js-tabs-affix');
if (fixedTabs) { if (fixedTabs) {
window.scrollBy(0, -fixedTabs.offsetHeight); adjustment -= fixedTabs.offsetHeight;
} }
window.scrollBy(0, adjustment);
} }
}; };
......
This diff is collapsed.
...@@ -155,7 +155,10 @@ import BlobForkSuggestion from './blob/blob_fork_suggestion'; ...@@ -155,7 +155,10 @@ import BlobForkSuggestion from './blob/blob_fork_suggestion';
scrollToElement(container) { scrollToElement(container) {
if (location.hash) { if (location.hash) {
const offset = -$('.js-tabs-affix').outerHeight(); const offset = 0 - (
$('.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 });
...@@ -291,7 +294,7 @@ import BlobForkSuggestion from './blob/blob_fork_suggestion'; ...@@ -291,7 +294,7 @@ import BlobForkSuggestion from './blob/blob_fork_suggestion';
// Scroll any linked note into view // Scroll any linked note into view
// Similar to `toggler_behavior` in the discussion tab // Similar to `toggler_behavior` in the discussion tab
const hash = window.gl.utils.getLocationHash(); const hash = window.gl.utils.getLocationHash();
const anchor = hash && $container.find(`[id="${hash}"]`); const anchor = hash && $container.find(`.note[id="${hash}"]`);
if (anchor && anchor.length > 0) { if (anchor && anchor.length > 0) {
const notesContent = anchor.closest('.notes_content'); const notesContent = anchor.closest('.notes_content');
const lineType = notesContent.hasClass('new') ? 'new' : 'old'; const lineType = notesContent.hasClass('new') ? 'new' : 'old';
...@@ -301,6 +304,7 @@ import BlobForkSuggestion from './blob/blob_fork_suggestion'; ...@@ -301,6 +304,7 @@ import BlobForkSuggestion from './blob/blob_fork_suggestion';
forceShow: true, forceShow: true,
}); });
anchor[0].scrollIntoView(); anchor[0].scrollIntoView();
window.gl.utils.handleLocationHash();
// We have multiple elements on the page with `#note_xxx` // We have multiple elements on the page with `#note_xxx`
// (discussion and diff tabs) and `:target` only applies to the first // (discussion and diff tabs) and `:target` only applies to the first
anchor.addClass('target'); anchor.addClass('target');
......
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
import eventHub from '../event_hub'; import eventHub from '../event_hub';
import loadingIcon from '../../vue_shared/components/loading_icon.vue'; import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import tooltipMixin from '../../vue_shared/mixins/tooltip'; import tooltip from '../../vue_shared/directives/tooltip';
export default { export default {
props: { props: {
...@@ -28,12 +28,12 @@ export default { ...@@ -28,12 +28,12 @@ export default {
required: false, required: false,
}, },
}, },
directives: {
tooltip,
},
components: { components: {
loadingIcon, loadingIcon,
}, },
mixins: [
tooltipMixin,
],
data() { data() {
return { return {
isLoading: false, isLoading: false,
...@@ -58,7 +58,6 @@ export default { ...@@ -58,7 +58,6 @@ export default {
makeRequest() { makeRequest() {
this.isLoading = true; this.isLoading = true;
$(this.$refs.tooltip).tooltip('destroy');
eventHub.$emit('postAction', this.endpoint); eventHub.$emit('postAction', this.endpoint);
}, },
}, },
...@@ -67,6 +66,7 @@ export default { ...@@ -67,6 +66,7 @@ export default {
<template> <template>
<button <button
v-tooltip
type="button" type="button"
@click="onClick" @click="onClick"
:class="buttonClass" :class="buttonClass"
...@@ -74,7 +74,6 @@ export default { ...@@ -74,7 +74,6 @@ export default {
:aria-label="title" :aria-label="title"
data-container="body" data-container="body"
data-placement="top" data-placement="top"
ref="tooltip"
:disabled="isLoading"> :disabled="isLoading">
<i <i
:class="iconClass" :class="iconClass"
......
<script> <script>
import getActionIcon from '../../../vue_shared/ci_action_icons'; import getActionIcon from '../../../vue_shared/ci_action_icons';
import tooltipMixin from '../../../vue_shared/mixins/tooltip'; import tooltip from '../../../vue_shared/directives/tooltip';
/** /**
* Renders either a cancel, retry or play icon pointing to the given path. * Renders either a cancel, retry or play icon pointing to the given path.
...@@ -29,9 +29,9 @@ ...@@ -29,9 +29,9 @@
}, },
}, },
mixins: [ directives: {
tooltipMixin, tooltip,
], },
computed: { computed: {
actionIconSvg() { actionIconSvg() {
...@@ -46,12 +46,11 @@ ...@@ -46,12 +46,11 @@
</script> </script>
<template> <template>
<a <a
v-tooltip
:data-method="actionMethod" :data-method="actionMethod"
:title="tooltipText" :title="tooltipText"
:href="link" :href="link"
ref="tooltip"
class="ci-action-icon-container" class="ci-action-icon-container"
data-toggle="tooltip"
data-container="body"> data-container="body">
<i <i
......
<script> <script>
import getActionIcon from '../../../vue_shared/ci_action_icons'; import getActionIcon from '../../../vue_shared/ci_action_icons';
import tooltipMixin from '../../../vue_shared/mixins/tooltip'; import tooltip from '../../../vue_shared/directives/tooltip';
/** /**
* Renders either a cancel, retry or play icon pointing to the given path. * Renders either a cancel, retry or play icon pointing to the given path.
...@@ -29,9 +29,9 @@ ...@@ -29,9 +29,9 @@
}, },
}, },
mixins: [ directives: {
tooltipMixin, tooltip,
], },
computed: { computed: {
actionIconSvg() { actionIconSvg() {
...@@ -42,13 +42,12 @@ ...@@ -42,13 +42,12 @@
</script> </script>
<template> <template>
<a <a
v-tooltip
:data-method="actionMethod" :data-method="actionMethod"
:title="tooltipText" :title="tooltipText"
:href="link" :href="link"
ref="tooltip"
rel="nofollow" rel="nofollow"
class="ci-action-icon-wrapper js-ci-status-icon" class="ci-action-icon-wrapper js-ci-status-icon"
data-toggle="tooltip"
data-container="body" data-container="body"
v-html="actionIconSvg" v-html="actionIconSvg"
aria-label="Job's action"> aria-label="Job's action">
......
<script> <script>
import jobNameComponent from './job_name_component.vue'; import jobNameComponent from './job_name_component.vue';
import jobComponent from './job_component.vue'; import jobComponent from './job_component.vue';
import tooltipMixin from '../../../vue_shared/mixins/tooltip'; import tooltip from '../../../vue_shared/directives/tooltip';
/** /**
* Renders the dropdown for the pipeline graph. * Renders the dropdown for the pipeline graph.
...@@ -34,9 +34,9 @@ ...@@ -34,9 +34,9 @@
}, },
}, },
mixins: [ directives: {
tooltipMixin, tooltip,
], },
components: { components: {
jobComponent, jobComponent,
...@@ -53,12 +53,12 @@ ...@@ -53,12 +53,12 @@
<template> <template>
<div> <div>
<button <button
v-tooltip
type="button" type="button"
data-toggle="dropdown" data-toggle="dropdown"
data-container="body" data-container="body"
class="dropdown-menu-toggle build-content" class="dropdown-menu-toggle build-content"
:title="tooltipText" :title="tooltipText">
ref="tooltip">
<job-name-component <job-name-component
:name="job.name" :name="job.name"
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
import actionComponent from './action_component.vue'; import actionComponent from './action_component.vue';
import dropdownActionComponent from './dropdown_action_component.vue'; import dropdownActionComponent from './dropdown_action_component.vue';
import jobNameComponent from './job_name_component.vue'; import jobNameComponent from './job_name_component.vue';
import tooltipMixin from '../../../vue_shared/mixins/tooltip'; import tooltip from '../../../vue_shared/directives/tooltip';
/** /**
* Renders the badge for the pipeline graph and the job's dropdown. * Renders the badge for the pipeline graph and the job's dropdown.
...@@ -54,9 +54,9 @@ ...@@ -54,9 +54,9 @@
jobNameComponent, jobNameComponent,
}, },
mixins: [ directives: {
tooltipMixin, tooltip,
], },
computed: { computed: {
tooltipText() { tooltipText() {
...@@ -77,12 +77,11 @@ ...@@ -77,12 +77,11 @@
<template> <template>
<div> <div>
<a <a
v-tooltip
v-if="job.status.details_path" v-if="job.status.details_path"
:href="job.status.details_path" :href="job.status.details_path"
:title="tooltipText" :title="tooltipText"
:class="cssClassJobName" :class="cssClassJobName"
ref="tooltip"
data-toggle="tooltip"
data-container="body"> data-container="body">
<job-name-component <job-name-component
...@@ -93,10 +92,9 @@ ...@@ -93,10 +92,9 @@
<div <div
v-else v-else
v-tooltip
:title="tooltipText" :title="tooltipText"
:class="cssClassJobName" :class="cssClassJobName"
ref="tooltip"
data-toggle="tooltip"
data-container="body"> data-container="body">
<job-name-component <job-name-component
......
<script> <script>
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue'; import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import tooltipMixin from '../../vue_shared/mixins/tooltip'; import tooltip from '../../vue_shared/directives/tooltip';
export default { export default {
props: { props: {
...@@ -12,9 +12,9 @@ export default { ...@@ -12,9 +12,9 @@ export default {
components: { components: {
userAvatarLink, userAvatarLink,
}, },
mixins: [ directives: {
tooltipMixin, tooltip,
], },
computed: { computed: {
user() { user() {
return this.pipeline.user; return this.pipeline.user;
...@@ -45,16 +45,16 @@ export default { ...@@ -45,16 +45,16 @@ export default {
<div class="label-container"> <div class="label-container">
<span <span
v-if="pipeline.flags.latest" v-if="pipeline.flags.latest"
v-tooltip
class="js-pipeline-url-latest label label-success" class="js-pipeline-url-latest label label-success"
title="Latest pipeline for this branch" title="Latest pipeline for this branch">
ref="tooltip">
latest latest
</span> </span>
<span <span
v-if="pipeline.flags.yaml_errors" v-if="pipeline.flags.yaml_errors"
v-tooltip
class="js-pipeline-url-yaml label label-danger" class="js-pipeline-url-yaml label label-danger"
:title="pipeline.yaml_errors" :title="pipeline.yaml_errors">
ref="tooltip">
yaml invalid yaml invalid
</span> </span>
<span <span
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
import playIconSvg from 'icons/_icon_play.svg'; import playIconSvg from 'icons/_icon_play.svg';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
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';
export default { export default {
props: { props: {
...@@ -12,6 +13,9 @@ ...@@ -12,6 +13,9 @@
required: true, required: true,
}, },
}, },
directives: {
tooltip,
},
components: { components: {
loadingIcon, loadingIcon,
}, },
...@@ -25,8 +29,6 @@ ...@@ -25,8 +29,6 @@
onClickAction(endpoint) { onClickAction(endpoint) {
this.isLoading = true; this.isLoading = true;
$(this.$refs.tooltip).tooltip('destroy');
eventHub.$emit('postAction', endpoint); eventHub.$emit('postAction', endpoint);
}, },
...@@ -43,13 +45,13 @@ ...@@ -43,13 +45,13 @@
<template> <template>
<div class="btn-group"> <div class="btn-group">
<button <button
v-tooltip
type="button" type="button"
class="dropdown-new btn btn-default has-tooltip js-pipeline-dropdown-manual-actions" class="dropdown-new btn btn-default js-pipeline-dropdown-manual-actions"
title="Manual job" title="Manual job"
data-toggle="dropdown" data-toggle="dropdown"
data-placement="top" data-placement="top"
aria-label="Manual job" aria-label="Manual job"
ref="tooltip"
:disabled="isLoading"> :disabled="isLoading">
<span v-html="playIconSvg"></span> <span v-html="playIconSvg"></span>
<i <i
......
<script> <script>
import tooltipMixin from '../../vue_shared/mixins/tooltip'; import tooltip from '../../vue_shared/directives/tooltip';
export default { export default {
props: { props: {
...@@ -8,9 +8,9 @@ ...@@ -8,9 +8,9 @@
required: true, required: true,
}, },
}, },
mixins: [ directives: {
tooltipMixin, tooltip,
], },
}; };
</script> </script>
<template> <template>
...@@ -18,12 +18,12 @@ ...@@ -18,12 +18,12 @@
class="btn-group" class="btn-group"
role="group"> role="group">
<button <button
v-tooltip
class="dropdown-toggle btn btn-default build-artifacts js-pipeline-dropdown-download" class="dropdown-toggle btn btn-default build-artifacts js-pipeline-dropdown-download"
title="Artifacts" title="Artifacts"
data-placement="top" data-placement="top"
data-toggle="dropdown" data-toggle="dropdown"
aria-label="Artifacts" aria-label="Artifacts">
ref="tooltip">
<i <i
class="fa fa-download" class="fa fa-download"
aria-hidden="true"> aria-hidden="true">
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
/* global Flash */ /* global Flash */
import { borderlessStatusIconEntityMap } from '../../vue_shared/ci_status_icons'; import { borderlessStatusIconEntityMap } from '../../vue_shared/ci_status_icons';
import loadingIcon from '../../vue_shared/components/loading_icon.vue'; import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import tooltipMixin from '../../vue_shared/mixins/tooltip'; import tooltip from '../../vue_shared/directives/tooltip';
export default { export default {
props: { props: {
...@@ -32,9 +32,9 @@ export default { ...@@ -32,9 +32,9 @@ export default {
}, },
}, },
mixins: [ directives: {
tooltipMixin, tooltip,
], },
data() { data() {
return { return {
...@@ -132,7 +132,7 @@ export default { ...@@ -132,7 +132,7 @@ export default {
<template> <template>
<div class="dropdown"> <div class="dropdown">
<button <button
ref="tooltip" v-tooltip
:class="triggerButtonClass" :class="triggerButtonClass"
@click="onClickStage" @click="onClickStage"
class="mini-pipeline-graph-dropdown-toggle js-builds-dropdown-button" class="mini-pipeline-graph-dropdown-toggle js-builds-dropdown-button"
......
<script> <script>
import iconTimerSvg from 'icons/_icon_timer.svg'; import iconTimerSvg from 'icons/_icon_timer.svg';
import '../../lib/utils/datetime_utility'; import '../../lib/utils/datetime_utility';
import tooltipMixin from '../../vue_shared/mixins/tooltip'; import tooltip from '../../vue_shared/directives/tooltip';
import timeagoMixin from '../../vue_shared/mixins/timeago'; import timeagoMixin from '../../vue_shared/mixins/timeago';
export default { export default {
...@@ -16,9 +16,11 @@ ...@@ -16,9 +16,11 @@
}, },
}, },
mixins: [ mixins: [
tooltipMixin,
timeagoMixin, timeagoMixin,
], ],
directives: {
tooltip,
},
data() { data() {
return { return {
iconTimerSvg, iconTimerSvg,
...@@ -81,7 +83,7 @@ ...@@ -81,7 +83,7 @@
</i> </i>
<time <time
ref="tooltip" v-tooltip
data-placement="top" data-placement="top"
data-container="body" data-container="body"
:title="tooltipTitle(finishedTime)"> :title="tooltipTitle(finishedTime)">
......
export default {
EMPTY: 'empty',
LOADING: 'loading',
LIST: 'list',
};
import PrometheusMetrics from './prometheus_metrics';
$(() => {
const prometheusMetrics = new PrometheusMetrics('.js-prometheus-metrics-monitoring');
prometheusMetrics.loadActiveMetrics();
});
import PANEL_STATE from './constants';
export default class PrometheusMetrics {
constructor(wrapperSelector) {
this.backOffRequestCounter = 0;
this.$wrapper = $(wrapperSelector);
this.$monitoredMetricsPanel = this.$wrapper.find('.js-panel-monitored-metrics');
this.$monitoredMetricsCount = this.$monitoredMetricsPanel.find('.js-monitored-count');
this.$monitoredMetricsLoading = this.$monitoredMetricsPanel.find('.js-loading-metrics');
this.$monitoredMetricsEmpty = this.$monitoredMetricsPanel.find('.js-empty-metrics');
this.$monitoredMetricsList = this.$monitoredMetricsPanel.find('.js-metrics-list');
this.$missingEnvVarPanel = this.$wrapper.find('.js-panel-missing-env-vars');
this.$panelToggle = this.$missingEnvVarPanel.find('.js-panel-toggle');
this.$missingEnvVarMetricCount = this.$missingEnvVarPanel.find('.js-env-var-count');
this.$missingEnvVarMetricsList = this.$missingEnvVarPanel.find('.js-missing-var-metrics-list');
this.activeMetricsEndpoint = this.$monitoredMetricsPanel.data('active-metrics');
this.$panelToggle.on('click', e => this.handlePanelToggle(e));
}
/* eslint-disable class-methods-use-this */
handlePanelToggle(e) {
const $toggleBtn = $(e.currentTarget);
const $currentPanelBody = $toggleBtn.closest('.panel').find('.panel-body');
$currentPanelBody.toggleClass('hidden');
if ($toggleBtn.hasClass('fa-caret-down')) {
$toggleBtn.removeClass('fa-caret-down').addClass('fa-caret-right');
} else {
$toggleBtn.removeClass('fa-caret-right').addClass('fa-caret-down');
}
}
showMonitoringMetricsPanelState(stateName) {
switch (stateName) {
case PANEL_STATE.LOADING:
this.$monitoredMetricsLoading.removeClass('hidden');
this.$monitoredMetricsEmpty.addClass('hidden');
this.$monitoredMetricsList.addClass('hidden');
break;
case PANEL_STATE.LIST:
this.$monitoredMetricsLoading.addClass('hidden');
this.$monitoredMetricsEmpty.addClass('hidden');
this.$monitoredMetricsList.removeClass('hidden');
break;
default:
this.$monitoredMetricsLoading.addClass('hidden');
this.$monitoredMetricsEmpty.removeClass('hidden');
this.$monitoredMetricsList.addClass('hidden');
break;
}
}
populateActiveMetrics(metrics) {
let totalMonitoredMetrics = 0;
let totalMissingEnvVarMetrics = 0;
metrics.forEach((metric) => {
this.$monitoredMetricsList.append(`<li>${metric.group}<span class="badge">${metric.active_metrics}</span></li>`);
totalMonitoredMetrics += metric.active_metrics;
if (metric.metrics_missing_requirements > 0) {
this.$missingEnvVarMetricsList.append(`<li>${metric.group}</li>`);
totalMissingEnvVarMetrics += 1;
}
});
this.$monitoredMetricsCount.text(totalMonitoredMetrics);
this.showMonitoringMetricsPanelState(PANEL_STATE.LIST);
if (totalMissingEnvVarMetrics > 0) {
this.$missingEnvVarPanel.removeClass('hidden');
this.$missingEnvVarPanel.find('.flash-container').off('click');
this.$missingEnvVarMetricCount.text(totalMissingEnvVarMetrics);
}
}
loadActiveMetrics() {
this.showMonitoringMetricsPanelState(PANEL_STATE.LOADING);
gl.utils.backOff((next, stop) => {
$.getJSON(this.activeMetricsEndpoint)
.done((res) => {
if (res && res.success) {
stop(res);
} else {
this.backOffRequestCounter = this.backOffRequestCounter += 1;
if (this.backOffRequestCounter < 3) {
next();
} else {
stop(res);
}
}
})
.fail(stop);
})
.then((res) => {
if (res && res.data && res.data.length) {
this.populateActiveMetrics(res.data);
} else {
this.showMonitoringMetricsPanelState(PANEL_STATE.EMPTY);
}
})
.catch(() => {
this.showMonitoringMetricsPanelState(PANEL_STATE.EMPTY);
});
}
}
...@@ -10,8 +10,6 @@ import Cookies from 'js-cookie'; ...@@ -10,8 +10,6 @@ import Cookies from 'js-cookie';
this.$sidebarInner = this.sidebar.find('.issuable-sidebar'); this.$sidebarInner = this.sidebar.find('.issuable-sidebar');
this.$navGitlab = $('.navbar-gitlab'); this.$navGitlab = $('.navbar-gitlab');
this.$layoutNav = $('.layout-nav');
this.$subScroll = $('.sub-nav-scroll');
this.$rightSidebar = $('.js-right-sidebar'); this.$rightSidebar = $('.js-right-sidebar');
this.removeListeners(); this.removeListeners();
...@@ -215,7 +213,7 @@ import Cookies from 'js-cookie'; ...@@ -215,7 +213,7 @@ import Cookies from 'js-cookie';
}; };
Sidebar.prototype.setSidebarHeight = function() { Sidebar.prototype.setSidebarHeight = function() {
const $navHeight = this.$navGitlab.outerHeight() + this.$layoutNav.outerHeight() + (this.$subScroll ? this.$subScroll.outerHeight() : 0); const $navHeight = this.$navGitlab.outerHeight();
const diff = $navHeight - $(window).scrollTop(); const diff = $navHeight - $(window).scrollTop();
if (diff > 0) { if (diff > 0) {
this.$rightSidebar.outerHeight($(window).height() - diff); this.$rightSidebar.outerHeight($(window).height() - diff);
......
...@@ -4,7 +4,7 @@ function expandSectionParent($section, $content) { ...@@ -4,7 +4,7 @@ function expandSectionParent($section, $content) {
} }
function expandSection($section) { function expandSection($section) {
$section.find('.js-settings-toggle').text('Close'); $section.find('.js-settings-toggle').text('Collapse');
const $content = $section.find('.settings-content'); const $content = $section.find('.settings-content');
$content.addClass('expanded').off('scroll.expandSection').scrollTop(0); $content.addClass('expanded').off('scroll.expandSection').scrollTop(0);
......
...@@ -643,7 +643,7 @@ UsersSelect.prototype.formatResult = function(user) { ...@@ -643,7 +643,7 @@ UsersSelect.prototype.formatResult = function(user) {
} else { } else {
avatar = gon.default_avatar_url; avatar = gon.default_avatar_url;
} }
return "<div class='user-result " + (!user.username ? 'no-username' : void 0) + "'> <div class='user-image'><img class='avatar s24' src='" + avatar + "'></div> <div class='user-name'>" + user.name + "</div> <div class='user-username'>" + (user.username || "") + "</div> </div>"; return "<div class='user-result " + (!user.username ? 'no-username' : void 0) + "'> <div class='user-image'><img class='avatar avatar-inline s32' src='" + avatar + "'></div> <div class='user-name dropdown-menu-user-full-name'>" + user.name + "</div> <div class='user-username dropdown-menu-user-username'>" + ("@" + user.username || "") + "</div> </div>";
}; };
UsersSelect.prototype.formatSelection = function(user) { UsersSelect.prototype.formatSelection = function(user) {
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
import ciIconBadge from './ci_badge_link.vue'; import ciIconBadge from './ci_badge_link.vue';
import loadingIcon from './loading_icon.vue'; import loadingIcon from './loading_icon.vue';
import timeagoTooltip from './time_ago_tooltip.vue'; import timeagoTooltip from './time_ago_tooltip.vue';
import tooltipMixin from '../mixins/tooltip'; import tooltip from '../directives/tooltip';
import userAvatarImage from './user_avatar/user_avatar_image.vue'; import userAvatarImage from './user_avatar/user_avatar_image.vue';
/** /**
...@@ -47,9 +47,9 @@ export default { ...@@ -47,9 +47,9 @@ export default {
}, },
}, },
mixins: [ directives: {
tooltipMixin, tooltip,
], },
components: { components: {
ciIconBadge, ciIconBadge,
...@@ -90,10 +90,10 @@ export default { ...@@ -90,10 +90,10 @@ export default {
<template v-if="user"> <template v-if="user">
<a <a
v-tooltip
:href="user.path" :href="user.path"
:title="user.email" :title="user.email"
class="js-user-link commit-committer-link" class="js-user-link commit-committer-link">
ref="tooltip">
<user-avatar-image <user-avatar-image
:img-src="user.avatar_url" :img-src="user.avatar_url"
......
<script> <script>
import tooltipMixin from '../../mixins/tooltip'; import tooltip from '../../directives/tooltip';
import toolbarButton from './toolbar_button.vue'; import toolbarButton from './toolbar_button.vue';
export default { export default {
mixins: [
tooltipMixin,
],
props: { props: {
previewMarkdown: { previewMarkdown: {
type: Boolean, type: Boolean,
required: true, required: true,
}, },
}, },
directives: {
tooltip,
},
components: { components: {
toolbarButton, toolbarButton,
}, },
...@@ -94,13 +94,13 @@ ...@@ -94,13 +94,13 @@
</div> </div>
<div class="toolbar-group"> <div class="toolbar-group">
<button <button
v-tooltip
aria-label="Go full screen" aria-label="Go full screen"
class="toolbar-btn js-zen-enter" class="toolbar-btn js-zen-enter"
data-container="body" data-container="body"
tabindex="-1" tabindex="-1"
title="Go full screen" title="Go full screen"
type="button" type="button">
ref="tooltip">
<i <i
aria-hidden="true" aria-hidden="true"
class="fa fa-arrows-alt fa-fw"> class="fa fa-arrows-alt fa-fw">
......
<script> <script>
import tooltipMixin from '../../mixins/tooltip'; import tooltip from '../../directives/tooltip';
export default { export default {
mixins: [
tooltipMixin,
],
props: { props: {
buttonTitle: { buttonTitle: {
type: String, type: String,
...@@ -29,6 +26,9 @@ ...@@ -29,6 +26,9 @@
default: false, default: false,
}, },
}, },
directives: {
tooltip,
},
computed: { computed: {
iconClass() { iconClass() {
return `fa-${this.icon}`; return `fa-${this.icon}`;
...@@ -39,10 +39,10 @@ ...@@ -39,10 +39,10 @@
<template> <template>
<button <button
v-tooltip
type="button" type="button"
class="toolbar-btn js-md hidden-xs" class="toolbar-btn js-md hidden-xs"
tabindex="-1" tabindex="-1"
ref="tooltip"
data-container="body" data-container="body"
:data-md-tag="tag" :data-md-tag="tag"
:data-md-block="tagBlock" :data-md-block="tagBlock"
......
<script> <script>
import tooltipMixin from '../mixins/tooltip'; import tooltip from '../directives/tooltip';
import timeagoMixin from '../mixins/timeago'; import timeagoMixin from '../mixins/timeago';
import '../../lib/utils/datetime_utility'; import '../../lib/utils/datetime_utility';
...@@ -28,19 +28,21 @@ export default { ...@@ -28,19 +28,21 @@ export default {
}, },
mixins: [ mixins: [
tooltipMixin,
timeagoMixin, timeagoMixin,
], ],
directives: {
tooltip,
},
}; };
</script> </script>
<template> <template>
<time <time
v-tooltip
:class="cssClass" :class="cssClass"
class="js-vue-timeago"
:title="tooltipTitle(time)" :title="tooltipTitle(time)"
:data-placement="tooltipPlacement" :data-placement="tooltipPlacement"
data-container="body" data-container="body">
ref="tooltip">
{{timeFormated(time)}} {{timeFormated(time)}}
</time> </time>
</template> </template>
...@@ -16,11 +16,10 @@ ...@@ -16,11 +16,10 @@
*/ */
import defaultAvatarUrl from 'images/no_avatar.png'; import defaultAvatarUrl from 'images/no_avatar.png';
import TooltipMixin from '../../mixins/tooltip'; import tooltip from '../../directives/tooltip';
export default { export default {
name: 'UserAvatarImage', name: 'UserAvatarImage',
mixins: [TooltipMixin],
props: { props: {
imgSrc: { imgSrc: {
type: String, type: String,
...@@ -53,6 +52,9 @@ export default { ...@@ -53,6 +52,9 @@ export default {
default: 'top', default: 'top',
}, },
}, },
directives: {
tooltip,
},
computed: { computed: {
tooltipContainer() { tooltipContainer() {
return this.tooltipText ? 'body' : null; return this.tooltipText ? 'body' : null;
...@@ -72,6 +74,7 @@ export default { ...@@ -72,6 +74,7 @@ export default {
<template> <template>
<img <img
v-tooltip
class="avatar" class="avatar"
:class="[avatarSizeClass, cssClasses]" :class="[avatarSizeClass, cssClasses]"
:src="imageSource" :src="imageSource"
...@@ -81,6 +84,5 @@ export default { ...@@ -81,6 +84,5 @@ export default {
:data-container="tooltipContainer" :data-container="tooltipContainer"
:data-placement="tooltipPlacement" :data-placement="tooltipPlacement"
:title="tooltipText" :title="tooltipText"
ref="tooltip"
/> />
</template> </template>
export default {
bind(el) {
$(el).tooltip();
},
componentUpdated(el) {
$(el).tooltip('fixTitle');
},
unbind(el) {
$(el).tooltip('destroy');
},
};
export default {
mounted() {
$(this.$refs.tooltip).tooltip();
},
updated() {
$(this.$refs.tooltip).tooltip('fixTitle');
},
beforeDestroy() {
$(this.$refs.tooltip).tooltip('destroy');
},
};
...@@ -395,6 +395,11 @@ ...@@ -395,6 +395,11 @@
.dropdown-menu-align-right { .dropdown-menu-align-right {
left: auto; left: auto;
right: 0; right: 0;
margin-top: -5px;
@media (max-width: $screen-xs-max) {
left: 0;
}
} }
.dropdown-menu-selectable { .dropdown-menu-selectable {
......
...@@ -12,6 +12,12 @@ ...@@ -12,6 +12,12 @@
&.readme-holder { &.readme-holder {
margin: $gl-padding 0; margin: $gl-padding 0;
&.limited-width-container .file-content {
max-width: $limited-layout-width-sm;
margin-left: auto;
margin-right: auto;
}
} }
table { table {
......
...@@ -346,6 +346,7 @@ header { ...@@ -346,6 +346,7 @@ header {
width: auto; width: auto;
min-width: 140px; min-width: 140px;
margin-top: -5px; margin-top: -5px;
left: auto;
.current-user { .current-user {
padding: 5px 18px; padding: 5px 18px;
......
...@@ -53,7 +53,7 @@ body { ...@@ -53,7 +53,7 @@ body {
} }
&.limit-container-width-sm { &.limit-container-width-sm {
max-width: 790px; max-width: $limited-layout-width-sm;
} }
} }
......
...@@ -161,6 +161,7 @@ $progress-color: #c0392b; ...@@ -161,6 +161,7 @@ $progress-color: #c0392b;
$header-height: 50px; $header-height: 50px;
$fixed-layout-width: 1280px; $fixed-layout-width: 1280px;
$limited-layout-width: 990px; $limited-layout-width: 990px;
$limited-layout-width-sm: 790px;
$gl-avatar-size: 40px; $gl-avatar-size: 40px;
$error-exclamation-point: $red-500; $error-exclamation-point: $red-500;
$border-radius-default: 3px; $border-radius-default: 3px;
......
...@@ -83,6 +83,7 @@ ...@@ -83,6 +83,7 @@
.avatar { .avatar {
float: none; float: none;
margin-right: 0;
} }
} }
......
...@@ -11,7 +11,9 @@ ...@@ -11,7 +11,9 @@
.commit-box, .commit-box,
.info-well, .info-well,
.commit-ci-menu, .commit-ci-menu,
.files-changed { .files-changed,
.limited-header-width,
.limited-width-notes {
@extend .fixed-width-container; @extend .fixed-width-container;
} }
......
...@@ -136,10 +136,6 @@ ...@@ -136,10 +136,6 @@
width: 250px; width: 250px;
} }
@media (min-width: $screen-md-min) {
width: 350px;
}
&.input-short { &.input-short {
@media (min-width: $screen-md-min) { @media (min-width: $screen-md-min) {
width: 170px; width: 170px;
......
...@@ -419,7 +419,7 @@ ...@@ -419,7 +419,7 @@
.commit { .commit {
margin: 0; margin: 0;
padding: 10px 0; padding: 10px;
list-style: none; list-style: none;
&:hover { &:hover {
......
...@@ -133,7 +133,7 @@ ...@@ -133,7 +133,7 @@
overflow: hidden; overflow: hidden;
display: inline-block; display: inline-block;
white-space: nowrap; white-space: nowrap;
vertical-align: top; vertical-align: middle;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
......
...@@ -126,3 +126,66 @@ ...@@ -126,3 +126,66 @@
margin-left: 5px; margin-left: 5px;
} }
} }
.prometheus-metrics-monitoring {
.panel {
.panel-toggle {
width: 14px;
}
.badge {
font-size: inherit;
}
.panel-heading .badge-count {
color: $white-light;
background: $common-gray-dark;
}
.panel-body {
padding: 0;
}
.flash-container {
margin-bottom: 0;
cursor: default;
.flash-notice {
border-radius: 0;
}
}
}
.loading-metrics,
.empty-metrics {
padding: 30px 10px;
p,
.btn {
margin-top: 10px;
margin-bottom: 0;
}
}
.loading-metrics .metrics-load-spinner {
color: $loading-color;
}
.metrics-list {
margin-bottom: 0;
li {
padding: $gl-padding;
.badge {
margin-left: 5px;
background: $badge-bg;
}
}
/* Ensure we don't add border if there's only single li */
li + li {
border-top: 1px solid $border-color;
}
}
}
...@@ -40,6 +40,10 @@ class ApplicationController < ActionController::Base ...@@ -40,6 +40,10 @@ class ApplicationController < ActionController::Base
render_404 render_404
end end
rescue_from(ActionController::UnknownFormat) do
render_404
end
rescue_from Gitlab::Access::AccessDeniedError do |exception| rescue_from Gitlab::Access::AccessDeniedError do |exception|
render_403 render_403
end end
......
...@@ -22,6 +22,22 @@ class Projects::DeploymentsController < Projects::ApplicationController ...@@ -22,6 +22,22 @@ class Projects::DeploymentsController < Projects::ApplicationController
render_404 render_404
end end
def additional_metrics
return render_404 unless deployment.has_additional_metrics?
respond_to do |format|
format.json do
metrics = deployment.additional_metrics
if metrics.any?
render json: metrics
else
head :no_content
end
end
end
end
private private
def deployment def deployment
......
...@@ -129,6 +129,16 @@ class Projects::EnvironmentsController < Projects::ApplicationController ...@@ -129,6 +129,16 @@ class Projects::EnvironmentsController < Projects::ApplicationController
end end
end end
def additional_metrics
respond_to do |format|
format.json do
additional_metrics = environment.additional_metrics || {}
render json: additional_metrics, status: additional_metrics.any? ? :ok : :no_content
end
end
end
private private
def verify_api_request! def verify_api_request!
......
...@@ -575,10 +575,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -575,10 +575,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def merge_request_params def merge_request_params
params.require(:merge_request) params.require(:merge_request)
.permit(merge_request_params_ce) .permit(merge_request_params_attributes)
end end
def merge_request_params_ce def merge_request_params_attributes
[ [
:assignee_id, :assignee_id,
:description, :description,
...@@ -598,7 +598,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -598,7 +598,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end end
def merge_params def merge_params
params.permit(:should_remove_source_branch, :commit_message) params.permit(merge_params_attributes)
end
def merge_params_attributes
[:should_remove_source_branch, :commit_message]
end end
# Make sure merge requests created before 8.0 # Make sure merge requests created before 8.0
......
...@@ -135,7 +135,12 @@ class Projects::PipelinesController < Projects::ApplicationController ...@@ -135,7 +135,12 @@ class Projects::PipelinesController < Projects::ApplicationController
@charts[:week] = Ci::Charts::WeekChart.new(project) @charts[:week] = Ci::Charts::WeekChart.new(project)
@charts[:month] = Ci::Charts::MonthChart.new(project) @charts[:month] = Ci::Charts::MonthChart.new(project)
@charts[:year] = Ci::Charts::YearChart.new(project) @charts[:year] = Ci::Charts::YearChart.new(project)
@charts[:build_times] = Ci::Charts::BuildTime.new(project) @charts[:pipeline_times] = Ci::Charts::PipelineTime.new(project)
@counts = {}
@counts[:total] = @project.pipelines.count(:all)
@counts[:success] = @project.pipelines.success.count(:all)
@counts[:failed] = @project.pipelines.failed.count(:all)
end end
private private
......
class Projects::PrometheusController < Projects::ApplicationController
before_action :authorize_read_project!
before_action :require_prometheus_metrics!
def active_metrics
respond_to do |format|
format.json do
matched_metrics = project.prometheus_service.matched_metrics || {}
if matched_metrics.any?
render json: matched_metrics
else
head :no_content
end
end
end
end
private
def require_prometheus_metrics!
render_404 unless project.prometheus_service.present?
end
end
...@@ -41,7 +41,7 @@ class IssuesFinder < IssuableFinder ...@@ -41,7 +41,7 @@ class IssuesFinder < IssuableFinder
def self.not_restricted_by_confidentiality(user) def self.not_restricted_by_confidentiality(user)
return Issue.where('issues.confidential IS NOT TRUE') if user.blank? return Issue.where('issues.confidential IS NOT TRUE') if user.blank?
return Issue.all if user.admin? return Issue.all if user.full_private_access?
Issue.where(' Issue.where('
issues.confidential IS NOT TRUE issues.confidential IS NOT TRUE
......
...@@ -85,20 +85,20 @@ module CommitsHelper ...@@ -85,20 +85,20 @@ module CommitsHelper
if @path.blank? if @path.blank?
return link_to( return link_to(
"Browse Files", _("Browse Files"),
namespace_project_tree_path(project.namespace, project, commit), namespace_project_tree_path(project.namespace, project, commit),
class: "btn btn-default" class: "btn btn-default"
) )
elsif @repo.blob_at(commit.id, @path) elsif @repo.blob_at(commit.id, @path)
return link_to( return link_to(
"Browse File", _("Browse File"),
namespace_project_blob_path(project.namespace, project, namespace_project_blob_path(project.namespace, project,
tree_join(commit.id, @path)), tree_join(commit.id, @path)),
class: "btn btn-default" class: "btn btn-default"
) )
elsif @path.present? elsif @path.present?
return link_to( return link_to(
"Browse Directory", _("Browse Directory"),
namespace_project_tree_path(project.namespace, project, namespace_project_tree_path(project.namespace, project,
tree_join(commit.id, @path)), tree_join(commit.id, @path)),
class: "btn btn-default" class: "btn btn-default"
......
...@@ -17,13 +17,10 @@ module GraphHelper ...@@ -17,13 +17,10 @@ module GraphHelper
ids.zip(parent_spaces) ids.zip(parent_spaces)
end end
def success_ratio(success_builds, failed_builds) def success_ratio(counts)
failed_builds = failed_builds.count(:all) return 100 if counts[:failed].zero?
success_builds = success_builds.count(:all)
return 100 if failed_builds.zero? ratio = (counts[:success].to_f / (counts[:success] + counts[:failed])) * 100
ratio = (success_builds.to_f / (success_builds + failed_builds)) * 100
ratio.to_i ratio.to_i
end end
end end
...@@ -15,7 +15,7 @@ module GroupsHelper ...@@ -15,7 +15,7 @@ module GroupsHelper
@has_group_title = true @has_group_title = true
full_title = '' full_title = ''
group.ancestors.each do |parent| group.ancestors.reverse.each do |parent|
full_title += link_to(simple_sanitize(parent.name), group_path(parent), class: 'group-path hidable') full_title += link_to(simple_sanitize(parent.name), group_path(parent), class: 'group-path hidable')
full_title += '<span class="hidable"> / </span>'.html_safe full_title += '<span class="hidable"> / </span>'.html_safe
end end
......
...@@ -4,4 +4,14 @@ module UsersHelper ...@@ -4,4 +4,14 @@ module UsersHelper
title: user.email, title: user.email,
class: 'has-tooltip commit-committer-link') class: 'has-tooltip commit-committer-link')
end end
def user_email_help_text(user)
return 'We also use email for avatar detection if no avatar is uploaded.' unless user.unconfirmed_email.present?
confirmation_link = link_to 'Resend confirmation e-mail', user_confirmation_path(user: { email: @user.unconfirmed_email }), method: :post
h('Please click the link in the confirmation email before continuing. It was sent to ') +
content_tag(:strong) { user.unconfirmed_email } + h('.') +
content_tag(:p) { confirmation_link }
end
end end
...@@ -114,6 +114,17 @@ class Deployment < ActiveRecord::Base ...@@ -114,6 +114,17 @@ class Deployment < ActiveRecord::Base
project.monitoring_service.deployment_metrics(self) project.monitoring_service.deployment_metrics(self)
end end
def has_additional_metrics?
project.prometheus_service.present?
end
def additional_metrics
return {} unless project.prometheus_service.present?
metrics = project.prometheus_service.additional_deployment_metrics(self)
metrics&.merge(deployment_time: created_at.to_i) || {}
end
private private
def ref_path def ref_path
......
...@@ -157,6 +157,16 @@ class Environment < ActiveRecord::Base ...@@ -157,6 +157,16 @@ class Environment < ActiveRecord::Base
project.monitoring_service.environment_metrics(self) if has_metrics? project.monitoring_service.environment_metrics(self) if has_metrics?
end end
def has_additional_metrics?
project.prometheus_service.present? && available? && last_deployment.present?
end
def additional_metrics
if has_additional_metrics?
project.prometheus_service.additional_environment_metrics(self)
end
end
# An environment name is not necessarily suitable for use in URLs, DNS # An environment name is not necessarily suitable for use in URLs, DNS
# or other third-party contexts, so provide a slugified version. A slug has # or other third-party contexts, so provide a slugified version. A slug has
# the following properties: # the following properties:
......
...@@ -350,7 +350,10 @@ class Project < ActiveRecord::Base ...@@ -350,7 +350,10 @@ class Project < ActiveRecord::Base
project.run_after_commit { add_import_job } project.run_after_commit { add_import_job }
end end
after_transition started: :finished, do: :reset_cache_and_import_attrs after_transition started: :finished do |project, _|
project.reset_cache_and_import_attrs
project.perform_housekeeping
end
end end
class << self class << self
...@@ -510,6 +513,18 @@ class Project < ActiveRecord::Base ...@@ -510,6 +513,18 @@ class Project < ActiveRecord::Base
remove_import_data remove_import_data
end end
def perform_housekeeping
return unless repo_exists?
run_after_commit do
begin
Projects::HousekeepingService.new(self).execute
rescue Projects::HousekeepingService::LeaseTaken => e
Rails.logger.info("Could not perform housekeeping for project #{self.path_with_namespace} (#{self.id}): #{e}")
end
end
end
def remove_import_data def remove_import_data
import_data&.destroy import_data&.destroy
end end
......
...@@ -90,7 +90,7 @@ class ProjectFeature < ActiveRecord::Base ...@@ -90,7 +90,7 @@ class ProjectFeature < ActiveRecord::Base
when DISABLED when DISABLED
false false
when PRIVATE when PRIVATE
user && (project.team.member?(user) || user.admin?) user && (project.team.member?(user) || user.full_private_access?)
when ENABLED when ENABLED
true true
else else
......
...@@ -28,17 +28,6 @@ class PrometheusService < MonitoringService ...@@ -28,17 +28,6 @@ class PrometheusService < MonitoringService
'Prometheus monitoring' 'Prometheus monitoring'
end end
def help
<<-MD.strip_heredoc
Retrieves the Kubernetes node metrics `container_cpu_usage_seconds_total`
and `container_memory_usage_bytes` from the configured Prometheus server.
If you are not using [Auto-Deploy](https://docs.gitlab.com/ee/ci/autodeploy/index.html)
or have set up your own Prometheus server, an `environment` label is required on each metric to
[identify the Environment](https://docs.gitlab.com/ce/user/project/integrations/prometheus.html#metrics-and-labels).
MD
end
def self.to_param def self.to_param
'prometheus' 'prometheus'
end end
...@@ -50,6 +39,7 @@ class PrometheusService < MonitoringService ...@@ -50,6 +39,7 @@ class PrometheusService < MonitoringService
name: 'api_url', name: 'api_url',
title: 'API URL', title: 'API URL',
placeholder: 'Prometheus API Base URL, like http://prometheus.example.com/', placeholder: 'Prometheus API Base URL, like http://prometheus.example.com/',
help: 'By default, Prometheus listens on ‘http://localhost:9090’. It’s not recommended to change the default address and port as this might affect or conflict with other services running on the GitLab server.',
required: true required: true
} }
] ]
...@@ -65,23 +55,34 @@ class PrometheusService < MonitoringService ...@@ -65,23 +55,34 @@ class PrometheusService < MonitoringService
end end
def environment_metrics(environment) def environment_metrics(environment)
with_reactive_cache(Gitlab::Prometheus::Queries::EnvironmentQuery.name, environment.id, &:itself) with_reactive_cache(Gitlab::Prometheus::Queries::EnvironmentQuery.name, environment.id, &method(:rename_data_to_metrics))
end end
def deployment_metrics(deployment) def deployment_metrics(deployment)
metrics = with_reactive_cache(Gitlab::Prometheus::Queries::DeploymentQuery.name, deployment.id, &:itself) metrics = with_reactive_cache(Gitlab::Prometheus::Queries::DeploymentQuery.name, deployment.id, &method(:rename_data_to_metrics))
metrics&.merge(deployment_time: created_at.to_i) || {} metrics&.merge(deployment_time: created_at.to_i) || {}
end end
def additional_environment_metrics(environment)
with_reactive_cache(Gitlab::Prometheus::Queries::AdditionalMetricsEnvironmentQuery.name, environment.id, &:itself)
end
def additional_deployment_metrics(deployment)
with_reactive_cache(Gitlab::Prometheus::Queries::AdditionalMetricsDeploymentQuery.name, deployment.id, &:itself)
end
def matched_metrics
with_reactive_cache(Gitlab::Prometheus::Queries::MatchedMetricsQuery.name, &:itself)
end
# Cache metrics for specific environment # Cache metrics for specific environment
def calculate_reactive_cache(query_class_name, *args) def calculate_reactive_cache(query_class_name, *args)
return unless active? && project && !project.pending_delete? return unless active? && project && !project.pending_delete?
metrics = Kernel.const_get(query_class_name).new(client).query(*args) data = Kernel.const_get(query_class_name).new(client).query(*args)
{ {
success: true, success: true,
metrics: metrics, data: data,
last_update: Time.now.utc last_update: Time.now.utc
} }
rescue Gitlab::PrometheusError => err rescue Gitlab::PrometheusError => err
...@@ -91,4 +92,11 @@ class PrometheusService < MonitoringService ...@@ -91,4 +92,11 @@ class PrometheusService < MonitoringService
def client def client
@prometheus ||= Gitlab::PrometheusClient.new(api_url: api_url) @prometheus ||= Gitlab::PrometheusClient.new(api_url: api_url)
end end
private
def rename_data_to_metrics(metrics)
metrics[:metrics] = metrics.delete :data
metrics
end
end end
...@@ -984,6 +984,12 @@ class User < ActiveRecord::Base ...@@ -984,6 +984,12 @@ class User < ActiveRecord::Base
self.admin = (new_level == 'admin') self.admin = (new_level == 'admin')
end end
# Does the user have access to all private groups & projects?
# Overridden in EE to also check auditor?
def full_private_access?
admin?
end
def update_two_factor_requirement def update_two_factor_requirement
periods = expanded_groups_requiring_two_factor_authentication.pluck(:two_factor_grace_period) periods = expanded_groups_requiring_two_factor_authentication.pluck(:two_factor_grace_period)
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
.avatar-container.s70.group-avatar .avatar-container.s70.group-avatar
= image_tag group_icon(@group), class: "avatar s70 avatar-tile" = image_tag group_icon(@group), class: "avatar s70 avatar-tile"
%h1.group-title %h1.group-title
@#{@group.path} = @group.name
%span.visibility-icon.has-tooltip{ data: { container: 'body' }, title: visibility_icon_description(@group) } %span.visibility-icon.has-tooltip{ data: { container: 'body' }, title: visibility_icon_description(@group) }
= visibility_level_icon(@group.visibility_level, fw: false) = visibility_level_icon(@group.visibility_level, fw: false)
......
...@@ -18,5 +18,4 @@ ...@@ -18,5 +18,4 @@
- if current_user - if current_user
To see all merge requests you should visit #{link_to 'dashboard', merge_requests_dashboard_path} page. To see all merge requests you should visit #{link_to 'dashboard', merge_requests_dashboard_path} page.
.prepend-top-default = render 'shared/merge_requests'
= render 'shared/merge_requests'
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
= icon('caret-down') = icon('caret-down')
.dropdown-menu-nav.dropdown-menu-align-right .dropdown-menu-nav.dropdown-menu-align-right
%ul %ul
- if @group - if @group&.persisted?
- create_group_project = can?(current_user, :create_projects, @group) - create_group_project = can?(current_user, :create_projects, @group)
- create_group_subgroup = can?(current_user, :create_subgroup, @group) - create_group_subgroup = can?(current_user, :create_subgroup, @group)
- if create_group_project || create_group_subgroup - if create_group_project || create_group_subgroup
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
%li.divider %li.divider
%li.dropdown-bold-header GitLab %li.dropdown-bold-header GitLab
- if @project && @project.persisted? - if @project&.persisted?
- create_project_issue = can?(current_user, :create_issue, @project) - create_project_issue = can?(current_user, :create_issue, @project)
- merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project)) - merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project))
- create_project_snippet = can?(current_user, :create_project_snippet, @project) - create_project_snippet = can?(current_user, :create_project_snippet, @project)
......
= render 'profiles/head' = render 'profiles/head'
= form_for @user, url: profile_path, method: :put, html: { multipart: true, class: "edit-user prepend-top-default" }, authenticity_token: true do |f| = bootstrap_form_for @user, url: profile_path, method: :put, html: { multipart: true, class: 'edit-user prepend-top-default' }, authenticity_token: true do |f|
= form_errors(@user) = form_errors(@user)
.row .row
...@@ -11,11 +11,11 @@ ...@@ -11,11 +11,11 @@
- if @user.avatar? - if @user.avatar?
You can change your avatar here You can change your avatar here
- if gravatar_enabled? - if gravatar_enabled?
or remove the current avatar to revert to #{link_to Gitlab.config.gravatar.host, "http://" + Gitlab.config.gravatar.host} or remove the current avatar to revert to #{link_to Gitlab.config.gravatar.host, 'http://' + Gitlab.config.gravatar.host}
- else - else
You can upload an avatar here You can upload an avatar here
- if gravatar_enabled? - if gravatar_enabled?
or change it at #{link_to Gitlab.config.gravatar.host, "http://" + Gitlab.config.gravatar.host} or change it at #{link_to Gitlab.config.gravatar.host, 'http://' + Gitlab.config.gravatar.host}
.col-lg-9 .col-lg-9
.clearfix.avatar-image.append-bottom-default .clearfix.avatar-image.append-bottom-default
= link_to avatar_icon(@user, 400), target: '_blank', rel: 'noopener noreferrer' do = link_to avatar_icon(@user, 400), target: '_blank', rel: 'noopener noreferrer' do
...@@ -26,12 +26,12 @@ ...@@ -26,12 +26,12 @@
%a.btn.js-choose-user-avatar-button %a.btn.js-choose-user-avatar-button
Browse file... Browse file...
%span.avatar-file-name.prepend-left-default.js-avatar-filename No file chosen %span.avatar-file-name.prepend-left-default.js-avatar-filename No file chosen
= f.file_field :avatar, class: "js-user-avatar-input hidden", accept: "image/*" = f.file_field_without_bootstrap :avatar, class: 'js-user-avatar-input hidden', accept: 'image/*'
.help-block .help-block
The maximum file size allowed is 200KB. The maximum file size allowed is 200KB.
- if @user.avatar? - if @user.avatar?
%hr %hr
= link_to 'Remove avatar', profile_avatar_path, data: { confirm: "Avatar will be removed. Are you sure?" }, method: :delete, class: "btn btn-gray" = link_to 'Remove avatar', profile_avatar_path, data: { confirm: 'Avatar will be removed. Are you sure?' }, method: :delete, class: 'btn btn-gray'
%hr %hr
.row .row
.col-lg-3.profile-settings-sidebar .col-lg-3.profile-settings-sidebar
...@@ -43,91 +43,50 @@ ...@@ -43,91 +43,50 @@
Some options are unavailable for LDAP accounts Some options are unavailable for LDAP accounts
.col-lg-9 .col-lg-9
.row .row
.form-group.col-md-9 = f.text_field :name, required: true, wrapper: { class: 'col-md-9' },
= f.label :name, class: "label-light" help: 'Enter your name, so people you know can recognize you.'
= f.text_field :name, class: "form-control", required: true = f.text_field :id, readonly: true, label: 'User ID', wrapper: { class: 'col-md-3' }
%span.help-block Enter your name, so people you know can recognize you.
.form-group.col-md-3 - if @user.external_email?
= f.label :id, class: 'label-light' do = f.text_field :email, required: true, readonly: true, help: "Your email address was automatically set based on your #{email_provider_label} account."
User ID - else
= f.text_field :id, class: 'form-control', readonly: true = f.text_field :email, required: true, value: (@user.email unless @user.temp_oauth_email?),
help: user_email_help_text(@user)
= f.select :public_email, options_for_select(@user.all_emails, selected: @user.public_email),
.form-group { help: 'This email will be displayed on your public profile.', include_blank: 'Do not show on profile' },
= f.label :email, class: "label-light" control_class: 'select2'
- if @user.external_email? = f.select :preferred_language, Gitlab::I18n::AVAILABLE_LANGUAGES.map { |value, label| [label, value] },
= f.text_field :email, class: "form-control", required: true, readonly: true { help: 'This feature is experimental and translations are not complete yet.' },
%span.help-block.light control_class: 'select2'
Your email address was automatically set based on your #{email_provider_label} account. = f.text_field :skype
- else = f.text_field :linkedin
- if @user.temp_oauth_email? = f.text_field :twitter
= f.text_field :email, class: "form-control", required: true, value: nil = f.text_field :website_url, label: 'Website'
- else = f.text_field :location
= f.text_field :email, class: "form-control", required: true = f.text_field :organization
- if @user.unconfirmed_email.present? = f.text_area :bio, rows: 4, maxlength: 250, help: 'Tell us about yourself in fewer than 250 characters.'
%span.help-block
Please click the link in the confirmation email before continuing. It was sent to
= succeed "." do
%strong= @user.unconfirmed_email
%p
= link_to "Resend confirmation e-mail", user_confirmation_path(user: { email: @user.unconfirmed_email }), method: :post
- else
%span.help-block We also use email for avatar detection if no avatar is uploaded.
.form-group
= f.label :public_email, class: "label-light"
= f.select :public_email, options_for_select(@user.all_emails, selected: @user.public_email), { include_blank: 'Do not show on profile' }, class: "select2"
%span.help-block This email will be displayed on your public profile.
.form-group
= f.label :preferred_language, class: "label-light"
= f.select :preferred_language, Gitlab::I18n::AVAILABLE_LANGUAGES.map { |value, label| [label, value] },
{}, class: "select2"
%span.help-block This feature is experimental and translations are not complete yet.
.form-group
= f.label :skype, class: "label-light"
= f.text_field :skype, class: "form-control"
.form-group
= f.label :linkedin, class: "label-light"
= f.text_field :linkedin, class: "form-control"
.form-group
= f.label :twitter, class: "label-light"
= f.text_field :twitter, class: "form-control"
.form-group
= f.label :website_url, 'Website', class: "label-light"
= f.text_field :website_url, class: "form-control"
.form-group
= f.label :location, 'Location', class: "label-light"
= f.text_field :location, class: "form-control"
.form-group
= f.label :organization, 'Organization', class: "label-light"
= f.text_field :organization, class: "form-control"
.form-group
= f.label :bio, class: "label-light"
= f.text_area :bio, rows: 4, class: "form-control", maxlength: 250
%span.help-block Tell us about yourself in fewer than 250 characters.
.prepend-top-default.append-bottom-default .prepend-top-default.append-bottom-default
= f.submit 'Update profile settings', class: "btn btn-success" = f.submit 'Update profile settings', class: 'btn btn-success'
= link_to "Cancel", user_path(current_user), class: "btn btn-cancel" = link_to 'Cancel', user_path(current_user), class: 'btn btn-cancel'
.modal.modal-profile-crop .modal.modal-profile-crop
.modal-dialog .modal-dialog
.modal-content .modal-content
.modal-header .modal-header
%button.close{ :type => "button", :'data-dismiss' => "modal" } %button.close{ type: 'button', 'data-dismiss': 'modal' }
%span %span
&times; &times;
%h4.modal-title %h4.modal-title
Position and size your new avatar Position and size your new avatar
.modal-body .modal-body
.profile-crop-image-container .profile-crop-image-container
%img.modal-profile-crop-image{ alt: "Avatar cropper" } %img.modal-profile-crop-image{ alt: 'Avatar cropper' }
.crop-controls .crop-controls
.btn-group .btn-group
%button.btn.btn-primary{ data: { method: "zoom", option: "0.1" } } %button.btn.btn-primary{ data: { method: 'zoom', option: '0.1' } }
%span.fa.fa-search-plus %span.fa.fa-search-plus
%button.btn.btn-primary{ data: { method: "zoom", option: "-0.1" } } %button.btn.btn-primary{ data: { method: 'zoom', option: '-0.1' } }
%span.fa.fa-search-minus %span.fa.fa-search-minus
.modal-footer .modal-footer
%button.btn.btn-primary.js-upload-user-avatar{ :type => "button" } %button.btn.btn-primary.js-upload-user-avatar{ type: 'button' }
Set new profile picture Set new profile picture
- @no_container = true - @no_container = true
- container_class = !fluid_layout && diff_view == :inline ? 'container-limited' : '' - container_class = !fluid_layout && diff_view == :inline ? 'container-limited' : ''
- limited_container_width = fluid_layout || diff_view == :inline ? '' : 'limit-container-width' - limited_container_width = fluid_layout ? '' : 'limit-container-width'
- page_title "#{@commit.title} (#{@commit.short_id})", "Commits" - page_title "#{@commit.title} (#{@commit.short_id})", "Commits"
- page_description @commit.description - page_description @commit.description
= render "projects/commits/head" = render "projects/commits/head"
...@@ -13,7 +13,8 @@ ...@@ -13,7 +13,8 @@
.block-connector .block-connector
= render "projects/diffs/diffs", diffs: @diffs, environment: @environment = render "projects/diffs/diffs", diffs: @diffs, environment: @environment
= render "shared/notes/notes_with_form", :autocomplete => true .limited-width-notes
- if can_collaborate_with_project? = render "shared/notes/notes_with_form", :autocomplete => true
- %w(revert cherry-pick).each do |type| - if can_collaborate_with_project?
= render "projects/commit/change", type: type, commit: @commit, title: @commit.title - %w(revert cherry-pick).each do |type|
= render "projects/commit/change", type: type, commit: @commit, title: @commit.title
...@@ -3,8 +3,8 @@ ...@@ -3,8 +3,8 @@
- commits.chunk { |c| c.committed_date.in_time_zone.to_date }.each do |day, commits| - commits.chunk { |c| c.committed_date.in_time_zone.to_date }.each do |day, commits|
%li.commit-header.js-commit-header{ data: { day: day } } %li.commit-header.js-commit-header{ data: { day: day } }
%span.day= day.strftime('%d %b, %Y') %span.day= l(day, format: '%d %b, %Y')
%span.commits-count= pluralize(commits.count, 'commit') %span.commits-count= n_("%d commit", "%d commits", commits.count) % commits.count
%li.commits-row{ data: { day: day } } %li.commits-row{ data: { day: day } }
%ul.content-list.commit-list %ul.content-list.commit-list
...@@ -12,4 +12,4 @@ ...@@ -12,4 +12,4 @@
- if hidden > 0 - if hidden > 0
%li.alert.alert-warning %li.alert.alert-warning
#{number_with_delimiter(hidden)} additional commits have been omitted to prevent performance issues. = n_('%d additional commit has been omitted to prevent performance issues.', '%d additional commits have been omitted to prevent performance issues.', hidden) % number_with_delimiter(hidden)
- @no_container = true - @no_container = true
- page_title "Commits", @ref - page_title _("Commits"), @ref
= content_for :meta_tags do = content_for :meta_tags do
= auto_discovery_link_tag(:atom, namespace_project_commits_url(@project.namespace, @project, @ref, rss_url_options), title: "#{@project.name}:#{@ref} commits") = auto_discovery_link_tag(:atom, namespace_project_commits_url(@project.namespace, @project, @ref, rss_url_options), title: "#{@project.name}:#{@ref} commits")
...@@ -18,16 +18,16 @@ ...@@ -18,16 +18,16 @@
.block-controls.hidden-xs.hidden-sm .block-controls.hidden-xs.hidden-sm
- if @merge_request.present? - if @merge_request.present?
.control .control
= link_to "View open merge request", namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'btn' = link_to _("View open merge request"), namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'btn'
- elsif create_mr_button?(@repository.root_ref, @ref) - elsif create_mr_button?(@repository.root_ref, @ref)
.control .control
= link_to "Create merge request", create_mr_path(@repository.root_ref, @ref), class: 'btn btn-success' = link_to _("Create merge request"), create_mr_path(@repository.root_ref, @ref), class: 'btn btn-success'
.control .control
= form_tag(namespace_project_commits_path(@project.namespace, @project, @id), method: :get, class: 'commits-search-form') do = form_tag(namespace_project_commits_path(@project.namespace, @project, @id), method: :get, class: 'commits-search-form') do
= search_field_tag :search, params[:search], { placeholder: 'Filter by commit message', id: 'commits-search', class: 'form-control search-text-input input-short', spellcheck: false } = search_field_tag :search, params[:search], { placeholder: _('Filter by commit message'), id: 'commits-search', class: 'form-control search-text-input input-short', spellcheck: false }
.control .control
= link_to namespace_project_commits_path(@project.namespace, @project, @ref, rss_url_options), title: "Commits feed", class: 'btn' do = link_to namespace_project_commits_path(@project.namespace, @project, @ref, rss_url_options), title: _("Commits feed"), class: 'btn' do
= icon("rss") = icon("rss")
%div{ id: dom_id(@project) } %div{ id: dom_id(@project) }
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
%h4 %h4
Deploy Keys Deploy Keys
%button.btn.js-settings-toggle %button.btn.js-settings-toggle
= expanded ? 'Close' : 'Expand' = expanded ? 'Collapse' : 'Expand'
%p %p
Deploy keys allow read-only or read-write (if enabled) access to your repository. Deploy keys can be used for CI, staging or production servers. You can create a deploy key or add an existing one. Deploy keys allow read-only or read-write (if enabled) access to your repository. Deploy keys can be used for CI, staging or production servers. You can create a deploy key or add an existing one.
.settings-content.no-animate{ class: ('expanded' if expanded) } .settings-content.no-animate{ class: ('expanded' if expanded) }
......
...@@ -3,24 +3,25 @@ ...@@ -3,24 +3,25 @@
.table-mobile-header{ role: 'rowheader' } ID .table-mobile-header{ role: 'rowheader' } ID
%strong.table-mobile-content ##{deployment.iid} %strong.table-mobile-content ##{deployment.iid}
.table-section.section-40{ role: 'gridcell' } .table-section.section-30{ role: 'gridcell' }
.table-mobile-header{ role: 'rowheader' } Commit .table-mobile-header{ role: 'rowheader' } Commit
= render 'projects/deployments/commit', deployment: deployment = render 'projects/deployments/commit', deployment: deployment
.table-section.section-15.build-column{ role: 'gridcell' } .table-section.section-25.build-column{ role: 'gridcell' }
.table-mobile-header{ role: 'rowheader' } Job .table-mobile-header{ role: 'rowheader' } Job
- if deployment.deployable - if deployment.deployable
= link_to [@project.namespace.becomes(Namespace), @project, deployment.deployable], class: 'build-link table-mobile-content' do .table-mobile-content
#{deployment.deployable.name} (##{deployment.deployable.id}) = link_to [@project.namespace.becomes(Namespace), @project, deployment.deployable], class: 'build-link' do
- if deployment.user #{deployment.deployable.name} (##{deployment.deployable.id})
by - if deployment.user
= user_avatar(user: deployment.user, size: 20) by
= user_avatar(user: deployment.user, size: 20)
.table-section.section-15{ role: 'gridcell' } .table-section.section-15{ role: 'gridcell' }
.table-mobile-header{ role: 'rowheader' } Created .table-mobile-header{ role: 'rowheader' } Created
%span.table-mobile-content= time_ago_with_tooltip(deployment.created_at) %span.table-mobile-content= time_ago_with_tooltip(deployment.created_at)
.table-section.section-20.table-button-footer{ role: 'gridcell' } .table-section.section-20.table-button-footer{ role: 'gridcell' }
.btn-group.table-action-button .btn-group.table-action-buttons
= render 'projects/deployments/actions', deployment: deployment = render 'projects/deployments/actions', deployment: deployment
= render 'projects/deployments/rollback', deployment: deployment = render 'projects/deployments/rollback', deployment: deployment
- @content_class = "limit-container-width" unless fluid_layout
= render "projects/settings/head" = render "projects/settings/head"
.project-edit-container .project-edit-container
.row.prepend-top-default .row.prepend-top-default
.col-lg-3.profile-settings-sidebar .col-lg-4.profile-settings-sidebar
%h4.prepend-top-0 %h4.prepend-top-0
Project settings Project settings
.col-lg-9 .col-lg-8
.project-edit-errors .project-edit-errors
= form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "edit-project" }, authenticity_token: true do |f| = form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "edit-project" }, authenticity_token: true do |f|
%fieldset %fieldset
...@@ -39,66 +41,66 @@ ...@@ -39,66 +41,66 @@
Sharing &amp; Permissions Sharing &amp; Permissions
.form_group.prepend-top-20.sharing-and-permissions .form_group.prepend-top-20.sharing-and-permissions
.row.js-visibility-select .row.js-visibility-select
.col-md-9 .col-md-8
.label-light .label-light
= label_tag :project_visibility, 'Project Visibility', class: 'label-light', for: :project_visibility_level = label_tag :project_visibility, 'Project Visibility', class: 'label-light', for: :project_visibility_level
= link_to icon('question-circle'), help_page_path("public_access/public_access") = link_to icon('question-circle'), help_page_path("public_access/public_access")
%span.help-block %span.help-block
.col-md-3.visibility-select-container .col-md-4.visibility-select-container
= render('projects/visibility_select', model_method: :visibility_level, form: f, selected_level: @project.visibility_level) = render('projects/visibility_select', model_method: :visibility_level, form: f, selected_level: @project.visibility_level)
= f.fields_for :project_feature do |feature_fields| = f.fields_for :project_feature do |feature_fields|
%fieldset.features %fieldset.features
.row .row
.col-md-9.project-feature .col-md-8.project-feature
= feature_fields.label :repository_access_level, "Repository", class: 'label-light' = feature_fields.label :repository_access_level, "Repository", class: 'label-light'
%span.help-block View and edit files in this project %span.help-block View and edit files in this project
.col-md-3.js-repo-access-level .col-md-4.js-repo-access-level
= project_feature_access_select(:repository_access_level) = project_feature_access_select(:repository_access_level)
.row .row
.col-md-9.project-feature.nested .col-md-8.project-feature.nested
= feature_fields.label :merge_requests_access_level, "Merge requests", class: 'label-light' = feature_fields.label :merge_requests_access_level, "Merge requests", class: 'label-light'
%span.help-block Submit changes to be merged upstream %span.help-block Submit changes to be merged upstream
.col-md-3 .col-md-4
= project_feature_access_select(:merge_requests_access_level) = project_feature_access_select(:merge_requests_access_level)
.row .row
.col-md-9.project-feature.nested .col-md-8.project-feature.nested
= feature_fields.label :builds_access_level, "Pipelines", class: 'label-light' = feature_fields.label :builds_access_level, "Pipelines", class: 'label-light'
%span.help-block Build, test, and deploy your changes %span.help-block Build, test, and deploy your changes
.col-md-3 .col-md-4
= project_feature_access_select(:builds_access_level) = project_feature_access_select(:builds_access_level)
.row .row
.col-md-9.project-feature .col-md-8.project-feature
= feature_fields.label :snippets_access_level, "Snippets", class: 'label-light' = feature_fields.label :snippets_access_level, "Snippets", class: 'label-light'
%span.help-block Share code pastes with others out of Git repository %span.help-block Share code pastes with others out of Git repository
.col-md-3 .col-md-4
= project_feature_access_select(:snippets_access_level) = project_feature_access_select(:snippets_access_level)
.row .row
.col-md-9.project-feature .col-md-8.project-feature
= feature_fields.label :issues_access_level, "Issues", class: 'label-light' = feature_fields.label :issues_access_level, "Issues", class: 'label-light'
%span.help-block Lightweight issue tracking system for this project %span.help-block Lightweight issue tracking system for this project
.col-md-3 .col-md-4
= project_feature_access_select(:issues_access_level) = project_feature_access_select(:issues_access_level)
.row .row
.col-md-9.project-feature .col-md-8.project-feature
= feature_fields.label :wiki_access_level, "Wiki", class: 'label-light' = feature_fields.label :wiki_access_level, "Wiki", class: 'label-light'
%span.help-block Pages for project documentation %span.help-block Pages for project documentation
.col-md-3 .col-md-4
= project_feature_access_select(:wiki_access_level) = project_feature_access_select(:wiki_access_level)
.form-group .form-group
= render 'shared/allow_request_access', form: f = render 'shared/allow_request_access', form: f
- if Gitlab.config.lfs.enabled && current_user.admin? - if Gitlab.config.lfs.enabled && current_user.admin?
.row.js-lfs-enabled .row.js-lfs-enabled
.col-md-9 .col-md-8
= f.label :lfs_enabled, 'LFS', class: 'label-light' = f.label :lfs_enabled, 'LFS', class: 'label-light'
%span.help-block %span.help-block
Git Large File Storage Git Large File Storage
= link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs') = link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs')
.col-md-3 .col-md-4
.select-wrapper .select-wrapper
= f.select :lfs_enabled, [%w(Enabled true), %w(Disabled false)], {}, selected: @project.lfs_enabled?, class: 'pull-right form-control project-repo-select select-control', data: { field: 'lfs_enabled' } = f.select :lfs_enabled, [%w(Enabled true), %w(Disabled false)], {}, selected: @project.lfs_enabled?, class: 'pull-right form-control project-repo-select select-control', data: { field: 'lfs_enabled' }
= icon('chevron-down') = icon('chevron-down')
...@@ -138,19 +140,19 @@ ...@@ -138,19 +140,19 @@
.row.prepend-top-default .row.prepend-top-default
%hr %hr
.row.prepend-top-default .row.prepend-top-default
.col-lg-3 .col-lg-4
%h4.prepend-top-0 %h4.prepend-top-0
Housekeeping Housekeeping
%p.append-bottom-0 %p.append-bottom-0
%p %p
Runs a number of housekeeping tasks within the current repository, Runs a number of housekeeping tasks within the current repository,
such as compressing file revisions and removing unreachable objects. such as compressing file revisions and removing unreachable objects.
.col-lg-9 .col-lg-8
= link_to 'Housekeeping', housekeeping_namespace_project_path(@project.namespace, @project), = link_to 'Housekeeping', housekeeping_namespace_project_path(@project.namespace, @project),
method: :post, class: "btn btn-default" method: :post, class: "btn btn-default"
%hr %hr
.row.prepend-top-default .row.prepend-top-default
.col-lg-3 .col-lg-4
%h4.prepend-top-0 %h4.prepend-top-0
Export project Export project
%p.append-bottom-0 %p.append-bottom-0
...@@ -159,7 +161,7 @@ ...@@ -159,7 +161,7 @@
%p %p
Once the exported file is ready, you will receive a notification email with a download link. Once the exported file is ready, you will receive a notification email with a download link.
.col-lg-9 .col-lg-8
- if @project.export_project_path - if @project.export_project_path
= link_to 'Download export', download_export_namespace_project_path(@project.namespace, @project), = link_to 'Download export', download_export_namespace_project_path(@project.namespace, @project),
...@@ -190,7 +192,7 @@ ...@@ -190,7 +192,7 @@
- if can? current_user, :archive_project, @project - if can? current_user, :archive_project, @project
%hr %hr
.row.prepend-top-default .row.prepend-top-default
.col-lg-3 .col-lg-4
%h4.warning-title.prepend-top-0 %h4.warning-title.prepend-top-0
- if @project.archived? - if @project.archived?
Unarchive project Unarchive project
...@@ -201,7 +203,7 @@ ...@@ -201,7 +203,7 @@
Unarchiving the project will mark its repository as active. The project can be committed to. Unarchiving the project will mark its repository as active. The project can be committed to.
- else - else
Archiving the project will mark its repository as read-only. It is hidden from the dashboard and doesn't show up in searches. Archiving the project will mark its repository as read-only. It is hidden from the dashboard and doesn't show up in searches.
.col-lg-9 .col-lg-8
- if @project.archived? - if @project.archived?
%p %p
%strong Once active this project shows up in the search and on the dashboard. %strong Once active this project shows up in the search and on the dashboard.
...@@ -216,10 +218,10 @@ ...@@ -216,10 +218,10 @@
method: :post, class: "btn btn-warning" method: :post, class: "btn btn-warning"
%hr %hr
.row.prepend-top-default .row.prepend-top-default
.col-lg-3 .col-lg-4
%h4.prepend-top-0.warning-title %h4.prepend-top-0.warning-title
Rename repository Rename repository
.col-lg-9 .col-lg-8
= render 'projects/errors' = render 'projects/errors'
= form_for([@project.namespace.becomes(Namespace), @project]) do |f| = form_for([@project.namespace.becomes(Namespace), @project]) do |f|
.form-group.project_name_holder .form-group.project_name_holder
...@@ -244,12 +246,12 @@ ...@@ -244,12 +246,12 @@
- if can?(current_user, :change_namespace, @project) - if can?(current_user, :change_namespace, @project)
%hr %hr
.row.prepend-top-default .row.prepend-top-default
.col-lg-3 .col-lg-4
%h4.prepend-top-0.danger-title %h4.prepend-top-0.danger-title
Transfer project to new group Transfer project to new group
%p.append-bottom-0 %p.append-bottom-0
Please select the group you want to transfer this project to in the dropdown to the right. Please select the group you want to transfer this project to in the dropdown to the right.
.col-lg-9 .col-lg-8
= form_for([@project.namespace.becomes(Namespace), @project], url: transfer_namespace_project_path(@project.namespace, @project), method: :put, remote: true, html: { class: 'js-project-transfer-form' } ) do |f| = form_for([@project.namespace.becomes(Namespace), @project], url: transfer_namespace_project_path(@project.namespace, @project), method: :put, remote: true, html: { class: 'js-project-transfer-form' } ) do |f|
.form-group .form-group
= label_tag :new_namespace_id, nil, class: 'label-light' do = label_tag :new_namespace_id, nil, class: 'label-light' do
...@@ -265,7 +267,7 @@ ...@@ -265,7 +267,7 @@
- if @project.forked? && can?(current_user, :remove_fork_project, @project) - if @project.forked? && can?(current_user, :remove_fork_project, @project)
%hr %hr
.row.prepend-top-default.append-bottom-default .row.prepend-top-default.append-bottom-default
.col-lg-3 .col-lg-4
%h4.prepend-top-0.danger-title %h4.prepend-top-0.danger-title
Remove fork relationship Remove fork relationship
%p.append-bottom-0 %p.append-bottom-0
...@@ -273,7 +275,7 @@ ...@@ -273,7 +275,7 @@
This will remove the fork relationship to source project This will remove the fork relationship to source project
= succeed "." do = succeed "." do
= link_to @project.forked_from_project.name_with_namespace, project_path(@project.forked_from_project) = link_to @project.forked_from_project.name_with_namespace, project_path(@project.forked_from_project)
.col-lg-9 .col-lg-8
= form_for([@project.namespace.becomes(Namespace), @project], url: remove_fork_namespace_project_path(@project.namespace, @project), method: :delete, remote: true, html: { class: 'transfer-project' }) do |f| = form_for([@project.namespace.becomes(Namespace), @project], url: remove_fork_namespace_project_path(@project.namespace, @project), method: :delete, remote: true, html: { class: 'transfer-project' }) do |f|
%p %p
%strong Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source. %strong Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source.
...@@ -281,12 +283,12 @@ ...@@ -281,12 +283,12 @@
- if can?(current_user, :remove_project, @project) - if can?(current_user, :remove_project, @project)
%hr %hr
.row.prepend-top-default.append-bottom-default .row.prepend-top-default.append-bottom-default
.col-lg-3 .col-lg-4
%h4.prepend-top-0.danger-title %h4.prepend-top-0.danger-title
Remove project Remove project
%p.append-bottom-0 %p.append-bottom-0
Removing the project will delete its repository and all related resources including issues, merge requests etc. Removing the project will delete its repository and all related resources including issues, merge requests etc.
.col-lg-9 .col-lg-8
= form_tag(namespace_project_path(@project.namespace, @project), method: :delete) do = form_tag(namespace_project_path(@project.namespace, @project), method: :delete) do
%p %p
%strong Removed projects cannot be restored! %strong Removed projects cannot be restored!
......
.row.prepend-top-default .row.prepend-top-default
.col-lg-3 .col-lg-4
%h4.prepend-top-0 %h4.prepend-top-0
= page_title = page_title
%p %p
#{link_to 'Webhooks', help_page_path('user/project/integrations/webhooks')} can be #{link_to 'Webhooks', help_page_path('user/project/integrations/webhooks')} can be
used for binding events when something is happening within the project. used for binding events when something is happening within the project.
.col-lg-9.append-bottom-default .col-lg-8.append-bottom-default
= form_for @hook, as: :hook, url: polymorphic_path([@project.namespace.becomes(Namespace), @project, :hooks]) do |f| = form_for @hook, as: :hook, url: polymorphic_path([@project.namespace.becomes(Namespace), @project, :hooks]) do |f|
= render partial: 'shared/web_hooks/form', locals: { form: f, hook: @hook } = render partial: 'shared/web_hooks/form', locals: { form: f, hook: @hook }
= f.submit 'Add webhook', class: 'btn btn-create' = f.submit 'Add webhook', class: 'btn btn-create'
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
.col-md-6 .col-md-6
= render 'projects/pipelines/charts/overall' = render 'projects/pipelines/charts/overall'
.col-md-6 .col-md-6
= render 'projects/pipelines/charts/build_times' = render 'projects/pipelines/charts/pipeline_times'
%hr %hr
= render 'projects/pipelines/charts/builds' = render 'projects/pipelines/charts/pipelines'
...@@ -2,18 +2,14 @@ ...@@ -2,18 +2,14 @@
%ul %ul
%li %li
Total: Total:
%strong= pluralize @project.builds.count(:all), 'job' %strong= pluralize @counts[:total], 'pipeline'
%li %li
Successful: Successful:
%strong= pluralize @project.builds.success.count(:all), 'job' %strong= pluralize @counts[:success], 'pipeline'
%li %li
Failed: Failed:
%strong= pluralize @project.builds.failed.count(:all), 'job' %strong= pluralize @counts[:failed], 'pipeline'
%li %li
Success ratio: Success ratio:
%strong %strong
#{success_ratio(@project.builds.success, @project.builds.failed)}% #{success_ratio(@counts)}%
%li
Commits covered:
%strong
= @project.pipelines.count(:all)
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
:javascript :javascript
var data = { var data = {
labels : #{@charts[:build_times].labels.to_json}, labels : #{@charts[:pipeline_times].labels.to_json},
datasets : [ datasets : [
{ {
fillColor : "rgba(220,220,220,0.5)", fillColor : "rgba(220,220,220,0.5)",
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
barStrokeWidth: 1, barStrokeWidth: 1,
barValueSpacing: 1, barValueSpacing: 1,
barDatasetSpacing: 1, barDatasetSpacing: 1,
data : #{@charts[:build_times].build_times.to_json} data : #{@charts[:pipeline_times].pipeline_times.to_json}
} }
] ]
} }
......
%div{ class: badge.title.gsub(' ', '-') } %div{ class: badge.title.gsub(' ', '-') }
.col-lg-3.profile-settings-sidebar .col-lg-4.profile-settings-sidebar
%h4.prepend-top-0 %h4.prepend-top-0
= badge.title.capitalize = badge.title.capitalize
.col-lg-9 .col-lg-8
.prepend-top-10 .prepend-top-10
.panel.panel-default .panel.panel-default
.panel-heading .panel-heading
......
.row.prepend-top-default .row.prepend-top-default
.col-lg-3.profile-settings-sidebar .col-lg-4.profile-settings-sidebar
%h4.prepend-top-0 %h4.prepend-top-0
Pipelines Pipelines
.col-lg-9 .col-lg-8
= form_for @project, url: namespace_project_pipelines_settings_path(@project.namespace.becomes(Namespace), @project) do |f| = form_for @project, url: namespace_project_pipelines_settings_path(@project.namespace.becomes(Namespace), @project) do |f|
%fieldset.builds-feature %fieldset.builds-feature
- unless @repository.gitlab_ci_yml - unless @repository.gitlab_ci_yml
......
.row.prepend-top-default .row.prepend-top-default
.col-lg-3.settings-sidebar .col-lg-4.settings-sidebar
%h4.prepend-top-0 %h4.prepend-top-0
Project members Project members
- if can?(current_user, :admin_project_member, @project) - if can?(current_user, :admin_project_member, @project)
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
%i Masters %i Masters
or or
%i Owners %i Owners
.col-lg-9 .col-lg-8
.light .light
- if can?(current_user, :admin_project_member, @project) - if can?(current_user, :admin_project_member, @project)
%ul.nav-links.project-member-tabs{ role: 'tablist' } %ul.nav-links.project-member-tabs{ role: 'tablist' }
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
%h4 %h4
Protected Branches Protected Branches
%button.btn.js-settings-toggle %button.btn.js-settings-toggle
= expanded ? 'Close' : 'Expand' = expanded ? 'Collapse' : 'Expand'
%p %p
Keep stable branches secure and force developers to use merge requests. Keep stable branches secure and force developers to use merge requests.
.settings-content.no-animate{ class: ('expanded' if expanded) } .settings-content.no-animate{ class: ('expanded' if expanded) }
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
%h4 %h4
Protected Tags Protected Tags
%button.btn.js-settings-toggle %button.btn.js-settings-toggle
= expanded ? 'Close' : 'Expand' = expanded ? 'Collapse' : 'Expand'
%p %p
Limit access to creating and updating tags. Limit access to creating and updating tags.
.settings-content.no-animate{ class: ('expanded' if expanded) } .settings-content.no-animate{ class: ('expanded' if expanded) }
......
...@@ -23,3 +23,7 @@ ...@@ -23,3 +23,7 @@
- disabled_title = @service.disabled_title - disabled_title = @service.disabled_title
= link_to 'Cancel', namespace_project_settings_integrations_path(@project.namespace, @project), class: 'btn btn-cancel' = link_to 'Cancel', namespace_project_settings_integrations_path(@project.namespace, @project), class: 'btn btn-cancel'
- if lookup_context.template_exists?('show', "projects/services/#{@service.to_param}", true)
%hr
= render "projects/services/#{@service.to_param}/show"
.row.prepend-top-default.append-bottom-default .row.prepend-top-default.append-bottom-default
.col-lg-3 .col-lg-4
%h4.prepend-top-0 %h4.prepend-top-0
Project services Project services
%p Project services allow you to integrate GitLab with other applications %p Project services allow you to integrate GitLab with other applications
.col-lg-9 .col-lg-8
%table.table %table.table
%colgroup %colgroup
%col %col
......
- content_for :page_specific_javascripts do
= webpack_bundle_tag('prometheus_metrics')
.row.prepend-top-default.append-bottom-default.prometheus-metrics-monitoring.js-prometheus-metrics-monitoring
.col-lg-3
%h4.prepend-top-0
Metrics
%p
Metrics are automatically configured and monitored
based on a library of metrics from popular exporters.
= link_to 'More information', '#'
.col-lg-9
.panel.panel-default.js-panel-monitored-metrics{ data: { "active-metrics" => "#{namespace_project_prometheus_active_metrics_path(@project.namespace, @project, :json)}" } }
.panel-heading
%h3.panel-title
Monitored
%span.badge.js-monitored-count 0
.panel-body
.loading-metrics.text-center.js-loading-metrics
= icon('spinner spin 3x', class: 'metrics-load-spinner')
%p Finding and configuring metrics...
.empty-metrics.text-center.hidden.js-empty-metrics
= custom_icon('icon_empty_metrics')
%p No metrics are being monitored. To start monitoring, deploy to an environment.
= link_to project_environments_path(@project), title: 'View environments', class: 'btn btn-success' do
View environments
%ul.list-unstyled.metrics-list.hidden.js-metrics-list
.panel.panel-default.hidden.js-panel-missing-env-vars
.panel-heading
%h3.panel-title
= icon('caret-right lg fw', class: 'panel-toggle js-panel-toggle', 'aria-label' => 'Toggle panel')
Missing environment variable
%span.badge.js-env-var-count 0
.panel-body.hidden
.flash-container
.flash-notice
.flash-text
To set up automatic monitoring, add the environment variable
%code
$CI_ENVIRONMENT_SLUG
to exporter&rsquo;s queries.
= link_to 'More information', '#'
%ul.list-unstyled.metrics-list.js-missing-var-metrics-list
- @content_class = "limit-container-width" unless fluid_layout
- page_title "Pipelines" - page_title "Pipelines"
= render "projects/settings/head" = render "projects/settings/head"
......
- @content_class = "limit-container-width" unless fluid_layout
- page_title 'Integrations' - page_title 'Integrations'
= render "projects/settings/head" = render "projects/settings/head"
= render 'projects/hooks/index' = render 'projects/hooks/index'
......
- @content_class = "limit-container-width" unless fluid_layout
- page_title "Members" - page_title "Members"
= render "projects/settings/head" = render "projects/settings/head"
......
- @content_class = "limit-container-width limited-inner-width-container" unless fluid_layout
- page_title "#{@snippet.title} (#{@snippet.to_reference})", "Snippets" - page_title "#{@snippet.title} (#{@snippet.to_reference})", "Snippets"
= render 'shared/snippets/header' = render 'shared/snippets/header'
...@@ -9,4 +10,4 @@ ...@@ -9,4 +10,4 @@
.row-content-block.top-block.content-component-block .row-content-block.top-block.content-component-block
= render 'award_emoji/awards_block', awardable: @snippet, inline: true = render 'award_emoji/awards_block', awardable: @snippet, inline: true
#notes= render "shared/notes/notes_with_form", :autocomplete => true #notes.limited-width-notes= render "shared/notes/notes_with_form", :autocomplete => true
- if readme.rich_viewer - if readme.rich_viewer
%article.file-holder.readme-holder %article.file-holder.readme-holder{ class: ("limited-width-container" unless fluid_layout) }
.js-file-title.file-title .js-file-title.file-title
= blob_icon readme.mode, readme.name = blob_icon readme.mode, readme.name
= link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@ref, readme.path)) do = link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@ref, readme.path)) do
......
.row.prepend-top-default.append-bottom-default.triggers-container .row.prepend-top-default.append-bottom-default.triggers-container
.col-lg-3 .col-lg-4
= render "projects/triggers/content" = render "projects/triggers/content"
.col-lg-9 .col-lg-8
.panel.panel-default .panel.panel-default
.panel-heading .panel-heading
%h4.panel-title %h4.panel-title
......
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.
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.
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