Commit 93c0729a authored by Lin Jen-Shin's avatar Lin Jen-Shin

Merge remote-tracking branch 'upstream/master' into add-ci_variables-environment_scope

* upstream/master: (226 commits)
  Polish sidebar toggle
  Add changelog entry
  Fix optional args for POST :id/variables
  Update CHANGELOG.md for 9.3.1
  Bump bootsnap to 1.1.1
  Add explicit message when no runners on admin
  Fix endpoint not being update correctly
  Remove unused Gitlab::Git::Commit#to_diff argument
  Drop GFM support for the title of Milestone/MergeRequest in template
  Handle Promise rejections in mr_widget_pipeline_spec.js
  Handle missing pipeline in merge request widget
  Store merge request ref_fetched status in the database
  Tag a spec as :nested_groups since it fails on MySQL
  Bump premailer-rails gem to 1.9.7 and its dependencies to prevent network retrieval of assets
  Truncate long job names in environment view; wrap author to next line
  Replaces 'dashboard/merge_requests' spinach with rspec
  Update GITLAB_SHELL_VERSION to 5.0.6
  Fix click not being able to find the current element to use trigger('click') instead
  make accepting mrs more prominent
  Remove last references to job for pipeline charts
  ...
parents 10e732d2 eacce60b
...@@ -461,6 +461,7 @@ karma: ...@@ -461,6 +461,7 @@ karma:
- coverage-javascript/ - coverage-javascript/
codeclimate: codeclimate:
<<: *except-docs
before_script: [] before_script: []
image: docker:latest image: docker:latest
stage: test stage: test
......
This diff is collapsed.
...@@ -49,6 +49,8 @@ _This notice should stay as the first item in the CONTRIBUTING.MD file._ ...@@ -49,6 +49,8 @@ _This notice should stay as the first item in the CONTRIBUTING.MD file._
Thank you for your interest in contributing to GitLab. This guide details how Thank you for your interest in contributing to GitLab. This guide details how
to contribute to GitLab in a way that is efficient for everyone. to contribute to GitLab in a way that is efficient for everyone.
Looking for something to work on? Look for the label [Accepting Merge Requests](#i-want-to-contribute).
GitLab comes into two flavors, GitLab Community Edition (CE) our free and open GitLab comes into two flavors, GitLab Community Edition (CE) our free and open
source edition, and GitLab Enterprise Edition (EE) which is our commercial source edition, and GitLab Enterprise Edition (EE) which is our commercial
edition. Throughout this guide you will see references to CE and EE for edition. Throughout this guide you will see references to CE and EE for
......
...@@ -2,7 +2,7 @@ source 'https://rubygems.org' ...@@ -2,7 +2,7 @@ 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' gem 'bootsnap', '~> 1.1'
# Responders respond_to and respond_with # Responders respond_to and respond_with
gem 'responders', '~> 2.0' gem 'responders', '~> 2.0'
...@@ -123,6 +123,7 @@ gem 'asciidoctor', '~> 1.5.2' ...@@ -123,6 +123,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
...@@ -256,7 +257,7 @@ gem 'base32', '~> 0.3.0' ...@@ -256,7 +257,7 @@ gem 'base32', '~> 0.3.0'
# Sentry integration # Sentry integration
gem 'sentry-raven', '~> 2.4.0' gem 'sentry-raven', '~> 2.4.0'
gem 'premailer-rails', '~> 1.9.0' gem 'premailer-rails', '~> 1.9.7'
# I18n # I18n
gem 'ruby_parser', '~> 3.8', require: false gem 'ruby_parser', '~> 3.8', require: false
...@@ -384,7 +385,7 @@ gem 'vmstat', '~> 2.3.0' ...@@ -384,7 +385,7 @@ gem 'vmstat', '~> 2.3.0'
gem 'sys-filesystem', '~> 1.1.6' gem 'sys-filesystem', '~> 1.1.6'
# Gitaly GRPC client # Gitaly GRPC client
gem 'gitaly', '~> 0.8.0' gem 'gitaly', '~> 0.9.0'
gem 'toml-rb', '~> 0.3.15', require: false gem 'toml-rb', '~> 0.3.15', require: false
......
...@@ -83,11 +83,12 @@ GEM ...@@ -83,11 +83,12 @@ 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) bootsnap (1.1.1)
msgpack (~> 1.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)
...@@ -138,7 +139,7 @@ GEM ...@@ -138,7 +139,7 @@ GEM
crack (0.4.3) crack (0.4.3)
safe_yaml (~> 1.0.0) safe_yaml (~> 1.0.0)
creole (0.5.0) creole (0.5.0)
css_parser (1.4.1) css_parser (1.5.0)
addressable addressable
d3_rails (3.5.11) d3_rails (3.5.11)
railties (>= 3.1.0) railties (>= 3.1.0)
...@@ -277,7 +278,7 @@ GEM ...@@ -277,7 +278,7 @@ GEM
po_to_json (>= 1.0.0) po_to_json (>= 1.0.0)
rails (>= 3.2.0) rails (>= 3.2.0)
gherkin-ruby (0.3.2) gherkin-ruby (0.3.2)
gitaly (0.8.0) gitaly (0.9.0)
google-protobuf (~> 3.1) google-protobuf (~> 3.1)
grpc (~> 1.0) grpc (~> 1.0)
github-linguist (4.7.6) github-linguist (4.7.6)
...@@ -353,7 +354,7 @@ GEM ...@@ -353,7 +354,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)
...@@ -591,10 +592,11 @@ GEM ...@@ -591,10 +592,11 @@ GEM
websocket-driver (>= 0.2.0) websocket-driver (>= 0.2.0)
posix-spawn (0.3.11) posix-spawn (0.3.11)
powerpack (0.1.1) powerpack (0.1.1)
premailer (1.8.6) premailer (1.10.4)
css_parser (>= 1.3.6) addressable
css_parser (>= 1.4.10)
htmlentities (>= 4.0.0) htmlentities (>= 4.0.0)
premailer-rails (1.9.2) premailer-rails (1.9.7)
actionmailer (>= 3, < 6) actionmailer (>= 3, < 6)
premailer (~> 1.7, >= 1.7.9) premailer (~> 1.7, >= 1.7.9)
prometheus-client-mmap (0.7.0.beta5) prometheus-client-mmap (0.7.0.beta5)
...@@ -928,8 +930,9 @@ DEPENDENCIES ...@@ -928,8 +930,9 @@ 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) bootsnap (~> 1.1)
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)
...@@ -977,7 +980,7 @@ DEPENDENCIES ...@@ -977,7 +980,7 @@ DEPENDENCIES
gettext (~> 3.2.2) gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.2.0) gettext_i18n_rails_js (~> 1.2.0)
gitaly (~> 0.8.0) gitaly (~> 0.9.0)
github-linguist (~> 4.7.0) github-linguist (~> 4.7.0)
gitlab-flowdock-git-hook (~> 1.0.1) gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-markup (~> 1.5.1) gitlab-markup (~> 1.5.1)
...@@ -1047,7 +1050,7 @@ DEPENDENCIES ...@@ -1047,7 +1050,7 @@ DEPENDENCIES
peek-sidekiq (~> 1.0.3) peek-sidekiq (~> 1.0.3)
pg (~> 0.18.2) pg (~> 0.18.2)
poltergeist (~> 1.9.0) poltergeist (~> 1.9.0)
premailer-rails (~> 1.9.0) premailer-rails (~> 1.9.7)
prometheus-client-mmap (~> 0.7.0.beta5) prometheus-client-mmap (~> 0.7.0.beta5)
pry-byebug (~> 3.4.1) pry-byebug (~> 3.4.1)
pry-rails (~> 0.3.4) pry-rails (~> 0.3.4)
......
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);
......
...@@ -55,6 +55,7 @@ import RefSelectDropdown from './ref_select_dropdown'; ...@@ -55,6 +55,7 @@ import RefSelectDropdown from './ref_select_dropdown';
import GfmAutoComplete from './gfm_auto_complete'; import GfmAutoComplete from './gfm_auto_complete';
import ShortcutsBlob from './shortcuts_blob'; import ShortcutsBlob from './shortcuts_blob';
import initSettingsPanels from './settings_panels'; import initSettingsPanels from './settings_panels';
import initExperimentalFlags from './experimental_flags';
(function() { (function() {
var Dispatcher; var Dispatcher;
...@@ -120,6 +121,9 @@ import initSettingsPanels from './settings_panels'; ...@@ -120,6 +121,9 @@ import initSettingsPanels from './settings_panels';
} }
switch (page) { switch (page) {
case 'profiles:preferences:show':
initExperimentalFlags();
break;
case 'sessions:new': case 'sessions:new':
new UsernameValidator(); new UsernameValidator();
new ActiveTabMemoizer(); new ActiveTabMemoizer();
......
...@@ -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"
......
...@@ -403,6 +403,14 @@ export default { ...@@ -403,6 +403,14 @@ export default {
return ''; return '';
}, },
displayEnvironmentActions() {
return this.hasManualActions ||
this.externalURL ||
this.monitoringUrl ||
this.hasStopAction ||
this.canRetry;
},
/** /**
* Constructs folder URL based on the current location and the folder id. * Constructs folder URL based on the current location and the folder id.
* *
...@@ -535,9 +543,12 @@ export default { ...@@ -535,9 +543,12 @@ export default {
</span> </span>
</div> </div>
<div class="table-section section-30 table-button-footer" role="gridcell"> <div
v-if="!model.isFolder && displayEnvironmentActions"
class="table-section section-30 table-button-footer"
role="gridcell">
<div <div
v-if="!model.isFolder"
class="btn-group table-action-buttons" class="btn-group table-action-buttons"
role="group"> role="group">
......
...@@ -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"
......
import Cookies from 'js-cookie';
export default () => {
$('.js-experiment-feature-toggle').on('change', (e) => {
const el = e.target;
Cookies.set(el.name, el.value, {
expires: 365 * 10,
});
});
};
...@@ -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(
...@@ -487,6 +491,7 @@ class FilteredSearchManager { ...@@ -487,6 +491,7 @@ class FilteredSearchManager {
} }
searchState(e) { searchState(e) {
e.preventDefault();
const target = e.currentTarget; const target = e.currentTarget;
// remove focus outline after click // remove focus outline after click
target.blur(); target.blur();
......
...@@ -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
......
...@@ -204,13 +204,7 @@ export default { ...@@ -204,13 +204,7 @@ export default {
method: 'getData', method: 'getData',
successCallback: (res) => { successCallback: (res) => {
const data = res.json(); const data = res.json();
const shouldUpdate = this.store.stateShouldUpdate(data);
this.store.updateState(data); this.store.updateState(data);
if (this.showForm && (shouldUpdate.title || shouldUpdate.description)) {
this.store.formState.lockedWarningVisible = true;
}
}, },
errorCallback(err) { errorCallback(err) {
throw new Error(err); throw new Error(err);
......
...@@ -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>
...@@ -12,6 +12,10 @@ export default class Store { ...@@ -12,6 +12,10 @@ export default class Store {
} }
updateState(data) { updateState(data) {
if (this.stateShouldUpdate(data)) {
this.formState.lockedWarningVisible = true;
}
this.state.titleHtml = data.title; this.state.titleHtml = data.title;
this.state.titleText = data.title_text; this.state.titleText = data.title_text;
this.state.descriptionHtml = data.description; this.state.descriptionHtml = data.description;
...@@ -23,10 +27,8 @@ export default class Store { ...@@ -23,10 +27,8 @@ export default class Store {
} }
stateShouldUpdate(data) { stateShouldUpdate(data) {
return { return this.state.titleText !== data.title_text ||
title: this.state.titleText !== data.title_text, this.state.descriptionText !== data.description_text;
description: this.state.descriptionText !== data.description_text,
};
} }
setFormState(state) { setFormState(state) {
......
...@@ -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.
This diff is collapsed.
...@@ -299,9 +299,10 @@ $(function () { ...@@ -299,9 +299,10 @@ $(function () {
// Commit show suppressed diff // Commit show suppressed diff
}); });
$('.navbar-toggle').on('click', function () { $('.navbar-toggle').on('click', function () {
$('.header-content .title').toggle(); $('.header-content .title, .header-content .navbar-sub-nav').toggle();
$('.header-content .header-logo').toggle(); $('.header-content .header-logo').toggle();
$('.header-content .navbar-collapse').toggle(); $('.header-content .navbar-collapse').toggle();
$('.js-navbar-toggle-left, .js-navbar-toggle-right, .title-container').toggle();
return $('.navbar-toggle').toggleClass('active'); return $('.navbar-toggle').toggleClass('active');
}); });
// Show/hide comments on diff // Show/hide comments on diff
......
...@@ -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,15 +32,14 @@ export default { ...@@ -32,15 +32,14 @@ export default {
}, },
}, },
mixins: [ directives: {
tooltipMixin, tooltip,
], },
data() { data() {
return { return {
isLoading: false, isLoading: false,
dropdownContent: '', dropdownContent: '',
endpoint: this.stage.dropdown_path,
}; };
}, },
...@@ -73,7 +72,7 @@ export default { ...@@ -73,7 +72,7 @@ export default {
}, },
fetchJobs() { fetchJobs() {
this.$http.get(this.endpoint) this.$http.get(this.stage.dropdown_path)
.then((response) => { .then((response) => {
this.dropdownContent = response.json().html; this.dropdownContent = response.json().html;
this.isLoading = false; this.isLoading = false;
...@@ -132,7 +131,7 @@ export default { ...@@ -132,7 +131,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);
......
...@@ -14,6 +14,11 @@ export default { ...@@ -14,6 +14,11 @@ export default {
type: Boolean, type: Boolean,
required: true, required: true,
}, },
showToggle: {
type: Boolean,
required: false,
default: false,
},
}, },
computed: { computed: {
assigneeTitle() { assigneeTitle() {
...@@ -36,6 +41,19 @@ export default { ...@@ -36,6 +41,19 @@ export default {
> >
Edit Edit
</a> </a>
<a
v-if="showToggle"
aria-label="Toggle sidebar"
class="gutter-toggle pull-right js-sidebar-toggle"
href="#"
role="button"
>
<i
aria-hidden="true"
data-hidden="true"
class="fa fa-angle-double-right"
/>
</a>
</div> </div>
`, `,
}; };
...@@ -64,6 +64,7 @@ export default { ...@@ -64,6 +64,7 @@ export default {
}, },
beforeMount() { beforeMount() {
this.field = this.$el.dataset.field; this.field = this.$el.dataset.field;
this.signedIn = typeof this.$el.dataset.signedIn !== 'undefined';
}, },
template: ` template: `
<div> <div>
...@@ -71,6 +72,7 @@ export default { ...@@ -71,6 +72,7 @@ export default {
:number-of-assignees="store.assignees.length" :number-of-assignees="store.assignees.length"
:loading="loading || store.isFetching.assignees" :loading="loading || store.isFetching.assignees"
:editable="store.editable" :editable="store.editable"
:show-toggle="!signedIn"
/> />
<assignees <assignees
v-if="!store.isFetching.assignees" v-if="!store.isFetching.assignees"
......
...@@ -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) {
......
...@@ -17,6 +17,9 @@ export default { ...@@ -17,6 +17,9 @@ export default {
return hasCI && !ciStatus; return hasCI && !ciStatus;
}, },
hasPipeline() {
return Object.keys(this.mr.pipeline || {}).length > 0;
},
svg() { svg() {
return statusIconEntityMap.icon_status_failed; return statusIconEntityMap.icon_status_failed;
}, },
...@@ -30,7 +33,11 @@ export default { ...@@ -30,7 +33,11 @@ export default {
template: ` template: `
<div class="mr-widget-heading"> <div class="mr-widget-heading">
<div class="ci-widget"> <div class="ci-widget">
<template v-if="hasCIError"> <template v-if="!hasPipeline">
<i class="fa fa-spinner fa-spin append-right-10" aria-hidden="true"></i>
Waiting for pipeline...
</template>
<template v-else-if="hasCIError">
<div class="ci-status-icon ci-status-icon-failed ci-error js-ci-error"> <div class="ci-status-icon ci-status-icon-failed ci-error js-ci-error">
<span class="js-icon-link icon-link"> <span class="js-icon-link icon-link">
<span <span
......
...@@ -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 {
...@@ -123,7 +129,7 @@ ...@@ -123,7 +129,7 @@
} }
/** /**
* Annotate file * Blame file
*/ */
&.blame { &.blame {
table { table {
......
...@@ -34,6 +34,8 @@ header { ...@@ -34,6 +34,8 @@ header {
top: 0; top: 0;
left: 0; left: 0;
right: 0; right: 0;
color: $gl-text-color-secondary;
border-radius: 0;
@media (max-width: $screen-xs-min) { @media (max-width: $screen-xs-min) {
padding: 0 16px; padding: 0 16px;
...@@ -59,7 +61,7 @@ header { ...@@ -59,7 +61,7 @@ header {
padding: 0; padding: 0;
.nav > li > a { .nav > li > a {
color: $gl-text-color-secondary; color: currentColor;
font-size: 18px; font-size: 18px;
padding: 0; padding: 0;
margin: (($header-height - 28) / 2) 3px; margin: (($header-height - 28) / 2) 3px;
...@@ -84,7 +86,7 @@ header { ...@@ -84,7 +86,7 @@ header {
&:hover, &:hover,
&:focus, &:focus,
&:active { &:active {
background-color: $gray-light; background-color: transparent;
color: $gl-text-color; color: $gl-text-color;
svg { svg {
...@@ -96,13 +98,19 @@ header { ...@@ -96,13 +98,19 @@ header {
font-size: 14px; font-size: 14px;
} }
.fa-chevron-down {
position: relative;
top: -3px;
font-size: 10px;
}
svg { svg {
position: relative; position: relative;
top: 2px; top: 2px;
height: 17px; height: 17px;
// hack to get SVG to line up with FA icons // hack to get SVG to line up with FA icons
width: 23px; width: 23px;
fill: $gl-text-color-secondary; fill: currentColor;
} }
} }
...@@ -225,7 +233,7 @@ header { ...@@ -225,7 +233,7 @@ header {
} }
a { a {
color: $gl-text-color; color: currentColor;
&:hover { &:hover {
text-decoration: underline; text-decoration: underline;
...@@ -346,6 +354,8 @@ header { ...@@ -346,6 +354,8 @@ header {
width: auto; width: auto;
min-width: 140px; min-width: 140px;
margin-top: -5px; margin-top: -5px;
color: $gl-text-color;
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;
} }
} }
......
...@@ -45,8 +45,7 @@ ...@@ -45,8 +45,7 @@
li { li {
display: flex; display: flex;
a, a {
.btn-link {
padding: $gl-btn-padding; padding: $gl-btn-padding;
padding-bottom: 11px; padding-bottom: 11px;
font-size: 14px; font-size: 14px;
...@@ -68,29 +67,7 @@ ...@@ -68,29 +67,7 @@
} }
} }
.btn-link { &.active a {
padding-top: 16px;
padding-left: 15px;
padding-right: 15px;
border-left: none;
border-right: none;
border-top: none;
border-radius: 0;
&:hover,
&:active,
&:focus {
background-color: transparent;
}
&:active {
outline: 0;
box-shadow: none;
}
}
&.active a,
&.active .btn-link {
border-bottom: 2px solid $link-underline-blue; border-bottom: 2px solid $link-underline-blue;
color: $black; color: $black;
font-weight: 600; font-weight: 600;
......
...@@ -44,6 +44,10 @@ ...@@ -44,6 +44,10 @@
&:target, &:target,
&.target { &.target {
background: $line-target-blue; background: $line-target-blue;
&.system-note .note-body .note-text.system-note-commit-list::after {
background: linear-gradient(rgba($line-target-blue, 0.1) -100px, $line-target-blue 100%);
}
} }
.avatar { .avatar {
......
...@@ -74,6 +74,10 @@ $red-700: #a62d19; ...@@ -74,6 +74,10 @@ $red-700: #a62d19;
$red-800: #8b2615; $red-800: #8b2615;
$red-900: #711e11; $red-900: #711e11;
$purple-700: #4a2192;
$purple-800: #2c0a5c;
$purple-900: #380d75;
$black: #000; $black: #000;
$black-transparent: rgba(0, 0, 0, 0.3); $black-transparent: rgba(0, 0, 0, 0.3);
...@@ -161,6 +165,7 @@ $progress-color: #c0392b; ...@@ -161,6 +165,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;
...@@ -323,6 +328,7 @@ $note-disabled-comment-color: #b2b2b2; ...@@ -323,6 +328,7 @@ $note-disabled-comment-color: #b2b2b2;
$note-targe3-outside: #fffff0; $note-targe3-outside: #fffff0;
$note-targe3-inside: #ffffd3; $note-targe3-inside: #ffffd3;
$note-line2-border: #ddd; $note-line2-border: #ddd;
$note-icon-gutter-width: 55px;
/* /*
......
@import "framework/variables";
@import 'framework/tw_bootstrap_variables';
@import "bootstrap/variables";
header.navbar-gitlab-new {
color: $white-light;
background-color: $purple-900;
border-bottom: 0;
.header-content {
padding-left: 0;
.title-container {
padding-top: 0;
overflow: visible;
}
.title {
display: block;
height: 100%;
padding-right: 0;
color: currentColor;
> a {
display: flex;
align-items: center;
height: 100%;
padding-top: 3px;
padding-right: $gl-padding;
padding-left: $gl-padding;
margin-left: -$gl-padding;
border-bottom: 3px solid transparent;
@media (min-width: $screen-sm-min) {
padding-right: $gl-padding;
padding-left: $gl-padding;
}
svg {
margin-top: -3px;
@media (min-width: $screen-sm-min) {
margin-right: 10px;
}
}
&:hover,
&:focus {
color: currentColor;
text-decoration: none;
border-bottom-color: $white-light;
}
}
}
.dropdown.open {
> a {
border-bottom-color: $white-light;
}
}
.dropdown-menu {
margin-top: 4px;
min-width: 130px;
@media (max-width: $screen-xs-max) {
left: auto;
right: 0;
}
}
}
.navbar-collapse {
padding-left: 0;
color: $white-light;
box-shadow: 0;
@media (max-width: $screen-xs-max) {
margin-left: -$gl-padding;
margin-right: -10px;
}
.dropdown-bold-header {
color: initial;
}
.nav {
> li:not(.hidden-xs) a {
@media (max-width: $screen-xs-max) {
margin-left: 0;
min-width: 100%;
}
}
}
}
.container-fluid {
.navbar-toggle {
min-width: 45px;
padding: 6px $gl-padding;
margin-right: -7px;
font-size: 14px;
text-align: center;
color: currentColor;
border-left: 1px solid lighten($purple-700, 10%);
&:hover,
&:focus,
&.active {
color: currentColor;
background-color: transparent;
}
}
.navbar-nav {
@media (max-width: $screen-xs-max) {
display: flex;
padding-right: 10px;
}
li {
.badge {
box-shadow: none;
}
}
}
.nav > li {
&.header-user {
@media (max-width: $screen-xs-max) {
padding-left: 10px;
}
}
> a {
background: none;
opacity: .9;
will-change: opacity;
&.header-user-dropdown-toggle {
.header-user-avatar {
border-color: $white-light;
}
}
&:hover,
&:focus {
color: $white-light;
opacity: 1;
> svg {
fill: $white-light;
}
&.header-user-dropdown-toggle {
.header-user-avatar {
border-color: $white-light;
}
}
}
}
}
}
}
.navbar-sub-nav {
display: flex;
margin-bottom: 0;
color: $white-light;
> li {
&.active > a,
a:hover,
a:focus {
border-bottom-color: $white-light;
text-decoration: none;
outline: 0;
opacity: 1;
}
> a {
display: block;
padding: 16px 10px 13px;
font-size: 13px;
color: currentColor;
border-bottom: 3px solid transparent;
opacity: .9;
will-change: opacity;
@media (min-width: $screen-sm-min) {
padding: 15px $gl-padding 12px;
font-size: 14px;
}
}
}
.dropdown-chevron {
position: relative;
top: -1px;
font-size: 10px;
}
}
.header-user .dropdown-menu-nav,
.header-new .dropdown-menu-nav {
margin-top: 4px;
}
.search {
form {
border-color: $purple-800;
&:hover {
border-color: rgba($white-light, .6);
box-shadow: none;
}
}
&.search-active form {
border-color: $white-light;
}
form,
.search-input {
background-color: $purple-700;
}
.search-input {
color: $white-light;
}
.search-input::placeholder {
color: rgba($white-light, .6);
}
.location-badge {
font-size: 12px;
color: rgba($white-light, .6);
background-color: $purple-800;
transition: color 0.15s;
will-change: color;
}
.search-input-wrap {
.search-icon,
.clear-icon {
color: rgba($white-light, .6);
}
}
&.search-active {
.location-badge {
color: $white-light;
background-color: $purple-800;
}
.search-input-wrap {
.search-icon {
color: rgba($white-light, .6);
}
.clear-icon {
color: $white-light;
}
}
}
}
...@@ -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;
} }
...@@ -226,6 +228,12 @@ ...@@ -226,6 +228,12 @@
padding-top: 10px; padding-top: 10px;
} }
&:not(.issue-boards-sidebar):not([data-signed-in]) {
.issuable-sidebar-header {
display: none;
}
}
.assign-yourself .btn-link { .assign-yourself .btn-link {
padding-left: 0; padding-left: 0;
} }
...@@ -247,6 +255,10 @@ ...@@ -247,6 +255,10 @@
border-left: 1px solid $border-gray-normal; border-left: 1px solid $border-gray-normal;
} }
.title .gutter-toggle {
margin-top: 0;
}
.assignee .avatar { .assignee .avatar {
float: left; float: left;
margin-right: 10px; margin-right: 10px;
......
...@@ -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 {
......
...@@ -148,8 +148,20 @@ ...@@ -148,8 +148,20 @@
padding: 6px 0; padding: 6px 0;
} }
.notes-form > li { .notes.notes-form > li.timeline-entry {
border: 0; @include notes-media('max', $screen-sm-max) {
padding: 0;
}
.timeline-content {
@include notes-media('max', $screen-sm-max) {
margin: 0;
}
}
.timeline-entry-inner {
border: 0;
}
} }
.note-edit-form { .note-edit-form {
......
...@@ -14,16 +14,6 @@ ul.notes { ...@@ -14,16 +14,6 @@ ul.notes {
margin: 0; margin: 0;
padding: 0; padding: 0;
.timeline-content {
margin-left: 55px;
&.timeline-content-form {
@include notes-media('max', $screen-sm-max) {
margin-left: 0;
}
}
}
.note-created-ago, .note-created-ago,
.note-updated-at { .note-updated-at {
white-space: nowrap; white-space: nowrap;
...@@ -46,17 +36,49 @@ ul.notes { ...@@ -46,17 +36,49 @@ ul.notes {
} }
} }
> li { > li { // .timeline-entry
padding: $gl-padding $gl-btn-padding; padding: 0;
display: block; display: block;
position: relative; position: relative;
border-bottom: 1px solid $white-normal; border-bottom: 0;
@include notes-media('min', $screen-sm-min) {
padding-left: $note-icon-gutter-width;
}
&:last-child { .timeline-entry-inner {
// Override `.timeline > li:last-child { border-bottom: none; }` padding: $gl-padding $gl-btn-padding;
border-bottom: 1px solid $white-normal; border-bottom: 1px solid $white-normal;
} }
&:target,
&.target {
border-bottom: 1px solid $white-normal;
&:not(:first-child) {
border-top: 1px solid $white-normal;
margin-top: -1px;
}
.timeline-entry-inner {
border-bottom: 0;
}
}
.timeline-icon {
@include notes-media('min', $screen-sm-min) {
margin-left: -$note-icon-gutter-width;
}
}
.timeline-content {
margin-left: $note-icon-gutter-width;
@include notes-media('min', $screen-sm-min) {
margin-left: 0;
}
}
&.being-posted { &.being-posted {
pointer-events: none; pointer-events: none;
opacity: 0.5; opacity: 0.5;
...@@ -73,7 +95,7 @@ ul.notes { ...@@ -73,7 +95,7 @@ ul.notes {
} }
&.note-discussion { &.note-discussion {
&.timeline-entry { .timeline-entry-inner {
padding: $gl-padding 10px; padding: $gl-padding 10px;
} }
} }
...@@ -152,13 +174,8 @@ ul.notes { ...@@ -152,13 +174,8 @@ ul.notes {
.system-note { .system-note {
font-size: 14px; font-size: 14px;
padding-left: 0;
clear: both; clear: both;
@include notes-media('min', $screen-sm-min) {
margin-left: 65px;
}
.note-header-info { .note-header-info {
padding-bottom: 0; padding-bottom: 0;
} }
...@@ -192,13 +209,16 @@ ul.notes { ...@@ -192,13 +209,16 @@ ul.notes {
.timeline-icon { .timeline-icon {
float: left; float: left;
@include notes-media('min', $screen-sm-min) {
margin-left: 0;
width: auto;
}
svg { svg {
width: 16px; width: 16px;
height: 16px; height: 16px;
fill: $gray-darkest; fill: $gray-darkest;
position: absolute; margin-top: 2px;
left: 0;
top: 2px;
} }
} }
...@@ -250,7 +270,7 @@ ul.notes { ...@@ -250,7 +270,7 @@ ul.notes {
&::after { &::after {
content: ''; content: '';
width: 100%; width: 100%;
height: 67px; height: 70px;
position: absolute; position: absolute;
left: 0; left: 0;
bottom: 0; bottom: 0;
...@@ -639,15 +659,12 @@ ul.notes { ...@@ -639,15 +659,12 @@ ul.notes {
.discussion-body, .discussion-body,
.diff-file { .diff-file {
.notes .note { .notes .note {
padding-left: $gl-padding; border-bottom: 1px solid $white-normal;
padding-right: $gl-padding;
&.system-note {
padding-left: 0;
@media (min-width: $screen-sm-min) { .timeline-entry-inner {
margin-left: 70px; padding-left: $gl-padding;
} padding-right: $gl-padding;
border-bottom: none;
} }
} }
} }
......
...@@ -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;
} }
......
...@@ -380,7 +380,7 @@ a.deploy-project-label { ...@@ -380,7 +380,7 @@ a.deploy-project-label {
padding: 0; padding: 0;
background: transparent; background: transparent;
border: none; border: none;
line-height: 36px; line-height: 34px;
margin: 0; margin: 0;
> li + li::before { > li + li::before {
......
...@@ -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;
}
}
}
.tree-holder { .tree-holder {
> .nav-block { .nav-block {
margin: 11px 0; margin: 10px 0;
@media (min-width: $screen-sm-min) {
display: flex;
.tree-ref-container {
flex: 1;
}
.tree-controls {
text-align: right;
.btn-group {
margin-left: 10px;
}
}
.tree-ref-holder {
float: left;
margin-right: 15px;
}
.repo-breadcrumb {
li:last-of-type {
position: relative;
}
}
.add-to-tree-dropdown {
position: absolute;
left: 18px;
}
}
}
@media (max-width: $screen-xs-max) {
.repo-breadcrumb {
margin-top: 10px;
position: relative;
.dropdown-menu {
min-width: 100%;
width: 100%;
left: inherit;
right: 0;
}
}
.add-to-tree-dropdown {
position: absolute;
left: 0;
right: 0;
}
.tree-controls {
margin-bottom: 10px;
.btn,
.dropdown,
.btn-group {
width: 100%;
}
.btn {
margin: 10px 0 0;
}
}
} }
.file-finder { .file-finder {
...@@ -131,11 +197,6 @@ ...@@ -131,11 +197,6 @@
} }
} }
.tree-ref-holder {
float: left;
margin-right: 15px;
}
.blob-commit-info { .blob-commit-info {
list-style: none; list-style: none;
margin: 0; margin: 0;
...@@ -159,16 +220,6 @@ ...@@ -159,16 +220,6 @@
color: $md-link-color; color: $md-link-color;
} }
.tree-controls {
float: right;
position: relative;
z-index: 2;
.project-action-button {
margin-left: $btn-side-margin;
}
}
.repo-charts { .repo-charts {
.sub-header { .sub-header {
margin: 20px 0; margin: 20px 0;
......
...@@ -54,7 +54,7 @@ class Admin::UsersController < Admin::ApplicationController ...@@ -54,7 +54,7 @@ class Admin::UsersController < Admin::ApplicationController
end end
def block def block
if user.block if update_user { |user| user.block }
redirect_back_or_admin_user(notice: "Successfully blocked") redirect_back_or_admin_user(notice: "Successfully blocked")
else else
redirect_back_or_admin_user(alert: "Error occurred. User was not blocked") redirect_back_or_admin_user(alert: "Error occurred. User was not blocked")
...@@ -64,7 +64,7 @@ class Admin::UsersController < Admin::ApplicationController ...@@ -64,7 +64,7 @@ class Admin::UsersController < Admin::ApplicationController
def unblock def unblock
if user.ldap_blocked? if user.ldap_blocked?
redirect_back_or_admin_user(alert: "This user cannot be unlocked manually from GitLab") redirect_back_or_admin_user(alert: "This user cannot be unlocked manually from GitLab")
elsif user.activate elsif update_user { |user| user.activate }
redirect_back_or_admin_user(notice: "Successfully unblocked") redirect_back_or_admin_user(notice: "Successfully unblocked")
else else
redirect_back_or_admin_user(alert: "Error occurred. User was not unblocked") redirect_back_or_admin_user(alert: "Error occurred. User was not unblocked")
...@@ -72,7 +72,7 @@ class Admin::UsersController < Admin::ApplicationController ...@@ -72,7 +72,7 @@ class Admin::UsersController < Admin::ApplicationController
end end
def unlock def unlock
if user.unlock_access! if update_user { |user| user.unlock_access! }
redirect_back_or_admin_user(alert: "Successfully unlocked") redirect_back_or_admin_user(alert: "Successfully unlocked")
else else
redirect_back_or_admin_user(alert: "Error occurred. User was not unlocked") redirect_back_or_admin_user(alert: "Error occurred. User was not unlocked")
...@@ -80,7 +80,7 @@ class Admin::UsersController < Admin::ApplicationController ...@@ -80,7 +80,7 @@ class Admin::UsersController < Admin::ApplicationController
end end
def confirm def confirm
if user.confirm if update_user { |user| user.confirm }
redirect_back_or_admin_user(notice: "Successfully confirmed") redirect_back_or_admin_user(notice: "Successfully confirmed")
else else
redirect_back_or_admin_user(alert: "Error occurred. User was not confirmed") redirect_back_or_admin_user(alert: "Error occurred. User was not confirmed")
...@@ -88,7 +88,8 @@ class Admin::UsersController < Admin::ApplicationController ...@@ -88,7 +88,8 @@ class Admin::UsersController < Admin::ApplicationController
end end
def disable_two_factor def disable_two_factor
user.disable_two_factor! update_user { |user| user.disable_two_factor! }
redirect_to admin_user_path(user), redirect_to admin_user_path(user),
notice: 'Two-factor Authentication has been disabled for this user' notice: 'Two-factor Authentication has been disabled for this user'
end end
...@@ -124,15 +125,18 @@ class Admin::UsersController < Admin::ApplicationController ...@@ -124,15 +125,18 @@ class Admin::UsersController < Admin::ApplicationController
end end
respond_to do |format| respond_to do |format|
user.skip_reconfirmation! result = Users::UpdateService.new(user, user_params_with_pass).execute do |user|
if user.update_attributes(user_params_with_pass) user.skip_reconfirmation!
end
if result[:status] == :success
format.html { redirect_to [:admin, user], notice: 'User was successfully updated.' } format.html { redirect_to [:admin, user], notice: 'User was successfully updated.' }
format.json { head :ok } format.json { head :ok }
else else
# restore username to keep form action url. # restore username to keep form action url.
user.username = params[:id] user.username = params[:id]
format.html { render "edit" } format.html { render "edit" }
format.json { render json: user.errors, status: :unprocessable_entity } format.json { render json: [result[:message]], status: result[:status] }
end end
end end
end end
...@@ -148,13 +152,16 @@ class Admin::UsersController < Admin::ApplicationController ...@@ -148,13 +152,16 @@ class Admin::UsersController < Admin::ApplicationController
def remove_email def remove_email
email = user.emails.find(params[:email_id]) email = user.emails.find(params[:email_id])
email.destroy success = Emails::DestroyService.new(user, email: email.email).execute
user.update_secondary_emails!
respond_to do |format| respond_to do |format|
format.html { redirect_back_or_admin_user(notice: "Successfully removed email.") } if success
format.js { head :ok } format.html { redirect_back_or_admin_user(notice: 'Successfully removed email.') }
format.json { head :ok }
else
format.html { redirect_back_or_admin_user(alert: 'There was an error removing the e-mail.') }
format.json { render json: 'There was an error removing the e-mail.', status: 400 }
end
end end
end end
...@@ -202,4 +209,10 @@ class Admin::UsersController < Admin::ApplicationController ...@@ -202,4 +209,10 @@ class Admin::UsersController < Admin::ApplicationController
:website_url :website_url
] ]
end end
def update_user(&block)
result = Users::UpdateService.new(user).execute(&block)
result[:status] == :success
end
end end
...@@ -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
......
class Profiles::AvatarsController < Profiles::ApplicationController class Profiles::AvatarsController < Profiles::ApplicationController
def destroy def destroy
@user = current_user @user = current_user
@user.remove_avatar!
@user.save Users::UpdateService.new(@user).execute { |user| user.remove_avatar! }
redirect_to profile_path, status: 302 redirect_to profile_path, status: 302
end end
......
...@@ -5,9 +5,9 @@ class Profiles::EmailsController < Profiles::ApplicationController ...@@ -5,9 +5,9 @@ class Profiles::EmailsController < Profiles::ApplicationController
end end
def create def create
@email = current_user.emails.new(email_params) @email = Emails::CreateService.new(current_user, email_params).execute
if @email.save if @email.errors.blank?
NotificationService.new.new_email(@email) NotificationService.new.new_email(@email)
else else
flash[:alert] = @email.errors.full_messages.first flash[:alert] = @email.errors.full_messages.first
...@@ -18,9 +18,8 @@ class Profiles::EmailsController < Profiles::ApplicationController ...@@ -18,9 +18,8 @@ class Profiles::EmailsController < Profiles::ApplicationController
def destroy def destroy
@email = current_user.emails.find(params[:id]) @email = current_user.emails.find(params[:id])
@email.destroy
current_user.update_secondary_emails! Emails::DestroyService.new(current_user, email: @email.email).execute
respond_to do |format| respond_to do |format|
format.html { redirect_to profile_emails_url, status: 302 } format.html { redirect_to profile_emails_url, status: 302 }
......
...@@ -7,7 +7,9 @@ class Profiles::NotificationsController < Profiles::ApplicationController ...@@ -7,7 +7,9 @@ class Profiles::NotificationsController < Profiles::ApplicationController
end end
def update def update
if current_user.update_attributes(user_params) result = Users::UpdateService.new(current_user, user_params).execute
if result[:status] == :success
flash[:notice] = "Notification settings saved" flash[:notice] = "Notification settings saved"
else else
flash[:alert] = "Failed to save new settings" flash[:alert] = "Failed to save new settings"
......
...@@ -15,17 +15,17 @@ class Profiles::PasswordsController < Profiles::ApplicationController ...@@ -15,17 +15,17 @@ class Profiles::PasswordsController < Profiles::ApplicationController
return return
end end
new_password = user_params[:password] password_attributes = {
new_password_confirmation = user_params[:password_confirmation] password: user_params[:password],
password_confirmation: user_params[:password_confirmation],
result = @user.update_attributes(
password: new_password,
password_confirmation: new_password_confirmation,
password_automatically_set: false password_automatically_set: false
) }
result = Users::UpdateService.new(@user, password_attributes).execute
if result[:status] == :success
Users::UpdateService.new(@user, password_expires_at: nil).execute
if result
@user.update_attributes(password_expires_at: nil)
redirect_to root_path, notice: 'Password successfully changed' redirect_to root_path, notice: 'Password successfully changed'
else else
render :new render :new
...@@ -46,7 +46,9 @@ class Profiles::PasswordsController < Profiles::ApplicationController ...@@ -46,7 +46,9 @@ class Profiles::PasswordsController < Profiles::ApplicationController
return return
end end
if @user.update_attributes(password_attributes) result = Users::UpdateService.new(@user, password_attributes).execute
if result[:status] == :success
flash[:notice] = "Password was successfully updated. Please login with it" flash[:notice] = "Password was successfully updated. Please login with it"
redirect_to new_user_session_path redirect_to new_user_session_path
else else
......
...@@ -6,7 +6,9 @@ class Profiles::PreferencesController < Profiles::ApplicationController ...@@ -6,7 +6,9 @@ class Profiles::PreferencesController < Profiles::ApplicationController
def update def update
begin begin
if @user.update_attributes(preferences_params) result = Users::UpdateService.new(user, preferences_params).execute
if result[:status] == :success
flash[:notice] = 'Preferences saved.' flash[:notice] = 'Preferences saved.'
else else
flash[:alert] = 'Failed to save preferences.' flash[:alert] = 'Failed to save preferences.'
......
...@@ -10,7 +10,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController ...@@ -10,7 +10,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
current_user.otp_grace_period_started_at = Time.current current_user.otp_grace_period_started_at = Time.current
end end
current_user.save! if current_user.changed? Users::UpdateService.new(current_user).execute!
if two_factor_authentication_required? && !current_user.two_factor_enabled? if two_factor_authentication_required? && !current_user.two_factor_enabled?
two_factor_authentication_reason( two_factor_authentication_reason(
...@@ -41,9 +41,9 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController ...@@ -41,9 +41,9 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
def create def create
if current_user.validate_and_consume_otp!(params[:pin_code]) if current_user.validate_and_consume_otp!(params[:pin_code])
current_user.otp_required_for_login = true Users::UpdateService.new(current_user, otp_required_for_login: true).execute! do |user|
@codes = current_user.generate_otp_backup_codes! @codes = user.generate_otp_backup_codes!
current_user.save! end
render 'create' render 'create'
else else
...@@ -70,8 +70,9 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController ...@@ -70,8 +70,9 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
end end
def codes def codes
@codes = current_user.generate_otp_backup_codes! Users::UpdateService.new(current_user).execute! do |user|
current_user.save! @codes = user.generate_otp_backup_codes!
end
end end
def destroy def destroy
......
...@@ -12,39 +12,47 @@ class ProfilesController < Profiles::ApplicationController ...@@ -12,39 +12,47 @@ class ProfilesController < Profiles::ApplicationController
user_params.except!(:email) if @user.external_email? user_params.except!(:email) if @user.external_email?
respond_to do |format| respond_to do |format|
if @user.update_attributes(user_params) result = Users::UpdateService.new(@user, user_params).execute
if result[:status] == :success
message = "Profile was successfully updated" message = "Profile was successfully updated"
format.html { redirect_back_or_default(default: { action: 'show' }, options: { notice: message }) } format.html { redirect_back_or_default(default: { action: 'show' }, options: { notice: message }) }
format.json { render json: { message: message } } format.json { render json: { message: message } }
else else
message = @user.errors.full_messages.uniq.join('. ') format.html { redirect_back_or_default(default: { action: 'show' }, options: { alert: result[:message] }) }
format.html { redirect_back_or_default(default: { action: 'show' }, options: { alert: "Failed to update profile. #{message}" }) } format.json { render json: result }
format.json { render json: { message: message }, status: :unprocessable_entity }
end end
end end
end end
def reset_private_token def reset_private_token
if current_user.reset_authentication_token! Users::UpdateService.new(@user).execute! do |user|
flash[:notice] = "Private token was successfully reset" user.reset_authentication_token!
end end
flash[:notice] = "Private token was successfully reset"
redirect_to profile_account_path redirect_to profile_account_path
end end
def reset_incoming_email_token def reset_incoming_email_token
if current_user.reset_incoming_email_token! Users::UpdateService.new(@user).execute! do |user|
flash[:notice] = "Incoming email token was successfully reset" user.reset_incoming_email_token!
end end
flash[:notice] = "Incoming email token was successfully reset"
redirect_to profile_account_path redirect_to profile_account_path
end end
def reset_rss_token def reset_rss_token
if current_user.reset_rss_token! Users::UpdateService.new(@user).execute! do |user|
flash[:notice] = "RSS token was successfully reset" user.reset_rss_token!
end end
flash[:notice] = "RSS token was successfully reset"
redirect_to profile_account_path redirect_to profile_account_path
end end
...@@ -55,12 +63,13 @@ class ProfilesController < Profiles::ApplicationController ...@@ -55,12 +63,13 @@ class ProfilesController < Profiles::ApplicationController
end end
def update_username def update_username
if @user.update_attributes(username: user_params[:username]) result = Users::UpdateService.new(@user, username: user_params[:username]).execute
options = { notice: "Username successfully changed" }
else options = if result[:status] == :success
message = @user.errors.full_messages.uniq.join('. ') { notice: "Username successfully changed" }
options = { alert: "Username change failed - #{message}" } else
end { alert: "Username change failed - #{result[:message]}" }
end
redirect_back_or_default(default: { action: 'show' }, options: options) redirect_back_or_default(default: { action: 'show' }, options: options)
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
...@@ -60,10 +60,11 @@ class SessionsController < Devise::SessionsController ...@@ -60,10 +60,11 @@ class SessionsController < Devise::SessionsController
return unless user && user.require_password? return unless user && user.require_password?
token = user.generate_reset_token Users::UpdateService.new(user).execute do |user|
user.save @token = user.generate_reset_token
end
redirect_to edit_user_password_path(reset_password_token: token), redirect_to edit_user_password_path(reset_password_token: @token),
notice: "Please create a password for your new account." notice: "Please create a password for your new account."
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
......
...@@ -300,4 +300,12 @@ module ApplicationHelper ...@@ -300,4 +300,12 @@ module ApplicationHelper
"https://www.twitter.com/#{name}" "https://www.twitter.com/#{name}"
end end
end end
def can_toggle_new_nav?
Rails.env.development?
end
def show_new_nav?
cookies["new_nav"] == "true"
end
end end
...@@ -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
...@@ -11,18 +11,21 @@ module HasStatus ...@@ -11,18 +11,21 @@ module HasStatus
class_methods do class_methods do
def status_sql def status_sql
scope = respond_to?(:exclude_ignored) ? exclude_ignored : all scope_relevant = respond_to?(:exclude_ignored) ? exclude_ignored : all
scope_warnings = respond_to?(:failed_but_allowed) ? failed_but_allowed : none
builds = scope.select('count(*)').to_sql
created = scope.created.select('count(*)').to_sql builds = scope_relevant.select('count(*)').to_sql
success = scope.success.select('count(*)').to_sql created = scope_relevant.created.select('count(*)').to_sql
manual = scope.manual.select('count(*)').to_sql success = scope_relevant.success.select('count(*)').to_sql
pending = scope.pending.select('count(*)').to_sql manual = scope_relevant.manual.select('count(*)').to_sql
running = scope.running.select('count(*)').to_sql pending = scope_relevant.pending.select('count(*)').to_sql
skipped = scope.skipped.select('count(*)').to_sql running = scope_relevant.running.select('count(*)').to_sql
canceled = scope.canceled.select('count(*)').to_sql skipped = scope_relevant.skipped.select('count(*)').to_sql
canceled = scope_relevant.canceled.select('count(*)').to_sql
warnings = scope_warnings.select('count(*) > 0').to_sql.presence || 'false'
"(CASE "(CASE
WHEN (#{builds})=(#{skipped}) AND (#{warnings}) THEN 'success'
WHEN (#{builds})=(#{skipped}) THEN 'skipped' WHEN (#{builds})=(#{skipped}) THEN 'skipped'
WHEN (#{builds})=(#{success}) THEN 'success' WHEN (#{builds})=(#{success}) THEN 'success'
WHEN (#{builds})=(#{created}) THEN 'created' WHEN (#{builds})=(#{created}) THEN 'created'
......
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.
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.
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.
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