Commit 0dbe6996 authored by Sean McGivern's avatar Sean McGivern

Merge branch 'master' into 'ee-sh-bump-grape-path-helpers'

# Conflicts:
#   Gemfile.lock
parents 3e3845a6 e1f53011
......@@ -423,13 +423,12 @@ gem 'sys-filesystem', '~> 1.1.6'
gem 'net-ntp'
# SSH host key support
gem 'net-ssh', '~> 4.2.0'
gem 'net-ssh', '~> 5.0'
gem 'sshkey', '~> 1.9.0'
# Required for ED25519 SSH host key support
group :ed25519 do
gem 'rbnacl-libsodium'
gem 'rbnacl', '~> 4.0'
gem 'ed25519', '~> 1.2'
gem 'bcrypt_pbkdf', '~> 1.0'
end
......
......@@ -185,6 +185,7 @@ GEM
json-jwt (~> 1.6)
dropzonejs-rails (0.7.2)
rails (> 3.1)
ed25519 (1.2.4)
elasticsearch (5.0.3)
elasticsearch-api (= 5.0.3)
elasticsearch-transport (= 5.0.3)
......@@ -539,7 +540,7 @@ GEM
mysql2 (0.4.10)
net-ldap (0.16.0)
net-ntp (2.1.3)
net-ssh (4.2.0)
net-ssh (5.0.1)
netrc (0.11.0)
nokogiri (1.8.2)
mini_portile2 (~> 2.3.0)
......@@ -725,10 +726,6 @@ GEM
ffi (>= 0.5.0, < 2)
rblineprof (0.3.6)
debugger-ruby_core_source (~> 1.3)
rbnacl (4.0.2)
ffi
rbnacl-libsodium (1.0.11)
rbnacl (>= 3.0.1)
rdoc (6.0.4)
re2 (1.1.1)
recaptcha (3.0.0)
......@@ -1046,6 +1043,7 @@ DEPENDENCIES
doorkeeper (~> 4.3)
doorkeeper-openid_connect (~> 1.3)
dropzonejs-rails (~> 0.7.1)
ed25519 (~> 1.2)
elasticsearch-api (= 5.0.3)
elasticsearch-model (~> 0.1.9)
elasticsearch-rails (~> 0.1.9)
......@@ -1125,7 +1123,7 @@ DEPENDENCIES
mysql2 (~> 0.4.10)
net-ldap
net-ntp
net-ssh (~> 4.2.0)
net-ssh (~> 5.0)
nokogiri (~> 1.8.2)
oauth2 (~> 1.4)
octokit (~> 4.9)
......@@ -1167,8 +1165,6 @@ DEPENDENCIES
rainbow (~> 2.2)
raindrops (~> 0.18)
rblineprof (~> 0.3.6)
rbnacl (~> 4.0)
rbnacl-libsodium
rdoc (~> 6.0)
re2 (~> 1.1.1)
recaptcha (~> 3.0)
......
......@@ -187,6 +187,7 @@ GEM
json-jwt (~> 1.6)
dropzonejs-rails (0.7.4)
rails (> 3.1)
ed25519 (1.2.4)
elasticsearch (5.0.3)
elasticsearch-api (= 5.0.3)
elasticsearch-transport (= 5.0.3)
......@@ -534,7 +535,7 @@ GEM
mysql2 (0.4.10)
net-ldap (0.16.1)
net-ntp (2.1.3)
net-ssh (4.2.0)
net-ssh (5.0.1)
netrc (0.11.0)
nio4r (2.3.1)
nokogiri (1.8.2)
......@@ -725,10 +726,6 @@ GEM
ffi (>= 0.5.0, < 2)
rblineprof (0.3.7)
debugger-ruby_core_source (~> 1.3)
rbnacl (4.0.2)
ffi
rbnacl-libsodium (1.0.16)
rbnacl (>= 3.0.1)
rdoc (6.0.4)
re2 (1.1.1)
recaptcha (3.4.0)
......@@ -1047,6 +1044,7 @@ DEPENDENCIES
doorkeeper (~> 4.3)
doorkeeper-openid_connect (~> 1.3)
dropzonejs-rails (~> 0.7.1)
ed25519 (~> 1.2)
elasticsearch-api (= 5.0.3)
elasticsearch-model (~> 0.1.9)
elasticsearch-rails (~> 0.1.9)
......@@ -1124,7 +1122,7 @@ DEPENDENCIES
mysql2 (~> 0.4.10)
net-ldap
net-ntp
net-ssh (~> 4.2.0)
net-ssh (~> 5.0)
nokogiri (~> 1.8.2)
oauth2 (~> 1.4)
octokit (~> 4.9)
......@@ -1167,8 +1165,6 @@ DEPENDENCIES
rainbow (~> 2.2)
raindrops (~> 0.18)
rblineprof (~> 0.3.6)
rbnacl (~> 4.0)
rbnacl-libsodium
rdoc (~> 6.0)
re2 (~> 1.1.1)
recaptcha (~> 3.0)
......
<script>
import { mapActions, mapState } from 'vuex';
import _ from 'underscore';
import { __ } from '../../../locale';
import tooltip from '../../../vue_shared/directives/tooltip';
import Icon from '../../../vue_shared/components/icon.vue';
import ScrollButton from './detail/scroll_button.vue';
import JobDescription from './detail/description.vue';
const scrollPositions = {
top: 0,
bottom: 1,
};
export default {
directives: {
tooltip,
},
components: {
Icon,
ScrollButton,
JobDescription,
},
data() {
return {
scrollPos: scrollPositions.top,
};
},
computed: {
...mapState('pipelines', ['detailJob']),
isScrolledToBottom() {
return this.scrollPos === scrollPositions.bottom;
},
isScrolledToTop() {
return this.scrollPos === scrollPositions.top;
},
jobOutput() {
return this.detailJob.output || __('No messages were logged');
},
},
mounted() {
this.getTrace();
},
methods: {
...mapActions('pipelines', ['fetchJobTrace', 'setDetailJob']),
scrollDown() {
if (this.$refs.buildTrace) {
this.$refs.buildTrace.scrollTo(0, this.$refs.buildTrace.scrollHeight);
}
},
scrollUp() {
if (this.$refs.buildTrace) {
this.$refs.buildTrace.scrollTo(0, 0);
}
},
scrollBuildLog: _.throttle(function buildLogScrollDebounce() {
const { scrollTop } = this.$refs.buildTrace;
const { offsetHeight, scrollHeight } = this.$refs.buildTrace;
if (scrollTop + offsetHeight === scrollHeight) {
this.scrollPos = scrollPositions.bottom;
} else if (scrollTop === 0) {
this.scrollPos = scrollPositions.top;
} else {
this.scrollPos = '';
}
}),
getTrace() {
return this.fetchJobTrace().then(() => this.scrollDown());
},
},
};
</script>
<template>
<div class="ide-pipeline build-page d-flex flex-column flex-fill">
<header class="ide-job-header d-flex align-items-center">
<button
class="btn btn-default btn-sm d-flex"
@click="setDetailJob(null)"
>
<icon
name="chevron-left"
/>
{{ __('View jobs') }}
</button>
</header>
<div class="top-bar d-flex border-left-0">
<job-description
:job="detailJob"
/>
<div class="controllers ml-auto">
<a
v-tooltip
:title="__('Show complete raw log')"
data-placement="top"
data-container="body"
class="controllers-buttons"
:href="detailJob.rawPath"
target="_blank"
>
<i
aria-hidden="true"
class="fa fa-file-text-o"
></i>
</a>
<scroll-button
direction="up"
:disabled="isScrolledToTop"
@click="scrollUp"
/>
<scroll-button
direction="down"
:disabled="isScrolledToBottom"
@click="scrollDown"
/>
</div>
</div>
<pre
class="build-trace mb-0 h-100"
ref="buildTrace"
@scroll="scrollBuildLog"
>
<code
class="bash"
v-html="jobOutput"
>
</code>
<div
v-show="detailJob.isLoading"
class="build-loader-animation"
>
</div>
</pre>
</div>
</template>
<script>
import Icon from '../../../../vue_shared/components/icon.vue';
import CiIcon from '../../../../vue_shared/components/ci_icon.vue';
export default {
components: {
Icon,
CiIcon,
},
props: {
job: {
type: Object,
required: true,
},
},
computed: {
jobId() {
return `#${this.job.id}`;
},
},
};
</script>
<template>
<div class="d-flex align-items-center">
<ci-icon
class="d-flex"
:status="job.status"
:borderless="true"
:size="24"
/>
<span class="prepend-left-8">
{{ job.name }}
<a
:href="job.path"
target="_blank"
class="ide-external-link"
>
{{ jobId }}
<icon
name="external-link"
:size="12"
/>
</a>
</span>
</div>
</template>
<script>
import { __ } from '../../../../locale';
import Icon from '../../../../vue_shared/components/icon.vue';
import tooltip from '../../../../vue_shared/directives/tooltip';
const directions = {
up: 'up',
down: 'down',
};
export default {
directives: {
tooltip,
},
components: {
Icon,
},
props: {
direction: {
type: String,
required: true,
validator(value) {
return Object.keys(directions).includes(value);
},
},
disabled: {
type: Boolean,
required: true,
},
},
computed: {
tooltipTitle() {
return this.direction === directions.up ? __('Scroll to top') : __('Scroll to bottom');
},
iconName() {
return `scroll_${this.direction}`;
},
},
methods: {
clickedScroll() {
this.$emit('click');
},
},
};
</script>
<template>
<div
v-tooltip
class="controllers-buttons"
data-container="body"
data-placement="top"
:title="tooltipTitle"
>
<button
class="btn-scroll btn-transparent btn-blank"
type="button"
:disabled="disabled"
@click="clickedScroll"
>
<icon
:name="iconName"
/>
</button>
</div>
</template>
<script>
import Icon from '../../../vue_shared/components/icon.vue';
import CiIcon from '../../../vue_shared/components/ci_icon.vue';
import JobDescription from './detail/description.vue';
export default {
components: {
Icon,
CiIcon,
JobDescription,
},
props: {
job: {
......@@ -18,29 +16,29 @@ export default {
return `#${this.job.id}`;
},
},
methods: {
clickViewLog() {
this.$emit('clickViewLog', this.job);
},
},
};
</script>
<template>
<div class="ide-job-item">
<ci-icon
:status="job.status"
:borderless="true"
:size="24"
<job-description
class="append-right-default"
:job="job"
/>
<span class="prepend-left-8">
{{ job.name }}
<a
:href="job.path"
target="_blank"
class="ide-external-link"
<div class="ml-auto align-self-center">
<button
v-if="job.started"
type="button"
class="btn btn-default btn-sm"
@click="clickViewLog"
>
{{ jobId }}
<icon
name="external-link"
:size="12"
/>
</a>
</span>
{{ __('View log') }}
</button>
</div>
</div>
</template>
......@@ -19,7 +19,7 @@ export default {
},
},
methods: {
...mapActions('pipelines', ['fetchJobs', 'toggleStageCollapsed']),
...mapActions('pipelines', ['fetchJobs', 'toggleStageCollapsed', 'setDetailJob']),
},
};
</script>
......@@ -38,6 +38,7 @@ export default {
:stage="stage"
@fetch="fetchJobs"
@toggleCollapsed="toggleStageCollapsed"
@clickViewLog="setDetailJob"
/>
</template>
</div>
......
......@@ -48,6 +48,9 @@ export default {
toggleCollapsed() {
this.$emit('toggleCollapsed', this.stage.id);
},
clickViewLog(job) {
this.$emit('clickViewLog', job);
},
},
};
</script>
......@@ -101,6 +104,7 @@ export default {
v-for="job in stage.jobs"
:key="job.id"
:job="job"
@clickViewLog="clickViewLog"
/>
</template>
</div>
......
......@@ -4,6 +4,7 @@ import tooltip from '../../../vue_shared/directives/tooltip';
import Icon from '../../../vue_shared/components/icon.vue';
import { rightSidebarViews } from '../../constants';
import PipelinesList from '../pipelines/list.vue';
import JobsDetail from '../jobs/detail.vue';
export default {
directives: {
......@@ -12,9 +13,16 @@ export default {
components: {
Icon,
PipelinesList,
JobsDetail,
},
computed: {
...mapState(['rightPane']),
pipelinesActive() {
return (
this.rightPane === rightSidebarViews.pipelines ||
this.rightPane === rightSidebarViews.jobsDetail
);
},
},
methods: {
...mapActions(['setRightPane']),
......@@ -48,7 +56,7 @@ export default {
:title="__('Pipelines')"
class="ide-sidebar-link is-right"
:class="{
active: rightPane === $options.rightSidebarViews.pipelines
active: pipelinesActive
}"
type="button"
@click="clickTab($event, $options.rightSidebarViews.pipelines)"
......
......@@ -23,4 +23,5 @@ export const viewerTypes = {
export const rightSidebarViews = {
pipelines: 'pipelines-list',
jobsDetail: 'jobs-detail',
};
......@@ -4,6 +4,7 @@ import { __ } from '../../../../locale';
import flash from '../../../../flash';
import Poll from '../../../../lib/utils/poll';
import service from '../../../services';
import { rightSidebarViews } from '../../../constants';
import * as types from './mutation_types';
let eTagPoll;
......@@ -77,4 +78,28 @@ export const fetchJobs = ({ dispatch }, stage) => {
export const toggleStageCollapsed = ({ commit }, stageId) =>
commit(types.TOGGLE_STAGE_COLLAPSE, stageId);
export const setDetailJob = ({ commit, dispatch }, job) => {
commit(types.SET_DETAIL_JOB, job);
dispatch('setRightPane', job ? rightSidebarViews.jobsDetail : rightSidebarViews.pipelines, {
root: true,
});
};
export const requestJobTrace = ({ commit }) => commit(types.REQUEST_JOB_TRACE);
export const receiveJobTraceError = ({ commit }) => {
flash(__('Error fetching job trace'));
commit(types.RECEIVE_JOB_TRACE_ERROR);
};
export const receiveJobTraceSuccess = ({ commit }, data) =>
commit(types.RECEIVE_JOB_TRACE_SUCCESS, data);
export const fetchJobTrace = ({ dispatch, state }) => {
dispatch('requestJobTrace');
return axios
.get(`${state.detailJob.path}/trace`, { params: { format: 'json' } })
.then(({ data }) => dispatch('receiveJobTraceSuccess', data))
.catch(() => dispatch('receiveJobTraceError'));
};
export default () => {};
......@@ -7,3 +7,9 @@ export const RECEIVE_JOBS_ERROR = 'RECEIVE_JOBS_ERROR';
export const RECEIVE_JOBS_SUCCESS = 'RECEIVE_JOBS_SUCCESS';
export const TOGGLE_STAGE_COLLAPSE = 'TOGGLE_STAGE_COLLAPSE';
export const SET_DETAIL_JOB = 'SET_DETAIL_JOB';
export const REQUEST_JOB_TRACE = 'REQUEST_JOB_TRACE';
export const RECEIVE_JOB_TRACE_ERROR = 'RECEIVE_JOB_TRACE_ERROR';
export const RECEIVE_JOB_TRACE_SUCCESS = 'RECEIVE_JOB_TRACE_SUCCESS';
......@@ -63,4 +63,17 @@ export default {
isCollapsed: stage.id === id ? !stage.isCollapsed : stage.isCollapsed,
}));
},
[types.SET_DETAIL_JOB](state, job) {
state.detailJob = { ...job };
},
[types.REQUEST_JOB_TRACE](state) {
state.detailJob.isLoading = true;
},
[types.RECEIVE_JOB_TRACE_ERROR](state) {
state.detailJob.isLoading = false;
},
[types.RECEIVE_JOB_TRACE_SUCCESS](state, data) {
state.detailJob.isLoading = false;
state.detailJob.output = data.html;
},
};
......@@ -3,4 +3,5 @@ export default () => ({
isLoadingJobs: false,
latestPipeline: null,
stages: [],
detailJob: null,
});
......@@ -4,4 +4,8 @@ export const normalizeJob = job => ({
name: job.name,
status: job.status,
path: job.build_path,
rawPath: `${job.build_path}/raw`,
started: job.started,
output: '',
isLoading: false,
});
......@@ -58,7 +58,7 @@ class ImporterStatus {
job.find('.import-target').html(`<a href="${data.full_path}">${data.full_path}</a>`);
$('table.import-jobs tbody').prepend(job);
job.addClass('active');
job.addClass('table-active');
const connectingVerb = this.ciCdOnly ? __('connecting') : __('importing');
job.find('.import-actions').html(sprintf(
_.escape(__('%{loadingIcon} Started')), {
......@@ -67,7 +67,15 @@ class ImporterStatus {
false,
));
})
.catch(() => flash(__('An error occurred while importing project')));
.catch((error) => {
let details = error;
if (error.response && error.response.data && error.response.data.errors) {
details = error.response.data.errors;
}
flash(__(`An error occurred while importing project: ${details}`));
});
}
autoUpdate() {
......@@ -81,7 +89,7 @@ class ImporterStatus {
switch (job.import_status) {
case 'finished':
jobItem.removeClass('active').addClass('success');
jobItem.removeClass('table-active').addClass('table-success');
statusField.html(`<span><i class="fa fa-check"></i> ${__('Done')}</span>`);
break;
case 'scheduled':
......
......@@ -42,6 +42,9 @@ export default {
jobStarted() {
return !this.job.started === false;
},
headerTime() {
return this.jobStarted ? this.job.started : this.job.created_at;
},
},
watch: {
job() {
......@@ -73,7 +76,7 @@ export default {
:status="status"
item-name="Job"
:item-id="job.id"
:time="job.created_at"
:time="headerTime"
:user="job.user"
:actions="actions"
:has-sidebar-button="true"
......
......@@ -174,7 +174,10 @@ export default {
:tags-path="tagsPath"
:show-legend="showLegend"
:small-graph="forceSmallGraph"
/>
>
<!-- EE content -->
{{ null }}
</graph>
</graph-group>
</div>
<empty-state
......
......@@ -232,9 +232,14 @@ export default {
@mouseover="showFlagContent = true"
@mouseleave="showFlagContent = false"
>
<h5 class="text-center graph-title">
{{ graphData.title }}
</h5>
<div class="prometheus-graph-header">
<h5 class="prometheus-graph-title">
{{ graphData.title }}
</h5>
<div class="prometheus-graph-widgets">
<slot></slot>
</div>
</div>
<div
class="prometheus-svg-container"
:style="paddingBottomRootSvg"
......
......@@ -56,6 +56,7 @@ export default {
<gl-modal
:id="`modal-peek-${metric}-details`"
:header-title-text="header"
modal-size="lg"
class="performance-bar-modal"
>
<table
......@@ -70,7 +71,7 @@ export default {
<td
v-for="key in keys"
:key="key"
class="break-word"
class="break-word all-words"
>
{{ item[key] }}
</td>
......
<script>
const buttonVariants = ['danger', 'primary', 'success', 'warning'];
const sizeVariants = ['sm', 'md', 'lg'];
export default {
name: 'GlModal',
props: {
id: {
type: String,
required: false,
default: null,
},
modalSize: {
type: String,
required: false,
default: 'md',
validator: value => sizeVariants.includes(value),
},
headerTitleText: {
type: String,
required: false,
......@@ -27,7 +33,11 @@ export default {
default: '',
},
},
computed: {
modalSizeClass() {
return this.modalSize === 'md' ? '' : `modal-${this.modalSize}`;
},
},
methods: {
emitCancel(event) {
this.$emit('cancel', event);
......@@ -48,6 +58,7 @@ export default {
>
<div
class="modal-dialog"
:class="modalSizeClass"
role="document"
>
<div class="modal-content">
......
......@@ -150,6 +150,16 @@ table {
color: $gl-text-color-secondary !important;
}
.bg-success,
.bg-primary,
.bg-info,
.bg-danger,
.bg-warning {
.card-header {
color: $white-light;
}
}
// Polyfill deprecated selectors
.hidden {
......
......@@ -456,6 +456,10 @@ img.emoji {
.break-word {
word-wrap: break-word;
&.all-words {
word-break: break-word;
}
}
/** COMMON CLASSES **/
......
......@@ -3,26 +3,26 @@
*/
@mixin gitlab-theme(
$color-100,
$color-200,
$color-500,
$color-700,
$color-800,
$color-900,
$location-badge-color,
$search-and-nav-links,
$active-tab-border,
$border-and-box-shadow,
$sidebar-text,
$nav-svg-color,
$color-alternate
) {
// Header
.navbar-gitlab {
background-color: $color-900;
background-color: $nav-svg-color;
.navbar-collapse {
color: $color-200;
color: $search-and-nav-links;
}
.container-fluid {
.navbar-toggler {
border-left: 1px solid lighten($color-700, 10%);
border-left: 1px solid lighten($border-and-box-shadow, 10%);
}
}
......@@ -31,40 +31,40 @@
> li {
> a:hover,
> a:focus {
background-color: rgba($color-200, 0.2);
background-color: rgba($search-and-nav-links, 0.2);
}
&.active > a,
&.dropdown.show > a {
color: $color-900;
color: $nav-svg-color;
background-color: $color-alternate;
}
&.line-separator {
border-left: 1px solid rgba($color-200, 0.2);
border-left: 1px solid rgba($search-and-nav-links, 0.2);
}
}
}
.navbar-sub-nav {
color: $color-200;
color: $search-and-nav-links;
}
.nav {
> li {
color: $color-200;
color: $search-and-nav-links;
> a {
&.header-user-dropdown-toggle {
.header-user-avatar {
border-color: $color-200;
border-color: $search-and-nav-links;
}
}
&:hover,
&:focus {
@include media-breakpoint-up(sm) {
background-color: rgba($color-200, 0.2);
background-color: rgba($search-and-nav-links, 0.2);
}
svg {
......@@ -75,12 +75,12 @@
&.active > a,
&.dropdown.show > a {
color: $color-900;
color: $nav-svg-color;
background-color: $color-alternate;
&:hover {
svg {
fill: $color-900;
fill: $nav-svg-color;
}
}
}
......@@ -88,7 +88,7 @@
.impersonated-user,
.impersonated-user:hover {
svg {
fill: $color-900;
fill: $nav-svg-color;
}
}
}
......@@ -99,34 +99,34 @@
> a {
&:hover,
&:focus {
background-color: rgba($color-200, 0.2);
background-color: rgba($search-and-nav-links, 0.2);
}
}
}
.search {
form {
background-color: rgba($color-200, 0.2);
background-color: rgba($search-and-nav-links, 0.2);
&:hover {
background-color: rgba($color-200, 0.3);
background-color: rgba($search-and-nav-links, 0.3);
}
}
.location-badge {
color: $color-100;
background-color: rgba($color-200, 0.1);
border-right: 1px solid $color-800;
color: $location-badge-color;
background-color: rgba($search-and-nav-links, 0.1);
border-right: 1px solid $sidebar-text;
}
.search-input::placeholder {
color: rgba($color-200, 0.8);
color: rgba($search-and-nav-links, 0.8);
}
.search-input-wrap {
.search-icon,
.clear-icon {
fill: rgba($color-200, 0.8);
fill: rgba($search-and-nav-links, 0.8);
}
}
......@@ -141,38 +141,34 @@
.search-input-wrap {
.search-icon {
fill: rgba($color-200, 0.8);
fill: rgba($search-and-nav-links, 0.8);
}
}
}
}
.btn-sign-in {
background-color: $color-100;
color: $color-900;
}
// Sidebar
.nav-sidebar li.active {
box-shadow: inset 4px 0 0 $color-700;
box-shadow: inset 4px 0 0 $border-and-box-shadow;
> a {
color: $color-800;
color: $sidebar-text;
}
svg {
fill: $color-800;
fill: $sidebar-text;
}
}
.sidebar-top-level-items > li.active .badge.badge-pill {
color: $color-800;
color: $sidebar-text;
}
.nav-links li {
&.active a,
a.active {
border-bottom: 2px solid $color-500;
border-bottom: 2px solid $active-tab-border;
.badge.badge-pill {
font-weight: $gl-font-weight-bold;
......@@ -181,27 +177,27 @@
}
.branch-header-title {
color: $color-700;
color: $border-and-box-shadow;
}
.ide-file-list .file.file-active {
color: $color-700;
color: $border-and-box-shadow;
}
.ide-sidebar-link {
&.active {
color: $color-700;
box-shadow: inset 3px 0 $color-700;
color: $border-and-box-shadow;
box-shadow: inset 3px 0 $border-and-box-shadow;
&.is-right {
box-shadow: inset -3px 0 $color-700;
box-shadow: inset -3px 0 $border-and-box-shadow;
}
}
}
}
body {
&.ui_indigo {
&.ui-indigo {
@include gitlab-theme(
$indigo-100,
$indigo-200,
......@@ -213,19 +209,19 @@ body {
);
}
&.ui_dark {
&.ui-light-indigo {
@include gitlab-theme(
$theme-gray-100,
$theme-gray-200,
$theme-gray-500,
$theme-gray-700,
$theme-gray-800,
$theme-gray-900,
$indigo-100,
$indigo-200,
$indigo-500,
$indigo-500,
$indigo-700,
$indigo-700,
$white-light
);
}
&.ui_blue {
&.ui-blue {
@include gitlab-theme(
$theme-blue-100,
$theme-blue-200,
......@@ -237,7 +233,19 @@ body {
);
}
&.ui_green {
&.ui-light-blue {
@include gitlab-theme(
$theme-light-blue-100,
$theme-light-blue-200,
$theme-light-blue-500,
$theme-light-blue-500,
$theme-light-blue-700,
$theme-light-blue-700,
$white-light
);
}
&.ui-green {
@include gitlab-theme(
$theme-green-100,
$theme-green-200,
......@@ -249,7 +257,55 @@ body {
);
}
&.ui_light {
&.ui-light-green {
@include gitlab-theme(
$theme-green-100,
$theme-green-200,
$theme-green-500,
$theme-green-500,
$theme-light-green-700,
$theme-light-green-700,
$white-light
);
}
&.ui-red {
@include gitlab-theme(
$theme-red-100,
$theme-red-200,
$theme-red-500,
$theme-red-700,
$theme-red-800,
$theme-red-900,
$white-light
);
}
&.ui-light-red {
@include gitlab-theme(
$theme-light-red-100,
$theme-light-red-200,
$theme-light-red-500,
$theme-light-red-500,
$theme-light-red-700,
$theme-light-red-700,
$white-light
);
}
&.ui-dark {
@include gitlab-theme(
$theme-gray-100,
$theme-gray-200,
$theme-gray-500,
$theme-gray-700,
$theme-gray-800,
$theme-gray-900,
$white-light
);
}
&.ui-light {
@include gitlab-theme(
$theme-gray-900,
$theme-gray-700,
......
......@@ -437,6 +437,8 @@
}
.btn-sign-in {
background-color: $indigo-100;
color: $indigo-900;
margin-top: 3px;
font-weight: $gl-font-weight-bold;
......
......@@ -118,6 +118,15 @@ $theme-blue-800: #25496e;
$theme-blue-900: #1a3652;
$theme-blue-950: #0f2235;
$theme-light-blue-50: #f2f7fc;
$theme-light-blue-100: #ebf1f7;
$theme-light-blue-200: #c9dcf2;
$theme-light-blue-300: #83abd4;
$theme-light-blue-400: #4d86bf;
$theme-light-blue-500: #367cc2;
$theme-light-blue-600: #3771ab;
$theme-light-blue-700: #2261a1;
$theme-green-50: #f2faf6;
$theme-green-100: #e4f3ea;
$theme-green-200: #c0dfcd;
......@@ -130,6 +139,29 @@ $theme-green-800: #145d33;
$theme-green-900: #0d4524;
$theme-green-950: #072d16;
$theme-light-green-700: #156b39;
$theme-red-50: #fcf4f2;
$theme-red-100: #fae9e6;
$theme-red-200: #ebcac5;
$theme-red-300: #d99b91;
$theme-red-400: #b0655a;
$theme-red-500: #ad4a3b;
$theme-red-600: #9e4133;
$theme-red-700: #912f20;
$theme-red-800: #78291d;
$theme-red-900: #691a16;
$theme-red-950: #36140f;
$theme-light-red-50: #fff6f5;
$theme-light-red-100: #fae2de;
$theme-light-red-200: #f7d5d0;
$theme-light-red-300: #d9796a;
$theme-light-red-400: #cf604e;
$theme-light-red-500: #c24b38;
$theme-light-red-600: #b03927;
$theme-light-red-700: #a62e21;
$black: #000;
$black-transparent: rgba(0, 0, 0, 0.3);
$almost-black: #242424;
......
......@@ -75,6 +75,7 @@
.top-bar {
height: 35px;
min-height: 35px;
background: $gray-light;
border: 1px solid $border-color;
color: $gl-text-color;
......
......@@ -23,7 +23,6 @@
}
.btn-group {
> a {
color: $gl-text-color-secondary;
}
......@@ -407,6 +406,7 @@
.prometheus-graph {
flex: 1 0 auto;
min-width: 450px;
max-width: 100%;
padding: $gl-padding / 2;
h5 {
......@@ -418,6 +418,17 @@
}
}
.prometheus-graph-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: $gl-padding-8;
h5 {
margin: 0;
}
}
.prometheus-graph-cursor {
position: absolute;
background: $theme-gray-600;
......
@mixin application-theme-preview($color-1, $color-2, $color-3, $color-4) {
.one {
background-color: $color-1;
border-top-left-radius: $border-radius-default;
}
.two {
background-color: $color-2;
border-top-right-radius: $border-radius-default;
}
.three {
background-color: $color-3;
border-bottom-left-radius: $border-radius-default;
}
.four {
background-color: $color-4;
border-bottom-right-radius: $border-radius-default;
}
}
.multi-file-editor-options {
label {
margin-right: 20px;
......@@ -38,44 +16,61 @@
.application-theme {
label {
margin-right: 20px;
margin: 0 $gl-padding $gl-padding 0;
text-align: center;
}
.preview {
font-size: 0;
margin-bottom: 10px;
height: 48px;
border-radius: 4px;
min-width: 135px;
margin-bottom: $gl-padding-8;
&.ui-indigo {
background-color: $indigo-900;
}
&.ui-light-indigo {
background-color: $indigo-700;
}
&.indigo {
@include application-theme-preview($indigo-900, $indigo-700, $indigo-800, $indigo-500);
&.ui-blue {
background-color: $theme-blue-900;
}
&.dark {
@include application-theme-preview($theme-gray-900, $theme-gray-700, $theme-gray-800, $theme-gray-600);
&.ui-light-blue {
background-color: $theme-light-blue-700;
}
&.light {
@include application-theme-preview($theme-gray-600, $theme-gray-200, $theme-gray-400, $theme-gray-100);
&.ui-green {
background-color: $theme-green-900;
}
&.blue {
@include application-theme-preview($theme-blue-900, $theme-blue-700, $theme-blue-800, $theme-blue-500);
&.ui-light-green {
background-color: $theme-light-green-700;
}
&.green {
@include application-theme-preview($theme-green-900, $theme-green-700, $theme-green-800, $theme-green-500);
&.ui-red {
background-color: $theme-red-900;
}
&.ui-light-red {
background-color: $theme-light-red-700;
}
&.ui-dark {
background-color: $theme-gray-900;
}
&.ui-light {
background-color: $theme-gray-200;
}
}
.preview-row {
display: block;
}
.quadrant {
display: inline-block;
height: 50px;
width: 80px;
}
}
.syntax-theme {
......
......@@ -1146,8 +1146,13 @@
}
.ide-external-link {
position: relative;
svg {
display: none;
position: absolute;
top: 2px;
right: -$gl-padding;
}
&:hover,
......@@ -1178,6 +1183,8 @@
display: flex;
flex-direction: column;
height: 100%;
margin-top: -$grid-size;
margin-bottom: -$grid-size;
.empty-state {
margin-top: auto;
......@@ -1194,6 +1201,17 @@
margin: 0;
}
}
.build-trace,
.top-bar {
margin-left: -$gl-padding;
}
&.build-page .top-bar {
top: 0;
font-size: 12px;
border-top-right-radius: $border-radius-default;
}
}
.ide-pipeline-list {
......@@ -1202,7 +1220,7 @@
}
.ide-pipeline-header {
min-height: 50px;
min-height: 55px;
padding-left: $gl-padding;
padding-right: $gl-padding;
......@@ -1222,8 +1240,7 @@
.ci-status-icon {
display: flex;
justify-content: center;
height: 20px;
margin-top: -2px;
min-width: 24px;
overflow: hidden;
}
}
......@@ -1253,3 +1270,7 @@
overflow: hidden;
text-overflow: ellipsis;
}
.ide-job-header {
min-height: 60px;
}
......@@ -102,10 +102,6 @@
.form-text.text-muted {
margin-top: 0;
}
.label-light {
margin-bottom: 0;
}
}
.settings-list-icon {
......
......@@ -25,4 +25,8 @@ class Import::BaseController < ApplicationController
current_user.namespace
end
def project_save_error(project)
project.errors.full_messages.join(', ')
end
end
......@@ -55,7 +55,7 @@ class Import::BitbucketController < Import::BaseController
if project.persisted?
render json: ProjectSerializer.new.represent(project)
else
render json: { errors: project.errors.full_messages }, status: :unprocessable_entity
render json: { errors: project_save_error(project) }, status: :unprocessable_entity
end
else
render json: { errors: 'This namespace has already been taken! Please choose another one.' }, status: :unprocessable_entity
......
......@@ -66,7 +66,7 @@ class Import::FogbugzController < Import::BaseController
if project.persisted?
render json: ProjectSerializer.new.represent(project)
else
render json: { errors: project.errors.full_messages }, status: :unprocessable_entity
render json: { errors: project_save_error(project) }, status: :unprocessable_entity
end
end
......
......@@ -50,7 +50,7 @@ class Import::GithubController < Import::BaseController
if project.persisted?
render json: ProjectSerializer.new.represent(project)
else
render json: { errors: project.errors.full_messages }, status: :unprocessable_entity
render json: { errors: project_save_error(project) }, status: :unprocessable_entity
end
else
render json: { errors: 'This namespace has already been taken! Please choose another one.' }, status: :unprocessable_entity
......
......@@ -32,7 +32,7 @@ class Import::GitlabController < Import::BaseController
if project.persisted?
render json: ProjectSerializer.new.represent(project)
else
render json: { errors: project.errors.full_messages }, status: :unprocessable_entity
render json: { errors: project_save_error(project) }, status: :unprocessable_entity
end
else
render json: { errors: 'This namespace has already been taken! Please choose another one.' }, status: :unprocessable_entity
......
......@@ -92,7 +92,7 @@ class Import::GoogleCodeController < Import::BaseController
if project.persisted?
render json: ProjectSerializer.new.represent(project)
else
render json: { errors: project.errors.full_messages }, status: :unprocessable_entity
render json: { errors: project_save_error(project) }, status: :unprocessable_entity
end
end
......
......@@ -31,15 +31,14 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
end
def show
validates_merge_request
close_merge_request_without_source_project
check_if_can_be_merged
# Return if the response has already been rendered
return if response_body
close_merge_request_if_no_source_project
mark_merge_request_mergeable
respond_to do |format|
format.html do
# use next to appease Rubocop
next render('invalid') if target_branch_missing?
# Build a note object for comment form
@note = @project.notes.new(noteable: @merge_request)
......@@ -238,20 +237,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
alias_method :issuable, :merge_request
alias_method :awardable, :merge_request
def validates_merge_request
# Show git not found page
# if there is no saved commits between source & target branch
if @merge_request.has_no_commits?
# and if target branch doesn't exist
return invalid_mr unless @merge_request.target_branch_exists?
end
end
def invalid_mr
# Render special view for MR with removed target branch
render 'invalid'
end
def merge_params
params.permit(merge_params_attributes)
end
......@@ -265,7 +250,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
@merge_request.head_pipeline && @merge_request.head_pipeline.active?
end
def close_merge_request_without_source_project
def close_merge_request_if_no_source_project
if !@merge_request.source_project && @merge_request.open?
@merge_request.close
end
......@@ -273,7 +258,11 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
private
def check_if_can_be_merged
def target_branch_missing?
@merge_request.has_no_commits? && !@merge_request.target_branch_exists?
end
def mark_merge_request_mergeable
@merge_request.check_if_can_be_merged
end
......
......@@ -410,11 +410,11 @@ module ProjectsHelper
def project_status_css_class(status)
case status
when "started"
"active"
"table-active"
when "failed"
"danger"
"table-danger"
when "finished"
"success"
"table-success"
end
end
......
......@@ -6,7 +6,7 @@ module WorkhorseHelper
headers.store(*Gitlab::Workhorse.send_git_blob(repository, blob))
headers['Content-Disposition'] = 'inline'
headers['Content-Type'] = safe_content_type(blob)
head :ok # 'render nothing: true' messes up the Content-Type
render plain: ""
end
# Send a Git diff through Workhorse
......
......@@ -70,6 +70,7 @@ module Ci
scope :last_month, ->() { where('created_at > ?', Date.today - 1.month) }
scope :manual_actions, ->() { where(when: :manual, status: COMPLETED_STATUSES + [:manual]) }
scope :ref_protected, -> { where(protected: true) }
scope :with_live_trace, -> { where('EXISTS (?)', Ci::BuildTraceChunk.where('ci_builds.id = ci_build_trace_chunks.build_id').select(1)) }
scope :matches_tag_ids, -> (tag_ids) do
matcher = ::ActsAsTaggableOn::Tagging
......
......@@ -47,6 +47,6 @@ module ExclusiveLeaseGuard
end
def log_error(message, extra_args = {})
logger.error(message)
Rails.logger.error(message)
end
end
......@@ -68,6 +68,7 @@ module Projects
message = "Unable to save #{e.record.type}: #{e.record.errors.full_messages.join(", ")} "
fail(error: message)
rescue => e
@project.errors.add(:base, e.message) if @project
fail(error: e.message)
end
......@@ -146,7 +147,6 @@ module Projects
Rails.logger.error(log_message)
if @project
@project.errors.add(:base, message)
@project.mark_import_as_failed(message) if @project.persisted? && @project.import?
end
......
......@@ -67,7 +67,7 @@
%th Projects
%th Jobs
%th Tags
%th= link_to 'Last contact', admin_runners_path(params.slice(:search).merge(sort: 'contacted_asc'))
%th= link_to 'Last contact', admin_runners_path(safe_params.slice(:search).merge(sort: 'contacted_asc'))
%th
- @runners.each do |runner|
......
!!! 5
%html.devise-layout-html{ class: system_message_class }
= render "layouts/head"
%body.ui_indigo.login-page.application.navless{ data: { page: body_data_page } }
%body.ui-indigo.login-page.application.navless{ data: { page: body_data_page } }
= header_message
.page-wrap
= render "layouts/header/empty"
......
!!! 5
%html{ lang: "en", class: system_message_class }
= render "layouts/head"
%body.ui_indigo.login-page.application.navless
%body.ui-indigo.login-page.application.navless
= header_message
= render "layouts/header/empty"
= render "layouts/broadcast"
......
......@@ -9,13 +9,7 @@
.col-lg-8.application-theme
- Gitlab::Themes.each do |theme|
= label_tag do
.preview{ class: theme.name.downcase }
.preview-row
.quadrant.one
.quadrant.two
.preview-row
.quadrant.three
.quadrant.four
.preview{ class: theme.css_class }
= f.radio_button :theme_id, theme.id, checked: Gitlab::Themes.for_user(@user).id == theme.id
= theme.name
......
......@@ -43,7 +43,7 @@
.settings-header
%h4
= _('Variables')
= link_to icon('question-circle'), help_page_path('ci/variables/README', anchor: 'secret-variables'), target: '_blank', rel: 'noopener noreferrer'
= link_to icon('question-circle'), help_page_path('ci/variables/README', anchor: 'variables'), target: '_blank', rel: 'noopener noreferrer'
%button.btn.js-settings-toggle{ type: 'button' }
= expanded ? 'Collapse' : 'Expand'
%p.append-bottom-0
......
......@@ -17,6 +17,7 @@
- cronjob:stuck_ci_jobs
- cronjob:stuck_import_jobs
- cronjob:stuck_merge_jobs
- cronjob:ci_archive_traces_cron
- cronjob:trending_projects
- cronjob:issue_due_scheduler
......
module Ci
class ArchiveTracesCronWorker
include ApplicationWorker
include CronjobQueue
def perform
# Archive stale live traces which still resides in redis or database
# This could happen when ArchiveTraceWorker sidekiq jobs were lost by receiving SIGKILL
# More details in https://gitlab.com/gitlab-org/gitlab-ce/issues/36791
Ci::Build.finished.with_live_trace.find_each(batch_size: 100) do |build|
begin
build.trace.archive!
rescue => e
failed_archive_counter.increment
Rails.logger.error "Failed to archive stale live trace. id: #{build.id} message: #{e.message}"
end
end
end
private
def failed_archive_counter
@failed_archive_counter ||= Gitlab::Metrics.counter(:job_trace_archive_failed_total, "Counter of failed attempts of traces archiving")
end
end
end
......@@ -29,7 +29,7 @@ class GitGarbageCollectWorker
task = task.to_sym
cmd = command(task)
gitaly_migrate(GITALY_MIGRATED_TASKS[task]) do |is_enabled|
gitaly_migrate(GITALY_MIGRATED_TASKS[task], status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled|
if is_enabled
gitaly_call(task, project.repository.raw_repository)
else
......@@ -114,8 +114,8 @@ class GitGarbageCollectWorker
%W[git -c repack.writeBitmaps=#{config_value}]
end
def gitaly_migrate(method, &block)
Gitlab::GitalyClient.migrate(method, &block)
def gitaly_migrate(method, status: Gitlab::GitalyClient::MigrationStatus::OPT_IN, &block)
Gitlab::GitalyClient.migrate(method, status: status, &block)
rescue GRPC::NotFound => e
Gitlab::GitLogger.error("#{method} failed:\nRepository not found")
raise Gitlab::Git::Repository::NoRepository.new(e)
......
---
title: Add variables to POST api/v4/projects/:id/pipeline
merge_request: 19124
author: Jacopo Beschi @jacopo-beschi
type: added
---
title: Add additional theme color options
merge_request:
author:
type: changed
---
title: Add Avatar API
merge_request: 19121
author: Imre Farkas
type: added
---
title: Use Github repo visibility during import while respecting restricted visibility
levels
merge_request:
author:
type: fixed
---
title: Migrate any remaining jobs from deprecated `object_storage_upload` queue.
merge_request:
author:
type: deprecated
---
title: Add a cronworker to rescue stale live traces
merge_request: 18680
author:
type: performance
---
title: 'Rails 5 fix unknown keywords: changes, key_id, project, gl_repository, action,
secret_token, protocol'
merge_request: 19466
author: Jasper Maes
type: fixed
---
title: Rails 5 fix glob spec
merge_request: 19469
author: Jasper Maes
type: fixed
---
title: Show a more helpful error for import status
merge_request:
author:
type: other
......@@ -78,10 +78,15 @@ production: &base
# username_changing_enabled: false # default: true - User can change her username/namespace
## Default theme ID
## 1 - Indigo
## 2 - Dark
## 3 - Light
## 4 - Blue
## 2 - Light Indigo
## 3 - Blue
## 4 - Light Blue
## 5 - Green
## 6 - Light Green
## 7 - Red
## 8 - Light Red
## 9 - Dark
## 10 - Light
# default_theme: 1 # default: 1
## Automatic issue closing
......
......@@ -341,6 +341,9 @@ Settings.cron_jobs['geo_migrated_local_files_clean_up_worker']['job_class'] ||=
Settings.cron_jobs['import_export_project_cleanup_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['import_export_project_cleanup_worker']['cron'] ||= '0 * * * *'
Settings.cron_jobs['import_export_project_cleanup_worker']['job_class'] = 'ImportExportProjectCleanupWorker'
Settings.cron_jobs['ci_archive_traces_cron_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['ci_archive_traces_cron_worker']['cron'] ||= '17 * * * *'
Settings.cron_jobs['ci_archive_traces_cron_worker']['job_class'] = 'Ci::ArchiveTracesCronWorker'
Settings.cron_jobs['requests_profiles_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['requests_profiles_worker']['cron'] ||= '0 0 * * *'
Settings.cron_jobs['requests_profiles_worker']['job_class'] = 'RequestsProfilesWorker'
......
class FixupEnvironmentNameUniqueness < ActiveRecord::Migration
include Gitlab::Database::ArelMethods
include Gitlab::Database::MigrationHelpers
DOWNTIME = true
......@@ -41,7 +42,7 @@ class FixupEnvironmentNameUniqueness < ActiveRecord::Migration
conflicts.each do |id, name|
update_sql =
Arel::UpdateManager.new(ActiveRecord::Base)
arel_update_manager
.table(environments)
.set(environments[:name] => name + "-" + id.to_s)
.where(environments[:id].eq(id))
......
......@@ -2,6 +2,7 @@
# for more information on how to write migrations for GitLab.
class AddEnvironmentSlug < ActiveRecord::Migration
include Gitlab::Database::ArelMethods
include Gitlab::Database::MigrationHelpers
DOWNTIME = true
......@@ -19,7 +20,7 @@ class AddEnvironmentSlug < ActiveRecord::Migration
finder = environments.project(:id, :name)
connection.exec_query(finder.to_sql).rows.each do |id, name|
updater = Arel::UpdateManager.new(ActiveRecord::Base)
updater = arel_update_manager
.table(environments)
.set(environments[:slug] => generate_slug(name))
.where(environments[:id].eq(id))
......
class FixProjectRecordsWithInvalidVisibility < ActiveRecord::Migration
include Gitlab::Database::ArelMethods
include Gitlab::Database::MigrationHelpers
BATCH_SIZE = 500
......@@ -33,7 +34,7 @@ class FixProjectRecordsWithInvalidVisibility < ActiveRecord::Migration
end
updates.each do |visibility_level, project_ids|
updater = Arel::UpdateManager.new(ActiveRecord::Base)
updater = arel_update_manager
.table(projects)
.set(projects[:visibility_level] => visibility_level)
.where(projects[:id].in(project_ids))
......
# rubocop:disable Migration/UpdateLargeTable
class MigrateUserActivitiesToUsersLastActivityOn < ActiveRecord::Migration
include Gitlab::Database::ArelMethods
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
......@@ -39,7 +40,7 @@ class MigrateUserActivitiesToUsersLastActivityOn < ActiveRecord::Migration
activities = activities(day.at_beginning_of_day, day.at_end_of_day, page: page)
update_sql =
Arel::UpdateManager.new(ActiveRecord::Base)
arel_update_manager
.table(users_table)
.set(users_table[:last_activity_on] => day.to_date)
.where(users_table[:username].in(activities.map(&:first)))
......
class MigrateObjectStorageUploadSidekiqQueue < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
sidekiq_queue_migrate 'object_storage_upload', to: 'object_storage:object_storage_background_move'
end
def down
# do not migrate any jobs back because we would migrate also
# jobs which were not part of the 'object_storage_upload'
end
end
# Avatar API
> [Introduced][ce-19121] in GitLab 11.0
## Get a single avatar URL
Get a single avatar URL for a given email addres. If user with matching public
email address is not found, results from external avatar services are returned.
This endpoint can be accessed without authentication. In case public visibility
is restricted, response will be `403 Forbidden` when unauthenticated.
```
GET /avatar?email=admin@example.com
```
| Attribute | Type | Required | Description |
| --------- | ------- | -------- | --------------------- |
| `email` | string | yes | Public email address of the user |
| `size` | integer | no | Single pixel dimension (since images are squares). Only used for avatar lookups at `Gravatar` or at the configured `Libravatar` server |
```bash
curl https://gitlab.example.com/api/v4/avatar?email=admin@example.com
```
Example response:
```json
{
"avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon"
}
```
[ce-19121]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/19121
......@@ -102,6 +102,7 @@ POST /projects/:id/pipeline
|------------|---------|----------|---------------------|
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `ref` | string | yes | Reference to commit |
| `variables` | array | no | An array containing the variables available in the pipeline, matching the structure [{ 'key' => 'UPLOAD_TO_S3', 'value' => 'true' }] |
```
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/pipeline?ref=master"
......
......@@ -216,12 +216,12 @@ If you want a line or set of lines to be ignored by the linter, you can use
`// scss-lint:disable RuleName` ([more info][disabling-linters]):
```scss
// This lint rule is disabled because the class name comes from a gem.
// scss-lint:disable SelectorFormat
.ui_indigo {
background-color: #333;
// This lint rule is disabled because it is supported only in Chrome/Safari
// scss-lint:disable PropertySpelling
body {
text-decoration-skip: ink;
}
// scss-lint:enable SelectorFormat
// scss-lint:enable PropertySpelling
```
Make sure a comment is added on the line above the `disable` rule, otherwise the
......
......@@ -176,3 +176,20 @@ git push -u origin update-project-templates
```
Now create a merge request and merge that to master.
## Generate route lists
To see the full list of API routes, you can run:
```shell
bundle exec rake grape:path_helpers
```
For the Rails controllers, run:
```shell
bundle exec rake routes
```
Since these take some time to create, it's often helpful to save the output to
a file for quick reference.
......@@ -80,8 +80,8 @@ More information can be found on the [yarn website](https://yarnpkg.com/en/docs/
### 5. Update Go
NOTE: GitLab 9.2 and higher only supports Go 1.8.3 and dropped support for Go
1.5.x through 1.7.x. Be sure to upgrade your installation if necessary.
NOTE: GitLab 9.2 and higher only supports Go 1.9 and dropped support for Go
1.5.x through 1.8.x. Be sure to upgrade your installation if necessary.
You can check which version you are running with `go version`.
......@@ -91,11 +91,11 @@ Download and install Go:
# Remove former Go installation folder
sudo rm -rf /usr/local/go
curl --remote-name --progress https://storage.googleapis.com/golang/go1.8.3.linux-amd64.tar.gz
echo '1862f4c3d3907e59b04a757cfda0ea7aa9ef39274af99a784f5be843c80c6772 go1.8.3.linux-amd64.tar.gz' | shasum -a256 -c - && \
sudo tar -C /usr/local -xzf go1.8.3.linux-amd64.tar.gz
curl --remote-name --progress https://storage.googleapis.com/golang/go1.9.linux-amd64.tar.gz
echo 'd70eadefce8e160638a9a6db97f7192d8463069ab33138893ad3bf31b0650a79 go1.9.linux-amd64.tar.gz' | shasum -a256 -c - && \
sudo tar -C /usr/local -xzf go1.9.linux-amd64.tar.gz
sudo ln -sf /usr/local/go/bin/{go,godoc,gofmt} /usr/local/bin/
rm go1.8.3.linux-amd64.tar.gz
rm go1.9.linux-amd64.tar.gz
```
### 6. Get latest code
......
......@@ -143,6 +143,24 @@ docker login registry.example.com -u <your_username> -p <your_access_token>
for errors (e.g. `/var/log/gitlab/gitlab-rails/production.log`). You may be able to find clues
there.
#### Enable the registry debug server
The optional debug server can be enabled by setting the registry debug address
in your `gitlab.rb` configuration.
```ruby
registry['debug_addr'] = "localhost:5001"
```
After adding the setting, [reconfigure] GitLab to apply the change.
Use curl to request debug output from the debug server:
```bash
curl localhost:5001/debug/health
curl localhost:5001/debug/vars
```
### Advanced Troubleshooting
>**NOTE:** The following section is only recommended for experts.
......@@ -275,3 +293,4 @@ Once the right permissions were set, the error will go away.
[docker-docs]: https://docs.docker.com/engine/userguide/intro/
[pat]: ../profile/personal_access_tokens.md
[pdt]: ../project/deploy_tokens/index.md
[reconfigure]: ../../administration/restart_gitlab.md#omnibus-gitlab-reconfigure
\ No newline at end of file
......@@ -90,6 +90,7 @@ module API
# Keep in alphabetical order
mount ::API::AccessRequests
mount ::API::Applications
mount ::API::Avatar
mount ::API::AwardEmoji
mount ::API::Badges
mount ::API::Boards
......
module API
class Avatar < Grape::API
resource :avatar do
desc 'Return avatar url for a user' do
success Entities::Avatar
end
params do
requires :email, type: String, desc: 'Public email address of the user'
optional :size, type: Integer, desc: 'Single pixel dimension for Gravatar images'
end
get do
forbidden!('Unauthorized access') unless can?(current_user, :read_users_list)
user = User.find_by_public_email(params[:email])
user ||= User.new(email: params[:email])
present user, with: Entities::Avatar, size: params[:size]
end
end
end
end
......@@ -722,6 +722,12 @@ module API
expose :notes, using: Entities::Note
end
class Avatar < Grape::Entity
expose :avatar_url do |avatarable, options|
avatarable.avatar_url(only_path: false, size: options[:size])
end
end
class AwardEmoji < Grape::Entity
expose :id
expose :name
......
......@@ -41,15 +41,20 @@ module API
end
params do
requires :ref, type: String, desc: 'Reference'
optional :variables, Array, desc: 'Array of variables available in the pipeline'
end
post ':id/pipeline' do
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42124')
authorize! :create_pipeline, user_project
pipeline_params = declared_params(include_missing: false)
.merge(variables_attributes: params[:variables])
.except(:variables)
new_pipeline = Ci::CreatePipelineService.new(user_project,
current_user,
declared_params(include_missing: false))
pipeline_params)
.execute(:api, ignore_skip_ci: true, save_on_errors: false)
if new_pipeline.persisted?
......
module Gitlab
module Ci
class Trace
include ExclusiveLeaseGuard
LEASE_TIMEOUT = 1.hour
ArchiveError = Class.new(StandardError)
attr_reader :job
......@@ -105,6 +109,14 @@ module Gitlab
end
def archive!
try_obtain_lease do
unsafe_archive!
end
end
private
def unsafe_archive!
raise ArchiveError, 'Already archived' if trace_artifact
raise ArchiveError, 'Job is not finished yet' unless job.complete?
......@@ -126,8 +138,6 @@ module Gitlab
end
end
private
def archive_stream!(stream)
clone_file!(stream, JobArtifactUploader.workhorse_upload_path) do |clone_path|
create_build_trace!(job, clone_path)
......@@ -206,6 +216,16 @@ module Gitlab
def trace_artifact
job.job_artifacts_trace
end
# For ExclusiveLeaseGuard concern
def lease_key
@lease_key ||= "trace:archive:#{job.id}"
end
# For ExclusiveLeaseGuard concern
def lease_timeout
LEASE_TIMEOUT
end
end
end
end
......@@ -60,6 +60,9 @@ module Gitlab
# Some weird thing?
return nil unless commit_id.is_a?(String)
# This saves us an RPC round trip.
return nil if commit_id.include?(':')
commit = repo.gitaly_migrate(:find_commit) do |is_enabled|
if is_enabled
repo.gitaly_commit_client.find_commit(commit_id)
......
......@@ -39,7 +39,9 @@ module Gitlab
end
def git_all_pointers
rev_list.all_objects(require_path: true) do |object_ids|
params = { options: ["--filter=blob:limit=#{Gitlab::Git::Blob::LFS_POINTER_MAX_SIZE}"], require_path: true }
rev_list.all_objects(params) do |object_ids|
Gitlab::Git::Blob.batch_lfs_pointers(@repository, object_ids)
end
end
......
......@@ -1557,7 +1557,7 @@ module Gitlab
end
end
def rev_list(including: [], excluding: [], objects: false, &block)
def rev_list(including: [], excluding: [], options: [], objects: false, &block)
args = ['rev-list']
args.push(*rev_list_param(including))
......@@ -1570,6 +1570,10 @@ module Gitlab
args.push('--objects') if objects
if options.any?
args.push(*options)
end
run_git!(args, lazy_block: block)
end
......
......@@ -37,8 +37,11 @@ module Gitlab
get_objects(opts, &lazy_block)
end
def all_objects(require_path: nil, &lazy_block)
get_objects(including: :all, require_path: require_path, &lazy_block)
def all_objects(options: [], require_path: nil, &lazy_block)
get_objects(including: :all,
options: options,
require_path: require_path,
&lazy_block)
end
# This methods returns an array of missed references
......@@ -54,8 +57,8 @@ module Gitlab
repository.rev_list(args).split("\n")
end
def get_objects(including: [], excluding: [], require_path: nil)
opts = { including: including, excluding: excluding, objects: true }
def get_objects(including: [], excluding: [], options: [], require_path: nil)
opts = { including: including, excluding: excluding, options: options, objects: true }
repository.rev_list(opts) do |lazy_output|
objects = objects_from_output(lazy_output, require_path: require_path)
......
......@@ -191,6 +191,8 @@ module Gitlab
metadata['call_site'] = feature.to_s if feature
metadata['gitaly-servers'] = address_metadata(remote_storage) if remote_storage
metadata.merge!(server_feature_flags)
result = { metadata: metadata }
# nil timeout indicates that we should use the default
......@@ -209,6 +211,14 @@ module Gitlab
result
end
SERVER_FEATURE_FLAGS = %w[gogit_findcommit].freeze
def self.server_feature_flags
SERVER_FEATURE_FLAGS.map do |f|
["gitaly-feature-#{f.tr('_', '-')}", feature_enabled?(f).to_s]
end.to_h
end
def self.token(storage)
params = Gitlab.config.repositories.storages[storage]
raise "storage not found: #{storage.inspect}" if params.nil?
......@@ -243,6 +253,10 @@ module Gitlab
else
false
end
rescue => ex
# During application startup feature lookups in SQL can fail
Rails.logger.warn "exception while checking Gitaly feature status for #{feature_name}: #{ex}"
false
end
# opt_into_all_features? returns true when the current environment
......
......@@ -54,7 +54,11 @@ module Gitlab
fingerprints = CurrentKeyChain.fingerprints_from_key(key)
GPGME::Key.find(:public, fingerprints).flat_map do |raw_key|
raw_key.uids.map { |uid| { name: uid.name, email: uid.email.downcase } }
raw_key.uids.each_with_object([]) do |uid, arr|
name = uid.name.force_encoding('UTF-8')
email = uid.email.force_encoding('UTF-8')
arr << { name: name, email: email.downcase } if name.valid_encoding? && email.valid_encoding?
end
end
end
end
......
......@@ -35,7 +35,10 @@ module Gitlab
end
def visibility_level
repo.private ? Gitlab::VisibilityLevel::PRIVATE : Gitlab::CurrentSettings.default_project_visibility
visibility_level = repo.private ? Gitlab::VisibilityLevel::PRIVATE : Gitlab::VisibilityLevel::PUBLIC
visibility_level = Gitlab::CurrentSettings.default_project_visibility if Gitlab::CurrentSettings.restricted_visibility_levels.include?(visibility_level)
visibility_level
end
#
......
......@@ -12,11 +12,16 @@ module Gitlab
# All available Themes
THEMES = [
Theme.new(1, 'Indigo', 'ui_indigo'),
Theme.new(2, 'Dark', 'ui_dark'),
Theme.new(3, 'Light', 'ui_light'),
Theme.new(4, 'Blue', 'ui_blue'),
Theme.new(5, 'Green', 'ui_green')
Theme.new(1, 'Indigo', 'ui-indigo'),
Theme.new(2, 'Light Indigo', 'ui-light-indigo'),
Theme.new(3, 'Blue', 'ui-blue'),
Theme.new(4, 'Light Blue', 'ui-light-blue'),
Theme.new(5, 'Green', 'ui-green'),
Theme.new(6, 'Light Green', 'ui-light-green'),
Theme.new(7, 'Red', 'ui-red'),
Theme.new(8, 'Light Red', 'ui-light-red'),
Theme.new(9, 'Dark', 'ui-dark'),
Theme.new(10, 'Light', 'ui-light')
].freeze
# Convenience method to get a space-separated String of all the theme
......
......@@ -28,6 +28,15 @@ RUN apt-get update -q && apt-get install -y google-chrome-stable && apt-get clea
RUN wget -q https://chromedriver.storage.googleapis.com/$(wget -q -O - https://chromedriver.storage.googleapis.com/LATEST_RELEASE)/chromedriver_linux64.zip
RUN unzip chromedriver_linux64.zip -d /usr/local/bin
##
# Install gcloud and kubectl CLI used in Auto DevOps test to create K8s
# clusters
#
RUN export CLOUD_SDK_REPO="cloud-sdk-$(lsb_release -c -s)" && \
echo "deb http://packages.cloud.google.com/apt $CLOUD_SDK_REPO main" | tee -a /etc/apt/sources.list.d/google-cloud-sdk.list && \
curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add - && \
apt-get update -y && apt-get install google-cloud-sdk kubectl -y
WORKDIR /home/qa
COPY ./Gemfile* ./
RUN bundle install
......
......@@ -41,6 +41,7 @@ module QA
autoload :SecretVariable, 'qa/factory/resource/secret_variable'
autoload :Runner, 'qa/factory/resource/runner'
autoload :PersonalAccessToken, 'qa/factory/resource/personal_access_token'
autoload :KubernetesCluster, 'qa/factory/resource/kubernetes_cluster'
end
module Repository
......@@ -72,6 +73,7 @@ module QA
module Integration
autoload :LDAP, 'qa/scenario/test/integration/ldap'
autoload :Kubernetes, 'qa/scenario/test/integration/kubernetes'
autoload :Mattermost, 'qa/scenario/test/integration/mattermost'
end
......@@ -150,6 +152,15 @@ module QA
autoload :Show, 'qa/page/project/issue/show'
autoload :Index, 'qa/page/project/issue/index'
end
module Operations
module Kubernetes
autoload :Index, 'qa/page/project/operations/kubernetes/index'
autoload :Add, 'qa/page/project/operations/kubernetes/add'
autoload :AddExisting, 'qa/page/project/operations/kubernetes/add_existing'
autoload :Show, 'qa/page/project/operations/kubernetes/show'
end
end
end
module Profile
......@@ -195,6 +206,7 @@ module QA
#
module Service
autoload :Shellout, 'qa/service/shellout'
autoload :KubernetesCluster, 'qa/service/kubernetes_cluster'
autoload :Omnibus, 'qa/service/omnibus'
autoload :Runner, 'qa/service/runner'
end
......
......@@ -15,7 +15,7 @@ module QA
def initialize
@file_name = 'file.txt'
@file_content = '# This is test project'
@commit_message = "Add #{@file_name}"
@commit_message = "This is a test commit"
@branch_name = 'master'
@new_branch = true
end
......@@ -24,6 +24,12 @@ module QA
@remote_branch ||= branch_name
end
def directory=(dir)
raise "Must set directory as a Pathname" unless dir.is_a?(Pathname)
@directory = dir
end
def fabricate!
project.visit!
......@@ -43,7 +49,14 @@ module QA
repository.checkout(branch_name)
end
repository.add_file(file_name, file_content)
if @directory
@directory.each_child do |f|
repository.add_file(f.basename, f.read) if f.file?
end
else
repository.add_file(file_name, file_content)
end
repository.commit(commit_message)
repository.push_changes("#{branch_name}:#{remote_branch}")
end
......
require 'securerandom'
module QA
module Factory
module Resource
class KubernetesCluster < Factory::Base
attr_writer :project, :cluster,
:install_helm_tiller, :install_ingress, :install_prometheus, :install_runner
product :ingress_ip do
Page::Project::Operations::Kubernetes::Show.perform do |page|
page.ingress_ip
end
end
def fabricate!
@project.visit!
Page::Menu::Side.act { click_operations_kubernetes }
Page::Project::Operations::Kubernetes::Index.perform do |page|
page.add_kubernetes_cluster
end
Page::Project::Operations::Kubernetes::Add.perform do |page|
page.add_existing_cluster
end
Page::Project::Operations::Kubernetes::AddExisting.perform do |page|
page.set_cluster_name(@cluster.cluster_name)
page.set_api_url(@cluster.api_url)
page.set_ca_certificate(@cluster.ca_certificate)
page.set_token(@cluster.token)
page.add_cluster!
end
if @install_helm_tiller
Page::Project::Operations::Kubernetes::Show.perform do |page|
# Helm must be installed before everything else
page.install!(:helm)
page.await_installed(:helm)
page.install!(:ingress) if @install_ingress
page.await_installed(:ingress) if @install_ingress
page.install!(:prometheus) if @install_prometheus
page.await_installed(:prometheus) if @install_prometheus
page.install!(:runner) if @install_runner
page.await_installed(:runner) if @install_runner
end
end
end
end
end
end
end
source 'https://rubygems.org'
gem 'rack'
gem 'rake'
GEM
remote: https://rubygems.org/
specs:
rack (2.0.4)
rake (12.3.0)
PLATFORMS
ruby
DEPENDENCIES
rack
rake
BUNDLED WITH
1.16.1
require 'rake/testtask'
task default: %w[test]
task :test do
puts "ok"
end
run lambda { |env| [200, { 'Content-Type' => 'text/plain' }, StringIO.new("Hello World!\n")] }
......@@ -7,9 +7,11 @@ module QA
element :settings_link, 'link_to edit_project_path'
element :repository_link, "title: 'Repository'"
element :pipelines_settings_link, "title: 'CI / CD'"
element :operations_kubernetes_link, "title: _('Kubernetes')"
element :issues_link, /link_to.*shortcuts-issues/
element :issues_link_text, "Issues"
element :top_level_items, '.sidebar-top-level-items'
element :operations_section, "class: 'shortcuts-operations'"
element :activity_link, "title: 'Activity'"
end
......@@ -33,6 +35,14 @@ module QA
end
end
def click_operations_kubernetes
hover_operations do
within_submenu do
click_link('Kubernetes')
end
end
end
def click_ci_cd_pipelines
within_sidebar do
click_link('CI / CD')
......@@ -61,6 +71,14 @@ module QA
end
end
def hover_operations
within_sidebar do
find('.shortcuts-operations').hover
yield
end
end
def within_sidebar
page.within('.sidebar-top-level-items') do
yield
......
module QA
module Page
module Project
module Operations
module Kubernetes
class Add < Page::Base
view 'app/views/projects/clusters/new.html.haml' do
element :add_kubernetes_cluster_button, "link_to s_('ClusterIntegration|Add an existing Kubernetes cluster')"
end
def add_existing_cluster
click_on 'Add an existing Kubernetes cluster'
end
end
end
end
end
end
end
module QA
module Page
module Project
module Operations
module Kubernetes
class AddExisting < Page::Base
view 'app/views/projects/clusters/user/_form.html.haml' do
element :cluster_name, 'text_field :name'
element :api_url, 'text_field :api_url'
element :ca_certificate, 'text_area :ca_cert'
element :token, 'text_field :token'
element :add_cluster_button, "submit s_('ClusterIntegration|Add Kubernetes cluster')"
end
def set_cluster_name(name)
fill_in 'cluster_name', with: name
end
def set_api_url(api_url)
fill_in 'cluster_platform_kubernetes_attributes_api_url', with: api_url
end
def set_ca_certificate(ca_certificate)
fill_in 'cluster_platform_kubernetes_attributes_ca_cert', with: ca_certificate
end
def set_token(token)
fill_in 'cluster_platform_kubernetes_attributes_token', with: token
end
def add_cluster!
click_on 'Add Kubernetes cluster'
end
end
end
end
end
end
end
module QA
module Page
module Project
module Operations
module Kubernetes
class Index < Page::Base
view 'app/views/projects/clusters/_empty_state.html.haml' do
element :add_kubernetes_cluster_button, "link_to s_('ClusterIntegration|Add Kubernetes cluster')"
end
def add_kubernetes_cluster
click_on 'Add Kubernetes cluster'
end
end
end
end
end
end
end
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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