Commit 12109f67 authored by Grzegorz Bizon's avatar Grzegorz Bizon

Merge branch 'master' into 'feature/gb/variables-expressions-in-only-except-ee'

 Conflicts:
   app/models/ci/build.rb
parents 1f75a144 69cfb90b
...@@ -15,6 +15,7 @@ app/models/project_services/packagist_service.rb ...@@ -15,6 +15,7 @@ app/models/project_services/packagist_service.rb
lib/gitlab/background_migration/normalize_ldap_extern_uids_range.rb lib/gitlab/background_migration/normalize_ldap_extern_uids_range.rb
lib/gitlab/background_migration/* lib/gitlab/background_migration/*
app/models/project_services/kubernetes_service.rb app/models/project_services/kubernetes_service.rb
lib/gitlab/workhorse.rb
ee/db/**/* ee/db/**/*
ee/app/serializers/ee/merge_request_widget_entity.rb ee/app/serializers/ee/merge_request_widget_entity.rb
......
...@@ -51,6 +51,7 @@ eslint-report.html ...@@ -51,6 +51,7 @@ eslint-report.html
/db/data.yml /db/data.yml
/doc/code/* /doc/code/*
/dump.rdb /dump.rdb
/jsconfig.json
/log/*.log* /log/*.log*
/node_modules/ /node_modules/
/nohup.out /nohup.out
......
See the general Documentation guidelines http://docs.gitlab.com/ce/development/doc_styleguide.html <!--See the general Documentation guidelines https://docs.gitlab.com/ce/development/writing_documentation.html -->
## What does this MR do? ## What does this MR do?
(briefly describe what this MR is about) <!-- Briefly describe what this MR is about -->
## Related issues
<!-- Mention the issue(s) this MR closes or is related to -->
Closes
## Moving docs to a new location? ## Moving docs to a new location?
See the guidelines: http://docs.gitlab.com/ce/development/doc_styleguide.html#changing-document-location Read the guidelines:
https://docs.gitlab.com/ce/development/writing_documentation.html#changing-document-location
- [ ] Make sure the old link is not removed and has its contents replaced with a link to the new location. - [ ] Make sure the old link is not removed and has its contents replaced with
a link to the new location.
- [ ] Make sure internal links pointing to the document in question are not broken. - [ ] Make sure internal links pointing to the document in question are not broken.
- [ ] Search and replace any links referring to old docs in GitLab Rails app, specifically under the `app/views/` directory. - [ ] Search and replace any links referring to old docs in GitLab Rails app,
- [ ] Make sure to add [`redirect_from`](https://docs.gitlab.com/ee/development/doc_styleguide.html#redirections-for-pages-with-disqus-comments) to the new document if there are any Disqus comments on the old document thread. specifically under the `app/views/` and `ee/app/views` (for GitLab EE) directories.
- [ ] If working on CE, submit an MR to EE with the changes as well. - [ ] Make sure to add [`redirect_from`](https://docs.gitlab.com/ce/development/writing_documentation.html#redirections-for-pages-with-disqus-comments)
to the new document if there are any Disqus comments on the old document thread.
- [ ] If working on CE and the `ee-compat-check` jobs fails, submit an MR to EE
with the changes as well (https://docs.gitlab.com/ce/development/writing_documentation.html#cherry-picking-from-ce-to-ee).
- [ ] Ping one of the technical writers for review. - [ ] Ping one of the technical writers for review.
/label ~Documentation
...@@ -156,8 +156,8 @@ gem 'rdoc', '~> 4.2' ...@@ -156,8 +156,8 @@ gem 'rdoc', '~> 4.2'
gem 'org-ruby', '~> 0.9.12' gem 'org-ruby', '~> 0.9.12'
gem 'creole', '~> 0.5.0' gem 'creole', '~> 0.5.0'
gem 'wikicloth', '0.8.1' gem 'wikicloth', '0.8.1'
gem 'asciidoctor', '~> 1.5.2' gem 'asciidoctor', '~> 1.5.6'
gem 'asciidoctor-plantuml', '0.0.7' gem 'asciidoctor-plantuml', '0.0.8'
gem 'rouge', '~> 2.0' gem 'rouge', '~> 2.0'
gem 'truncato', '~> 0.7.9' gem 'truncato', '~> 0.7.9'
gem 'bootstrap_form', '~> 2.7.0' gem 'bootstrap_form', '~> 2.7.0'
...@@ -436,7 +436,7 @@ group :ed25519 do ...@@ -436,7 +436,7 @@ group :ed25519 do
end end
# Gitaly GRPC client # Gitaly GRPC client
gem 'gitaly-proto', '~> 0.88.0', require: 'gitaly' gem 'gitaly-proto', '~> 0.91.0', require: 'gitaly'
gem 'grpc', '~> 1.10.0' gem 'grpc', '~> 1.10.0'
# Locked until https://github.com/google/protobuf/issues/4210 is closed # Locked until https://github.com/google/protobuf/issues/4210 is closed
......
...@@ -56,8 +56,8 @@ GEM ...@@ -56,8 +56,8 @@ GEM
faraday_middleware (~> 0.9) faraday_middleware (~> 0.9)
faraday_middleware-multi_json (~> 0.0) faraday_middleware-multi_json (~> 0.0)
oauth2 (~> 1.0) oauth2 (~> 1.0)
asciidoctor (1.5.3) asciidoctor (1.5.6.2)
asciidoctor-plantuml (0.0.7) asciidoctor-plantuml (0.0.8)
asciidoctor (~> 1.5) asciidoctor (~> 1.5)
asset_sync (2.2.0) asset_sync (2.2.0)
activemodel (>= 4.1.0) activemodel (>= 4.1.0)
...@@ -314,7 +314,7 @@ GEM ...@@ -314,7 +314,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-proto (0.88.0) gitaly-proto (0.91.0)
google-protobuf (~> 3.1) google-protobuf (~> 3.1)
grpc (~> 1.0) grpc (~> 1.0)
github-linguist (5.3.3) github-linguist (5.3.3)
...@@ -1028,8 +1028,8 @@ DEPENDENCIES ...@@ -1028,8 +1028,8 @@ DEPENDENCIES
akismet (~> 2.0) akismet (~> 2.0)
allocations (~> 1.0) allocations (~> 1.0)
asana (~> 0.6.0) asana (~> 0.6.0)
asciidoctor (~> 1.5.2) asciidoctor (~> 1.5.6)
asciidoctor-plantuml (= 0.0.7) asciidoctor-plantuml (= 0.0.8)
asset_sync (~> 2.2.0) asset_sync (~> 2.2.0)
attr_encrypted (~> 3.0.0) attr_encrypted (~> 3.0.0)
awesome_print (~> 1.2.0) awesome_print (~> 1.2.0)
...@@ -1096,7 +1096,7 @@ DEPENDENCIES ...@@ -1096,7 +1096,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.3) gettext_i18n_rails_js (~> 1.3)
gitaly-proto (~> 0.88.0) gitaly-proto (~> 0.91.0)
github-linguist (~> 5.3.3) github-linguist (~> 5.3.3)
gitlab-flowdock-git-hook (~> 1.0.1) gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-license (~> 1.0) gitlab-license (~> 1.0)
......
...@@ -757,7 +757,7 @@ GitLabDropdown = (function() { ...@@ -757,7 +757,7 @@ GitLabDropdown = (function() {
} }
if (this.options.isSelectable && !this.options.isSelectable(selectedObject, el)) { if (this.options.isSelectable && !this.options.isSelectable(selectedObject, el)) {
return; return [selectedObject];
} }
if (el.hasClass(ACTIVE_CLASS) && value !== 0) { if (el.hasClass(ACTIVE_CLASS) && value !== 0) {
......
...@@ -6,6 +6,7 @@ export const defaultEditorOptions = { ...@@ -6,6 +6,7 @@ export const defaultEditorOptions = {
minimap: { minimap: {
enabled: false, enabled: false,
}, },
wordWrap: 'bounded',
}; };
export default [ export default [
......
...@@ -20,6 +20,11 @@ ...@@ -20,6 +20,11 @@
type: String, type: String,
required: true, required: true,
}, },
canAttachFile: {
type: Boolean,
required: false,
default: true,
},
enableAutocomplete: { enableAutocomplete: {
type: Boolean, type: Boolean,
required: false, required: false,
...@@ -42,6 +47,7 @@ ...@@ -42,6 +47,7 @@
<markdown-field <markdown-field
:markdown-preview-path="markdownPreviewPath" :markdown-preview-path="markdownPreviewPath"
:markdown-docs-path="markdownDocsPath" :markdown-docs-path="markdownDocsPath"
:can-attach-file="canAttachFile"
:enable-autocomplete="enableAutocomplete" :enable-autocomplete="enableAutocomplete"
> >
<textarea <textarea
......
...@@ -48,6 +48,11 @@ ...@@ -48,6 +48,11 @@
required: false, required: false,
default: true, default: true,
}, },
canAttachFile: {
type: Boolean,
required: false,
default: true,
},
enableAutocomplete: { enableAutocomplete: {
type: Boolean, type: Boolean,
required: false, required: false,
...@@ -92,6 +97,7 @@ ...@@ -92,6 +97,7 @@
:form-state="formState" :form-state="formState"
:markdown-preview-path="markdownPreviewPath" :markdown-preview-path="markdownPreviewPath"
:markdown-docs-path="markdownDocsPath" :markdown-docs-path="markdownDocsPath"
:can-attach-file="canAttachFile"
:enable-autocomplete="enableAutocomplete" :enable-autocomplete="enableAutocomplete"
/> />
<edit-actions <edit-actions
......
...@@ -11,11 +11,19 @@ ...@@ -11,11 +11,19 @@
type: String, type: String,
required: true, required: true,
}, },
helpUrl: {
type: String,
required: false,
default: '',
},
}, },
computed: { computed: {
hasTitle() { hasTitle() {
return this.title.length > 0; return this.title.length > 0;
}, },
hasHelpURL() {
return this.helpUrl.length > 0;
},
}, },
}; };
</script> </script>
...@@ -28,5 +36,21 @@ ...@@ -28,5 +36,21 @@
{{ title }}: {{ title }}:
</span> </span>
{{ value }} {{ value }}
<span
v-if="hasHelpURL"
class="help-button pull-right"
>
<a
:href="helpUrl"
target="_blank"
rel="noopener noreferrer nofollow"
>
<i
class="fa fa-question-circle"
aria-hidden="true"
></i>
</a>
</span>
</p> </p>
</template> </template>
...@@ -22,6 +22,11 @@ ...@@ -22,6 +22,11 @@
type: Boolean, type: Boolean,
required: true, required: true,
}, },
runnerHelpUrl: {
type: String,
required: false,
default: '',
},
}, },
computed: { computed: {
shouldRenderContent() { shouldRenderContent() {
...@@ -39,6 +44,21 @@ ...@@ -39,6 +44,21 @@
runnerId() { runnerId() {
return `#${this.job.runner.id}`; return `#${this.job.runner.id}`;
}, },
hasTimeout() {
return this.job.metadata != null && this.job.metadata.timeout_human_readable !== '';
},
timeout() {
if (this.job.metadata == null) {
return '';
}
let t = this.job.metadata.timeout_human_readable;
if (this.job.metadata.timeout_source !== '') {
t += ` (from ${this.job.metadata.timeout_source})`;
}
return t;
},
renderBlock() { renderBlock() {
return this.job.merge_request || return this.job.merge_request ||
this.job.duration || this.job.duration ||
...@@ -114,6 +134,13 @@ ...@@ -114,6 +134,13 @@
title="Queued" title="Queued"
:value="queued" :value="queued"
/> />
<detail-row
class="js-job-timeout"
v-if="hasTimeout"
title="Timeout"
:help-url="runnerHelpUrl"
:value="timeout"
/>
<detail-row <detail-row
class="js-job-runner" class="js-job-runner"
v-if="job.runner" v-if="job.runner"
......
...@@ -51,6 +51,7 @@ export default () => { ...@@ -51,6 +51,7 @@ export default () => {
props: { props: {
isLoading: this.mediator.state.isLoading, isLoading: this.mediator.state.isLoading,
job: this.mediator.store.state.job, job: this.mediator.store.state.job,
runnerHelpUrl: dataset.runnerHelpUrl,
}, },
}); });
}, },
......
<script> <script>
import _ from 'underscore'; import _ from 'underscore';
import Flash from '../../flash'; import Flash from '../../flash';
import MonitoringService from '../services/monitoring_service'; import MonitoringService from '../services/monitoring_service';
import GraphGroup from './graph_group.vue'; import GraphGroup from './graph_group.vue';
import Graph from './graph.vue'; import Graph from './graph.vue';
import EmptyState from './empty_state.vue'; import EmptyState from './empty_state.vue';
import MonitoringStore from '../stores/monitoring_store'; import MonitoringStore from '../stores/monitoring_store';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
export default { export default {
components: { components: {
Graph, Graph,
GraphGroup, GraphGroup,
EmptyState, EmptyState,
}, },
props: { props: {
hasMetrics: { hasMetrics: {
type: Boolean, type: Boolean,
...@@ -82,7 +81,6 @@ ...@@ -82,7 +81,6 @@
required: true, required: true,
}, },
}, },
data() { data() {
return { return {
store: new MonitoringStore(), store: new MonitoringStore(),
...@@ -94,7 +92,6 @@ ...@@ -94,7 +92,6 @@
resizeThrottled: {}, resizeThrottled: {},
}; };
}, },
created() { created() {
this.service = new MonitoringService({ this.service = new MonitoringService({
metricsEndpoint: this.metricsEndpoint, metricsEndpoint: this.metricsEndpoint,
...@@ -103,13 +100,11 @@ ...@@ -103,13 +100,11 @@
eventHub.$on('toggleAspectRatio', this.toggleAspectRatio); eventHub.$on('toggleAspectRatio', this.toggleAspectRatio);
eventHub.$on('hoverChanged', this.hoverChanged); eventHub.$on('hoverChanged', this.hoverChanged);
}, },
beforeDestroy() { beforeDestroy() {
eventHub.$off('toggleAspectRatio', this.toggleAspectRatio); eventHub.$off('toggleAspectRatio', this.toggleAspectRatio);
eventHub.$off('hoverChanged', this.hoverChanged); eventHub.$off('hoverChanged', this.hoverChanged);
window.removeEventListener('resize', this.resizeThrottled, false); window.removeEventListener('resize', this.resizeThrottled, false);
}, },
mounted() { mounted() {
this.resizeThrottled = _.throttle(this.resize, 600); this.resizeThrottled = _.throttle(this.resize, 600);
if (!this.hasMetrics) { if (!this.hasMetrics) {
...@@ -119,14 +114,13 @@ ...@@ -119,14 +114,13 @@
window.addEventListener('resize', this.resizeThrottled, false); window.addEventListener('resize', this.resizeThrottled, false);
} }
}, },
methods: { methods: {
getGraphsData() { getGraphsData() {
this.state = 'loading'; this.state = 'loading';
Promise.all([ Promise.all([
this.service.getGraphsData() this.service.getGraphsData().then(data => this.store.storeMetrics(data)),
.then(data => this.store.storeMetrics(data)), this.service
this.service.getDeploymentData() .getDeploymentData()
.then(data => this.store.storeDeploymentData(data)) .then(data => this.store.storeDeploymentData(data))
.catch(() => new Flash('Error getting deployment information.')), .catch(() => new Flash('Error getting deployment information.')),
]) ])
...@@ -137,13 +131,13 @@ ...@@ -137,13 +131,13 @@
} }
this.showEmptyState = false; this.showEmptyState = false;
}) })
.catch(() => { this.state = 'unableToConnect'; }); .catch(() => {
this.state = 'unableToConnect';
});
}, },
resize() { resize() {
this.updateAspectRatio = true; this.updateAspectRatio = true;
}, },
toggleAspectRatio() { toggleAspectRatio() {
this.updatedAspectRatios = this.updatedAspectRatios += 1; this.updatedAspectRatios = this.updatedAspectRatios += 1;
if (this.store.getMetricsCount() === this.updatedAspectRatios) { if (this.store.getMetricsCount() === this.updatedAspectRatios) {
...@@ -151,12 +145,11 @@ ...@@ -151,12 +145,11 @@
this.updatedAspectRatios = 0; this.updatedAspectRatios = 0;
} }
}, },
hoverChanged(data) { hoverChanged(data) {
this.hoverData = data; this.hoverData = data;
}, },
}, },
}; };
</script> </script>
<template> <template>
......
<script> <script>
export default { export default {
props: { props: {
documentationPath: { documentationPath: {
type: String, type: String,
...@@ -79,13 +79,12 @@ ...@@ -79,13 +79,12 @@
currentState() { currentState() {
return this.states[this.selectedState]; return this.states[this.selectedState];
}, },
showButtonDescription() { showButtonDescription() {
if (this.selectedState === 'unableToConnect') return true; if (this.selectedState === 'unableToConnect') return true;
return false; return false;
}, },
}, },
}; };
</script> </script>
<template> <template>
......
<script> <script>
import { scaleLinear, scaleTime } from 'd3-scale'; import { scaleLinear, scaleTime } from 'd3-scale';
import { axisLeft, axisBottom } from 'd3-axis'; import { axisLeft, axisBottom } from 'd3-axis';
import { max, extent } from 'd3-array'; import { max, extent } from 'd3-array';
import { select } from 'd3-selection'; import { select } from 'd3-selection';
import GraphLegend from './graph/legend.vue'; import GraphLegend from './graph/legend.vue';
import GraphFlag from './graph/flag.vue'; import GraphFlag from './graph/flag.vue';
import GraphDeployment from './graph/deployment.vue'; import GraphDeployment from './graph/deployment.vue';
import GraphPath from './graph/path.vue'; import GraphPath from './graph/path.vue';
import MonitoringMixin from '../mixins/monitoring_mixins'; import MonitoringMixin from '../mixins/monitoring_mixins';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
import measurements from '../utils/measurements'; import measurements from '../utils/measurements';
import { bisectDate, timeScaleFormat } from '../utils/date_time_formatters'; import { bisectDate, timeScaleFormat } from '../utils/date_time_formatters';
import createTimeSeries from '../utils/multiple_time_series'; import createTimeSeries from '../utils/multiple_time_series';
import bp from '../../breakpoints'; import bp from '../../breakpoints';
const d3 = { scaleLinear, scaleTime, axisLeft, axisBottom, max, extent, select }; const d3 = { scaleLinear, scaleTime, axisLeft, axisBottom, max, extent, select };
export default { export default {
components: { components: {
GraphLegend, GraphLegend,
GraphFlag, GraphFlag,
GraphDeployment, GraphDeployment,
GraphPath, GraphPath,
}, },
mixins: [MonitoringMixin], mixins: [MonitoringMixin],
props: { props: {
graphData: { graphData: {
type: Object, type: Object,
...@@ -63,7 +61,6 @@ ...@@ -63,7 +61,6 @@
default: false, default: false,
}, },
}, },
data() { data() {
return { return {
baseGraphHeight: 450, baseGraphHeight: 450,
...@@ -90,31 +87,25 @@ ...@@ -90,31 +87,25 @@
realPixelRatio: 1, realPixelRatio: 1,
}; };
}, },
computed: { computed: {
outerViewBox() { outerViewBox() {
return `0 0 ${this.baseGraphWidth} ${this.baseGraphHeight}`; return `0 0 ${this.baseGraphWidth} ${this.baseGraphHeight}`;
}, },
innerViewBox() { innerViewBox() {
return `0 0 ${this.baseGraphWidth - 150} ${this.baseGraphHeight}`; return `0 0 ${this.baseGraphWidth - 150} ${this.baseGraphHeight}`;
}, },
axisTransform() { axisTransform() {
return `translate(70, ${this.graphHeight - 100})`; return `translate(70, ${this.graphHeight - 100})`;
}, },
paddingBottomRootSvg() { paddingBottomRootSvg() {
return { return {
paddingBottom: `${(Math.ceil(this.baseGraphHeight * 100) / this.baseGraphWidth) || 0}%`, paddingBottom: `${Math.ceil(this.baseGraphHeight * 100) / this.baseGraphWidth || 0}%`,
}; };
}, },
deploymentFlagData() { deploymentFlagData() {
return this.reducedDeploymentData.find(deployment => deployment.showDeploymentFlag); return this.reducedDeploymentData.find(deployment => deployment.showDeploymentFlag);
}, },
}, },
watch: { watch: {
updateAspectRatio() { updateAspectRatio() {
if (this.updateAspectRatio) { if (this.updateAspectRatio) {
...@@ -125,16 +116,13 @@ ...@@ -125,16 +116,13 @@
eventHub.$emit('toggleAspectRatio'); eventHub.$emit('toggleAspectRatio');
} }
}, },
hoverData() { hoverData() {
this.positionFlag(); this.positionFlag();
}, },
}, },
mounted() { mounted() {
this.draw(); this.draw();
}, },
methods: { methods: {
draw() { draw() {
const breakpointSize = bp.getBreakpointSize(); const breakpointSize = bp.getBreakpointSize();
...@@ -148,19 +136,17 @@ ...@@ -148,19 +136,17 @@
this.unitOfDisplay = query.unit || ''; this.unitOfDisplay = query.unit || '';
this.yAxisLabel = this.graphData.y_label || 'Values'; this.yAxisLabel = this.graphData.y_label || 'Values';
this.legendTitle = query.label || 'Average'; this.legendTitle = query.label || 'Average';
this.graphWidth = this.$refs.baseSvg.clientWidth - this.graphWidth = this.$refs.baseSvg.clientWidth - this.margin.left - this.margin.right;
this.margin.left - this.margin.right;
this.graphHeight = this.graphHeight - this.margin.top - this.margin.bottom; this.graphHeight = this.graphHeight - this.margin.top - this.margin.bottom;
this.baseGraphHeight = this.graphHeight; this.baseGraphHeight = this.graphHeight;
this.baseGraphWidth = this.graphWidth; this.baseGraphWidth = this.graphWidth;
// pixel offsets inside the svg and outside are not 1:1 // pixel offsets inside the svg and outside are not 1:1
this.realPixelRatio = (this.$refs.baseSvg.clientWidth / this.baseGraphWidth); this.realPixelRatio = this.$refs.baseSvg.clientWidth / this.baseGraphWidth;
this.renderAxesPaths(); this.renderAxesPaths();
this.formatDeployments(); this.formatDeployments();
}, },
handleMouseOverGraph(e) { handleMouseOverGraph(e) {
let point = this.$refs.graphData.createSVGPoint(); let point = this.$refs.graphData.createSVGPoint();
point.x = e.clientX; point.x = e.clientX;
...@@ -174,7 +160,7 @@ ...@@ -174,7 +160,7 @@
const d1 = firstTimeSeries.values[overlayIndex]; const d1 = firstTimeSeries.values[overlayIndex];
if (d0 === undefined || d1 === undefined) return; if (d0 === undefined || d1 === undefined) return;
const evalTime = timeValueOverlay - d0[0] > d1[0] - timeValueOverlay; const evalTime = timeValueOverlay - d0[0] > d1[0] - timeValueOverlay;
const hoveredDataIndex = evalTime ? overlayIndex : (overlayIndex - 1); const hoveredDataIndex = evalTime ? overlayIndex : overlayIndex - 1;
const hoveredDate = firstTimeSeries.values[hoveredDataIndex].time; const hoveredDate = firstTimeSeries.values[hoveredDataIndex].time;
const currentDeployXPos = this.mouseOverDeployInfo(point.x); const currentDeployXPos = this.mouseOverDeployInfo(point.x);
...@@ -183,7 +169,6 @@ ...@@ -183,7 +169,6 @@
currentDeployXPos, currentDeployXPos,
}); });
}, },
renderAxesPaths() { renderAxesPaths() {
this.timeSeries = createTimeSeries( this.timeSeries = createTimeSeries(
this.graphData.queries, this.graphData.queries,
...@@ -198,39 +183,47 @@ ...@@ -198,39 +183,47 @@
this.baseGraphHeight = this.baseGraphHeight += (this.timeSeries.length - 3) * 20; this.baseGraphHeight = this.baseGraphHeight += (this.timeSeries.length - 3) * 20;
} }
const axisXScale = d3.scaleTime() const axisXScale = d3.scaleTime().range([0, this.graphWidth - 70]);
.range([0, this.graphWidth - 70]); const axisYScale = d3.scaleLinear().range([this.graphHeight - this.graphHeightOffset, 0]);
const axisYScale = d3.scaleLinear()
.range([this.graphHeight - this.graphHeightOffset, 0]);
const allValues = this.timeSeries.reduce((all, { values }) => all.concat(values), []); const allValues = this.timeSeries.reduce((all, { values }) => all.concat(values), []);
axisXScale.domain(d3.extent(allValues, d => d.time)); axisXScale.domain(d3.extent(allValues, d => d.time));
axisYScale.domain([0, d3.max(allValues.map(d => d.value))]); axisYScale.domain([0, d3.max(allValues.map(d => d.value))]);
const xAxis = d3.axisBottom() const xAxis = d3
.axisBottom()
.scale(axisXScale) .scale(axisXScale)
.ticks(this.graphWidth / 120) .ticks(this.graphWidth / 120)
.tickFormat(timeScaleFormat); .tickFormat(timeScaleFormat);
const yAxis = d3.axisLeft() const yAxis = d3
.axisLeft()
.scale(axisYScale) .scale(axisYScale)
.ticks(measurements.yTicks); .ticks(measurements.yTicks);
d3.select(this.$refs.baseSvg).select('.x-axis').call(xAxis); d3
.select(this.$refs.baseSvg)
.select('.x-axis')
.call(xAxis);
const width = this.graphWidth; const width = this.graphWidth;
d3.select(this.$refs.baseSvg).select('.y-axis').call(yAxis) d3
.select(this.$refs.baseSvg)
.select('.y-axis')
.call(yAxis)
.selectAll('.tick') .selectAll('.tick')
.each(function createTickLines(d, i) { .each(function createTickLines(d, i) {
if (i > 0) { if (i > 0) {
d3.select(this).select('line') d3
.select(this)
.select('line')
.attr('x2', width) .attr('x2', width)
.attr('class', 'axis-tick'); .attr('class', 'axis-tick');
} // Avoid adding the class to the first tick, to prevent coloring } // Avoid adding the class to the first tick, to prevent coloring
}); // This will select all of the ticks once they're rendered }); // This will select all of the ticks once they're rendered
}, },
}, },
}; };
</script> </script>
<template> <template>
......
<script> <script>
export default { export default {
props: { props: {
deploymentData: { deploymentData: {
type: Array, type: Array,
...@@ -14,19 +14,17 @@ ...@@ -14,19 +14,17 @@
required: true, required: true,
}, },
}, },
computed: { computed: {
calculatedHeight() { calculatedHeight() {
return this.graphHeight - this.graphHeightOffset; return this.graphHeight - this.graphHeightOffset;
}, },
}, },
methods: { methods: {
transformDeploymentGroup(deployment) { transformDeploymentGroup(deployment) {
return `translate(${Math.floor(deployment.xPos) - 5}, 20)`; return `translate(${Math.floor(deployment.xPos) - 5}, 20)`;
}, },
}, },
}; };
</script> </script>
<template> <template>
<g class="deploy-info"> <g class="deploy-info">
......
<script> <script>
import { dateFormat, timeFormat } from '../../utils/date_time_formatters'; import { dateFormat, timeFormat } from '../../utils/date_time_formatters';
import { formatRelevantDigits } from '../../../lib/utils/number_utils'; import { formatRelevantDigits } from '../../../lib/utils/number_utils';
import icon from '../../../vue_shared/components/icon.vue'; import icon from '../../../vue_shared/components/icon.vue';
export default { export default {
components: { components: {
icon, icon,
}, },
...@@ -54,24 +54,21 @@ ...@@ -54,24 +54,21 @@
required: true, required: true,
}, },
}, },
computed: { computed: {
formatTime() { formatTime() {
return this.deploymentFlagData ? return this.deploymentFlagData
timeFormat(this.deploymentFlagData.time) : ? timeFormat(this.deploymentFlagData.time)
timeFormat(this.currentData.time); : timeFormat(this.currentData.time);
}, },
formatDate() { formatDate() {
return this.deploymentFlagData ? return this.deploymentFlagData
dateFormat(this.deploymentFlagData.time) : ? dateFormat(this.deploymentFlagData.time)
dateFormat(this.currentData.time); : dateFormat(this.currentData.time);
}, },
cursorStyle() { cursorStyle() {
const xCoordinate = this.deploymentFlagData ? const xCoordinate = this.deploymentFlagData
this.deploymentFlagData.xPos : ? this.deploymentFlagData.xPos
this.currentXCoordinate; : this.currentXCoordinate;
const offsetTop = 20 * this.realPixelRatio; const offsetTop = 20 * this.realPixelRatio;
const offsetLeft = (70 + xCoordinate) * this.realPixelRatio; const offsetLeft = (70 + xCoordinate) * this.realPixelRatio;
...@@ -83,7 +80,6 @@ ...@@ -83,7 +80,6 @@
height: `${height}px`, height: `${height}px`,
}; };
}, },
flagOrientation() { flagOrientation() {
if (this.currentXCoordinate * this.realPixelRatio > 120) { if (this.currentXCoordinate * this.realPixelRatio > 120) {
return 'left'; return 'left';
...@@ -91,20 +87,17 @@ ...@@ -91,20 +87,17 @@
return 'right'; return 'right';
}, },
}, },
methods: { methods: {
seriesMetricValue(series) { seriesMetricValue(series) {
const index = this.deploymentFlagData ? const index = this.deploymentFlagData
this.deploymentFlagData.seriesIndex : ? this.deploymentFlagData.seriesIndex
this.currentDataIndex; : this.currentDataIndex;
const value = series.values[index] && const value = series.values[index] && series.values[index].value;
series.values[index].value;
if (isNaN(value)) { if (isNaN(value)) {
return '-'; return '-';
} }
return `${formatRelevantDigits(value)}${this.unitOfDisplay}`; return `${formatRelevantDigits(value)}${this.unitOfDisplay}`;
}, },
seriesMetricLabel(index, series) { seriesMetricLabel(index, series) {
if (this.timeSeries.length < 2) { if (this.timeSeries.length < 2) {
return this.legendTitle; return this.legendTitle;
...@@ -114,14 +107,13 @@ ...@@ -114,14 +107,13 @@
} }
return `series ${index + 1}`; return `series ${index + 1}`;
}, },
strokeDashArray(type) { strokeDashArray(type) {
if (type === 'dashed') return '6, 3'; if (type === 'dashed') return '6, 3';
if (type === 'dotted') return '3, 3'; if (type === 'dotted') return '3, 3';
return null; return null;
}, },
}, },
}; };
</script> </script>
<template> <template>
......
<script> <script>
import { formatRelevantDigits } from '../../../lib/utils/number_utils'; import { formatRelevantDigits } from '../../../lib/utils/number_utils';
export default { export default {
props: { props: {
graphWidth: { graphWidth: {
type: Number, type: Number,
...@@ -55,29 +55,24 @@ ...@@ -55,29 +55,24 @@
}, },
computed: { computed: {
textTransform() { textTransform() {
const yCoordinate = (((this.graphHeight - this.margin.top) const yCoordinate =
+ this.measurements.axisLabelLineOffset) / 2) || 0; (this.graphHeight - this.margin.top + this.measurements.axisLabelLineOffset) / 2 || 0;
return `translate(15, ${yCoordinate}) rotate(-90)`; return `translate(15, ${yCoordinate}) rotate(-90)`;
}, },
rectTransform() { rectTransform() {
const yCoordinate = (((this.graphHeight - this.margin.top) const yCoordinate =
+ this.measurements.axisLabelLineOffset) / 2) (this.graphHeight - this.margin.top + this.measurements.axisLabelLineOffset) / 2 +
+ (this.yLabelWidth / 2) || 0; this.yLabelWidth / 2 || 0;
return `translate(0, ${yCoordinate}) rotate(-90)`; return `translate(0, ${yCoordinate}) rotate(-90)`;
}, },
xPosition() { xPosition() {
return (((this.graphWidth + this.measurements.axisLabelLineOffset) / 2) return (this.graphWidth + this.measurements.axisLabelLineOffset) / 2 - this.margin.right || 0;
- this.margin.right) || 0;
}, },
yPosition() { yPosition() {
return ((this.graphHeight - this.margin.top) + this.measurements.axisLabelLineOffset) || 0; return this.graphHeight - this.margin.top + this.measurements.axisLabelLineOffset || 0;
}, },
}, },
mounted() { mounted() {
this.$nextTick(() => { this.$nextTick(() => {
...@@ -96,32 +91,29 @@ ...@@ -96,32 +91,29 @@
}, },
methods: { methods: {
translateLegendGroup(index) { translateLegendGroup(index) {
return `translate(0, ${12 * (index)})`; return `translate(0, ${12 * index})`;
}, },
formatMetricUsage(series) { formatMetricUsage(series) {
const value = series.values[this.currentDataIndex] && const value =
series.values[this.currentDataIndex].value; series.values[this.currentDataIndex] && series.values[this.currentDataIndex].value;
if (isNaN(value)) { if (isNaN(value)) {
return '-'; return '-';
} }
return `${formatRelevantDigits(value)} ${this.unitOfDisplay}`; return `${formatRelevantDigits(value)} ${this.unitOfDisplay}`;
}, },
createSeriesString(index, series) { createSeriesString(index, series) {
if (series.metricTag) { if (series.metricTag) {
return `${series.metricTag} ${this.formatMetricUsage(series)}`; return `${series.metricTag} ${this.formatMetricUsage(series)}`;
} }
return `${this.legendTitle} series ${index + 1} ${this.formatMetricUsage(series)}`; return `${this.legendTitle} series ${index + 1} ${this.formatMetricUsage(series)}`;
}, },
strokeDashArray(type) { strokeDashArray(type) {
if (type === 'dashed') return '6, 3'; if (type === 'dashed') return '6, 3';
if (type === 'dotted') return '3, 3'; if (type === 'dotted') return '3, 3';
return null; return null;
}, },
}, },
}; };
</script> </script>
<template> <template>
<g class="axis-label-container"> <g class="axis-label-container">
......
<script> <script>
export default { export default {
props: { props: {
generatedLinePath: { generatedLinePath: {
type: String, type: String,
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
return null; return null;
}, },
}, },
}; };
</script> </script>
<template> <template>
<g> <g>
......
<script> <script>
export default { export default {
props: { props: {
name: { name: {
type: String, type: String,
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
default: true, default: true,
}, },
}, },
}; };
</script> </script>
<template> <template>
......
<script> <script>
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import createFlash from '~/flash'; import createFlash from '~/flash';
import GlModal from '~/vue_shared/components/gl_modal.vue'; import GlModal from '~/vue_shared/components/gl_modal.vue';
import { s__, sprintf } from '~/locale'; import { s__, sprintf } from '~/locale';
import { visitUrl } from '~/lib/utils/url_utility'; import { visitUrl } from '~/lib/utils/url_utility';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
export default { export default {
components: { components: {
GlModal, GlModal,
}, },
...@@ -19,32 +19,75 @@ ...@@ -19,32 +19,75 @@
type: String, type: String,
required: true, required: true,
}, },
groupName: {
type: String,
required: true,
},
}, },
computed: { computed: {
title() { title() {
return sprintf(s__('Milestones|Promote %{milestoneTitle} to group milestone?'), { milestoneTitle: this.milestoneTitle }); return sprintf(s__('Milestones|Promote %{milestoneTitle} to group milestone?'), {
milestoneTitle: this.milestoneTitle,
});
}, },
text() { text() {
return s__(`Milestones|Promoting this milestone will make it available for all projects inside the group. const milestonePromotion = sprintf(
Existing project milestones with the same title will be merged. s__(`Milestones|Promoting %{milestone} will make it available for all projects inside %{groupName}.
This action cannot be reversed.`); Existing project milestones with the same name will be merged. `),
{
milestone: this.milestoneTitle,
groupName: this.groupName,
},
);
const missingFeatureWarn = sprintf(
s__(`Milestones|Group milestones are currently %{linkStart} missing features such as burndown charts. %{linkEnd}
You will not have these features once you've promoted a project milestone.
They will be available in future releases.`),
{
linkStart: `<a href="https://docs.gitlab.com/ee/user/project/milestones/"
target="_blank" rel="noopener noreferrer">`,
linkEnd: '</a>',
},
false,
);
const finalWarning = s__('Milestones|This action cannot be reversed.');
return sprintf(
s__(
`Milestones|<p>%{milestonePromotion}</p>
<p>%{missingFeatureWarn}</p>%{finalWarning}`,
),
{
milestonePromotion,
missingFeatureWarn,
finalWarning,
},
false,
);
}, },
}, },
methods: { methods: {
onSubmit() { onSubmit() {
eventHub.$emit('promoteMilestoneModal.requestStarted', this.url); eventHub.$emit('promoteMilestoneModal.requestStarted', this.url);
return axios.post(this.url, { params: { format: 'json' } }) return axios
.then((response) => { .post(this.url, { params: { format: 'json' } })
eventHub.$emit('promoteMilestoneModal.requestFinished', { milestoneUrl: this.url, successful: true }); .then(response => {
eventHub.$emit('promoteMilestoneModal.requestFinished', {
milestoneUrl: this.url,
successful: true,
});
visitUrl(response.data.url); visitUrl(response.data.url);
}) })
.catch((error) => { .catch(error => {
eventHub.$emit('promoteMilestoneModal.requestFinished', { milestoneUrl: this.url, successful: false }); eventHub.$emit('promoteMilestoneModal.requestFinished', {
milestoneUrl: this.url,
successful: false,
});
createFlash(error); createFlash(error);
}); });
}, },
}, },
}; };
</script> </script>
<template> <template>
<gl-modal <gl-modal
...@@ -58,7 +101,10 @@ ...@@ -58,7 +101,10 @@
> >
{{ title }} {{ title }}
</template> </template>
{{ text }} <div
v-html="text"
>
</div>
</gl-modal> </gl-modal>
</template> </template>
...@@ -25,6 +25,7 @@ export default () => { ...@@ -25,6 +25,7 @@ export default () => {
const modalProps = { const modalProps = {
milestoneTitle: button.dataset.milestoneTitle, milestoneTitle: button.dataset.milestoneTitle,
url: button.dataset.url, url: button.dataset.url,
groupName: button.dataset.groupName,
}; };
eventHub.$once('promoteMilestoneModal.requestStarted', onRequestStarted); eventHub.$once('promoteMilestoneModal.requestStarted', onRequestStarted);
eventHub.$emit('promoteMilestoneModal.props', modalProps); eventHub.$emit('promoteMilestoneModal.props', modalProps);
...@@ -54,6 +55,7 @@ export default () => { ...@@ -54,6 +55,7 @@ export default () => {
return { return {
modalProps: { modalProps: {
milestoneTitle: '', milestoneTitle: '',
groupName: '',
url: '', url: '',
}, },
}; };
......
<script> <script>
import _ from 'underscore';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import createFlash from '~/flash'; import createFlash from '~/flash';
import GlModal from '~/vue_shared/components/gl_modal.vue'; import GlModal from '~/vue_shared/components/gl_modal.vue';
...@@ -27,19 +28,26 @@ ...@@ -27,19 +28,26 @@
type: String, type: String,
required: true, required: true,
}, },
groupName: {
type: String,
required: true,
},
}, },
computed: { computed: {
text() { text() {
return s__(`Milestones|Promoting this label will make it available for all projects inside the group. return sprintf(s__(`Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}.
Existing project labels with the same title will be merged. This action cannot be reversed.`); Existing project labels with the same title will be merged. This action cannot be reversed.`), {
labelTitle: this.labelTitle,
groupName: this.groupName,
});
}, },
title() { title() {
const label = `<span const label = `<span
class="label color-label" class="label color-label"
style="background-color: ${this.labelColor}; color: ${this.labelTextColor};" style="background-color: ${this.labelColor}; color: ${this.labelTextColor};"
>${this.labelTitle}</span>`; >${_.escape(this.labelTitle)}</span>`;
return sprintf(s__('Labels|Promote label %{labelTitle} to Group Label?'), { return sprintf(s__('Labels|<span>Promote label</span> %{labelTitle} <span>to Group Label?</span>'), {
labelTitle: label, labelTitle: label,
}, false); }, false);
}, },
...@@ -69,6 +77,7 @@ ...@@ -69,6 +77,7 @@
> >
<div <div
slot="title" slot="title"
class="modal-title-with-label"
v-html="title" v-html="title"
> >
{{ title }} {{ title }}
......
...@@ -30,6 +30,7 @@ const initLabelIndex = () => { ...@@ -30,6 +30,7 @@ const initLabelIndex = () => {
labelColor: button.dataset.labelColor, labelColor: button.dataset.labelColor,
labelTextColor: button.dataset.labelTextColor, labelTextColor: button.dataset.labelTextColor,
url: button.dataset.url, url: button.dataset.url,
groupName: button.dataset.groupName,
}; };
eventHub.$once('promoteLabelModal.requestStarted', onRequestStarted); eventHub.$once('promoteLabelModal.requestStarted', onRequestStarted);
eventHub.$emit('promoteLabelModal.props', modalProps); eventHub.$emit('promoteLabelModal.props', modalProps);
...@@ -62,6 +63,7 @@ const initLabelIndex = () => { ...@@ -62,6 +63,7 @@ const initLabelIndex = () => {
labelColor: '', labelColor: '',
labelTextColor: '', labelTextColor: '',
url: '', url: '',
groupName: '',
}, },
}; };
}, },
......
<script>
import { parseSeconds, stringifyTime } from '../../../lib/utils/pretty_time'; import { parseSeconds, stringifyTime } from '../../../lib/utils/pretty_time';
export default { export default {
name: 'time-tracking-comparison-pane', name: 'TimeTrackingComparisonPane',
props: { props: {
timeSpent: { timeSpent: {
type: Number, type: Number,
...@@ -43,7 +44,10 @@ export default { ...@@ -43,7 +44,10 @@ export default {
return this.timeEstimate >= this.timeSpent ? 'within_estimate' : 'over_estimate'; return this.timeEstimate >= this.timeSpent ? 'within_estimate' : 'over_estimate';
}, },
}, },
template: ` };
</script>
<template>
<div class="time-tracking-comparison-pane"> <div class="time-tracking-comparison-pane">
<div <div
class="compare-meter" class="compare-meter"
...@@ -63,7 +67,8 @@ export default { ...@@ -63,7 +67,8 @@ export default {
<div <div
:style="{ width: timeRemainingPercent }" :style="{ width: timeRemainingPercent }"
class="meter-fill" class="meter-fill"
/> >
</div>
</div> </div>
<div class="compare-display-container"> <div class="compare-display-container">
<div class="compare-display pull-left"> <div class="compare-display pull-left">
...@@ -85,5 +90,4 @@ export default { ...@@ -85,5 +90,4 @@ export default {
</div> </div>
</div> </div>
</div> </div>
`, </template>
};
...@@ -4,7 +4,7 @@ import TimeTrackingCollapsedState from './collapsed_state.vue'; ...@@ -4,7 +4,7 @@ import TimeTrackingCollapsedState from './collapsed_state.vue';
import timeTrackingSpentOnlyPane from './spent_only_pane'; import timeTrackingSpentOnlyPane from './spent_only_pane';
import timeTrackingNoTrackingPane from './no_tracking_pane'; import timeTrackingNoTrackingPane from './no_tracking_pane';
import timeTrackingEstimateOnlyPane from './estimate_only_pane'; import timeTrackingEstimateOnlyPane from './estimate_only_pane';
import timeTrackingComparisonPane from './comparison_pane'; import TimeTrackingComparisonPane from './comparison_pane.vue';
import eventHub from '../../event_hub'; import eventHub from '../../event_hub';
...@@ -15,7 +15,7 @@ export default { ...@@ -15,7 +15,7 @@ export default {
'time-tracking-estimate-only-pane': timeTrackingEstimateOnlyPane, 'time-tracking-estimate-only-pane': timeTrackingEstimateOnlyPane,
'time-tracking-spent-only-pane': timeTrackingSpentOnlyPane, 'time-tracking-spent-only-pane': timeTrackingSpentOnlyPane,
'time-tracking-no-tracking-pane': timeTrackingNoTrackingPane, 'time-tracking-no-tracking-pane': timeTrackingNoTrackingPane,
'time-tracking-comparison-pane': timeTrackingComparisonPane, TimeTrackingComparisonPane,
'time-tracking-help-state': timeTrackingHelpState, 'time-tracking-help-state': timeTrackingHelpState,
}, },
props: { props: {
......
...@@ -17,8 +17,8 @@ export default { ...@@ -17,8 +17,8 @@ export default {
/> />
<div class="media-body space-children"> <div class="media-body space-children">
<span class="bold"> <span class="bold">
The source branch HEAD has recently changed. {{ s__(`mrWidget|The source branch HEAD has recently changed.
Please reload the page and review the changes before merging. Please reload the page and review the changes before merging`) }}
</span> </span>
</div> </div>
</div> </div>
......
...@@ -32,6 +32,11 @@ ...@@ -32,6 +32,11 @@
required: false, required: false,
default: '', default: '',
}, },
canAttachFile: {
type: Boolean,
required: false,
default: true,
},
enableAutocomplete: { enableAutocomplete: {
type: Boolean, type: Boolean,
required: false, required: false,
...@@ -139,6 +144,7 @@ ...@@ -139,6 +144,7 @@
<markdown-toolbar <markdown-toolbar
:markdown-docs-path="markdownDocsPath" :markdown-docs-path="markdownDocsPath"
:quick-actions-docs-path="quickActionsDocsPath" :quick-actions-docs-path="quickActionsDocsPath"
:can-attach-file="canAttachFile"
/> />
</div> </div>
</div> </div>
......
...@@ -10,6 +10,11 @@ ...@@ -10,6 +10,11 @@
required: false, required: false,
default: '', default: '',
}, },
canAttachFile: {
type: Boolean,
required: false,
default: true,
},
}, },
computed: { computed: {
hasQuickActionsDocsPath() { hasQuickActionsDocsPath() {
...@@ -50,7 +55,10 @@ ...@@ -50,7 +55,10 @@
are supported are supported
</template> </template>
</div> </div>
<span class="uploading-container"> <span
v-if="canAttachFile"
class="uploading-container"
>
<span class="uploading-progress-container hide"> <span class="uploading-progress-container hide">
<i <i
class="fa fa-file-image-o toolbar-button-icon" class="fa fa-file-image-o toolbar-button-icon"
......
...@@ -2,7 +2,15 @@ ...@@ -2,7 +2,15 @@
* Styles the GitLab application with a specific color theme * Styles the GitLab application with a specific color theme
*/ */
@mixin gitlab-theme($color-100, $color-200, $color-500, $color-700, $color-800, $color-900, $color-alternate) { @mixin gitlab-theme(
$color-100,
$color-200,
$color-500,
$color-700,
$color-800,
$color-900,
$color-alternate
) {
// Header // Header
.navbar-gitlab { .navbar-gitlab {
...@@ -23,7 +31,7 @@ ...@@ -23,7 +31,7 @@
> li { > li {
> a:hover, > a:hover,
> a:focus { > a:focus {
background-color: rgba($color-200, .2); background-color: rgba($color-200, 0.2);
} }
&.active > a, &.active > a,
...@@ -33,7 +41,7 @@ ...@@ -33,7 +41,7 @@
} }
&.line-separator { &.line-separator {
border-left: 1px solid rgba($color-200, .2); border-left: 1px solid rgba($color-200, 0.2);
} }
} }
} }
...@@ -56,7 +64,7 @@ ...@@ -56,7 +64,7 @@
&:hover, &:hover,
&:focus { &:focus {
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
background-color: rgba($color-200, .2); background-color: rgba($color-200, 0.2);
} }
svg { svg {
...@@ -91,34 +99,34 @@ ...@@ -91,34 +99,34 @@
> a { > a {
&:hover, &:hover,
&:focus { &:focus {
background-color: rgba($color-200, .2); background-color: rgba($color-200, 0.2);
} }
} }
} }
.search { .search {
form { form {
background-color: rgba($color-200, .2); background-color: rgba($color-200, 0.2);
&:hover { &:hover {
background-color: rgba($color-200, .3); background-color: rgba($color-200, 0.3);
} }
} }
.location-badge { .location-badge {
color: $color-100; color: $color-100;
background-color: rgba($color-200, .1); background-color: rgba($color-200, 0.1);
border-right: 1px solid $color-800; border-right: 1px solid $color-800;
} }
.search-input::placeholder { .search-input::placeholder {
color: rgba($color-200, .8); color: rgba($color-200, 0.8);
} }
.search-input-wrap { .search-input-wrap {
.search-icon, .search-icon,
.clear-icon { .clear-icon {
fill: rgba($color-200, .8); fill: rgba($color-200, 0.8);
} }
} }
...@@ -133,7 +141,7 @@ ...@@ -133,7 +141,7 @@
.search-input-wrap { .search-input-wrap {
.search-icon { .search-icon {
fill: rgba($color-200, .8); fill: rgba($color-200, 0.8);
} }
} }
} }
...@@ -144,7 +152,6 @@ ...@@ -144,7 +152,6 @@
color: $color-900; color: $color-900;
} }
// Sidebar // Sidebar
.nav-sidebar li.active { .nav-sidebar li.active {
box-shadow: inset 4px 0 0 $color-700; box-shadow: inset 4px 0 0 $color-700;
...@@ -169,28 +176,90 @@ ...@@ -169,28 +176,90 @@
font-weight: $gl-font-weight-bold; font-weight: $gl-font-weight-bold;
} }
} }
}
// Web IDE
.ide-sidebar-link {
color: $color-200;
background-color: $color-700;
&:hover,
&:focus {
background-color: $color-500;
}
&:active {
background: $color-800;
}
}
.branch-container {
border-left-color: $color-700;
}
.branch-header-title {
color: $color-700;
}
}
body { body {
&.ui_indigo { &.ui_indigo {
@include gitlab-theme($indigo-100, $indigo-200, $indigo-500, $indigo-700, $indigo-800, $indigo-900, $white-light); @include gitlab-theme(
$indigo-100,
$indigo-200,
$indigo-500,
$indigo-700,
$indigo-800,
$indigo-900,
$white-light
);
} }
&.ui_dark { &.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); @include gitlab-theme(
$theme-gray-100,
$theme-gray-200,
$theme-gray-500,
$theme-gray-700,
$theme-gray-800,
$theme-gray-900,
$white-light
);
} }
&.ui_blue { &.ui_blue {
@include gitlab-theme($theme-blue-100, $theme-blue-200, $theme-blue-500, $theme-blue-700, $theme-blue-800, $theme-blue-900, $white-light); @include gitlab-theme(
$theme-blue-100,
$theme-blue-200,
$theme-blue-500,
$theme-blue-700,
$theme-blue-800,
$theme-blue-900,
$white-light
);
} }
&.ui_green { &.ui_green {
@include gitlab-theme($theme-green-100, $theme-green-200, $theme-green-500, $theme-green-700, $theme-green-800, $theme-green-900, $white-light); @include gitlab-theme(
$theme-green-100,
$theme-green-200,
$theme-green-500,
$theme-green-700,
$theme-green-800,
$theme-green-900,
$white-light
);
} }
&.ui_light { &.ui_light {
@include gitlab-theme($theme-gray-900, $theme-gray-700, $theme-gray-800, $theme-gray-700, $theme-gray-700, $theme-gray-100, $theme-gray-700); @include gitlab-theme(
$theme-gray-900,
$theme-gray-700,
$theme-gray-800,
$theme-gray-700,
$theme-gray-700,
$theme-gray-100,
$theme-gray-700
);
.navbar-gitlab { .navbar-gitlab {
background-color: $theme-gray-100; background-color: $theme-gray-100;
...@@ -270,5 +339,9 @@ body { ...@@ -270,5 +339,9 @@ body {
.sidebar-top-level-items > li.active .badge { .sidebar-top-level-items > li.active .badge {
color: $theme-gray-900; color: $theme-gray-900;
} }
.ide-sidebar-link {
color: $white-light;
}
} }
} }
...@@ -4,9 +4,15 @@ ...@@ -4,9 +4,15 @@
.page-title, .page-title,
.modal-title { .modal-title {
.modal-title-with-label span {
vertical-align: middle;
display: inline-block;
}
.color-label { .color-label {
font-size: $gl-font-size; font-size: $gl-font-size;
padding: $gl-vert-padding $label-padding-modal; padding: $gl-vert-padding $label-padding-modal;
vertical-align: middle;
} }
} }
......
...@@ -402,7 +402,7 @@ ...@@ -402,7 +402,7 @@
} }
.branch-container { .branch-container {
border-left: 4px solid $indigo-700; border-left: 4px solid;
margin-bottom: $gl-bar-padding; margin-bottom: $gl-bar-padding;
} }
...@@ -414,7 +414,6 @@ ...@@ -414,7 +414,6 @@
.branch-header-title { .branch-header-title {
flex: 1; flex: 1;
padding: $grid-size $gl-padding; padding: $grid-size $gl-padding;
color: $indigo-700;
font-weight: $gl-font-weight-bold; font-weight: $gl-font-weight-bold;
svg { svg {
...@@ -767,20 +766,7 @@ ...@@ -767,20 +766,7 @@
.ide-sidebar-link { .ide-sidebar-link {
padding: $gl-padding-8 $gl-padding; padding: $gl-padding-8 $gl-padding;
background: $indigo-700;
color: $white-light;
text-decoration: none;
display: flex; display: flex;
align-items: center; align-items: center;
font-weight: $gl-font-weight-bold;
&:focus,
&:hover {
color: $white-light;
text-decoration: underline;
background: $indigo-500;
}
&:active {
background: $indigo-800;
}
} }
...@@ -112,7 +112,7 @@ class Projects::LabelsController < Projects::ApplicationController ...@@ -112,7 +112,7 @@ class Projects::LabelsController < Projects::ApplicationController
begin begin
return render_404 unless promote_service.execute(@label) return render_404 unless promote_service.execute(@label)
flash[:notice] = "#{@label.title} promoted to group label." flash[:notice] = "#{@label.title} promoted to <a href=\"#{group_labels_path(@project.group)}\">group label</a>.".html_safe
respond_to do |format| respond_to do |format|
format.html do format.html do
redirect_to(project_labels_path(@project), status: 303) redirect_to(project_labels_path(@project), status: 303)
......
...@@ -75,9 +75,9 @@ class Projects::MilestonesController < Projects::ApplicationController ...@@ -75,9 +75,9 @@ class Projects::MilestonesController < Projects::ApplicationController
end end
def promote def promote
Milestones::PromoteService.new(project, current_user).execute(milestone) promoted_milestone = Milestones::PromoteService.new(project, current_user).execute(milestone)
flash[:notice] = "#{milestone.title} promoted to group milestone" flash[:notice] = "#{milestone.title} promoted to <a href=\"#{group_milestone_path(project.group, promoted_milestone.iid)}\">group milestone</a>.".html_safe
respond_to do |format| respond_to do |format|
format.html do format.html do
redirect_to project_milestones_path(project) redirect_to project_milestones_path(project)
......
...@@ -5,12 +5,8 @@ class Projects::ProtectedBranchesController < Projects::ProtectedRefsController ...@@ -5,12 +5,8 @@ class Projects::ProtectedBranchesController < Projects::ProtectedRefsController
@project.repository.branches @project.repository.branches
end end
def create_service_class def service_namespace
::ProtectedBranches::CreateService ::ProtectedBranches
end
def update_service_class
::ProtectedBranches::UpdateService
end end
def load_protected_ref def load_protected_ref
......
...@@ -37,7 +37,7 @@ class Projects::ProtectedRefsController < Projects::ApplicationController ...@@ -37,7 +37,7 @@ class Projects::ProtectedRefsController < Projects::ApplicationController
end end
def destroy def destroy
@protected_ref.destroy destroy_service_class.new(@project, current_user).execute(@protected_ref)
respond_to do |format| respond_to do |format|
format.html { redirect_to_repository_settings(@project) } format.html { redirect_to_repository_settings(@project) }
...@@ -47,6 +47,18 @@ class Projects::ProtectedRefsController < Projects::ApplicationController ...@@ -47,6 +47,18 @@ class Projects::ProtectedRefsController < Projects::ApplicationController
protected protected
def create_service_class
service_namespace::CreateService
end
def update_service_class
service_namespace::UpdateService
end
def destroy_service_class
service_namespace::DestroyService
end
def access_level_attributes def access_level_attributes
%i(access_level id user_id _destroy group_id) %i(access_level id user_id _destroy group_id)
end end
......
...@@ -5,12 +5,8 @@ class Projects::ProtectedTagsController < Projects::ProtectedRefsController ...@@ -5,12 +5,8 @@ class Projects::ProtectedTagsController < Projects::ProtectedRefsController
@project.repository.tags @project.repository.tags
end end
def create_service_class def service_namespace
::ProtectedTags::CreateService ::ProtectedTags
end
def update_service_class
::ProtectedTags::UpdateService
end end
def load_protected_ref def load_protected_ref
......
...@@ -39,7 +39,10 @@ module PageLayoutHelper ...@@ -39,7 +39,10 @@ module PageLayoutHelper
end end
def favicon def favicon
Rails.env.development? ? 'favicon-green.ico' : 'favicon.ico' return 'favicon-yellow.ico' if Gitlab::Utils.to_boolean(ENV['CANARY'])
return 'favicon-green.ico' if Rails.env.development?
'favicon.ico'
end end
def page_image def page_image
......
...@@ -27,6 +27,9 @@ module Ci ...@@ -27,6 +27,9 @@ module Ci
has_one :job_artifacts_metadata, -> { where(file_type: Ci::JobArtifact.file_types[:metadata]) }, class_name: 'Ci::JobArtifact', inverse_of: :job, foreign_key: :job_id has_one :job_artifacts_metadata, -> { where(file_type: Ci::JobArtifact.file_types[:metadata]) }, class_name: 'Ci::JobArtifact', inverse_of: :job, foreign_key: :job_id
has_one :job_artifacts_trace, -> { where(file_type: Ci::JobArtifact.file_types[:trace]) }, class_name: 'Ci::JobArtifact', inverse_of: :job, foreign_key: :job_id has_one :job_artifacts_trace, -> { where(file_type: Ci::JobArtifact.file_types[:trace]) }, class_name: 'Ci::JobArtifact', inverse_of: :job, foreign_key: :job_id
has_one :metadata, class_name: 'Ci::BuildMetadata'
delegate :timeout, to: :metadata, prefix: true, allow_nil: true
## ##
# The "environment" field for builds is a String, and is the unexpanded name! # The "environment" field for builds is a String, and is the unexpanded name!
# #
...@@ -158,6 +161,14 @@ module Ci ...@@ -158,6 +161,14 @@ module Ci
before_transition any => [:running] do |build| before_transition any => [:running] do |build|
build.validates_dependencies! unless Feature.enabled?('ci_disable_validates_dependencies') build.validates_dependencies! unless Feature.enabled?('ci_disable_validates_dependencies')
end end
before_transition pending: :running do |build|
build.ensure_metadata.update_timeout_state
end
end
def ensure_metadata
metadata || build_metadata(project: project)
end end
def detailed_status(current_user) def detailed_status(current_user)
...@@ -238,10 +249,6 @@ module Ci ...@@ -238,10 +249,6 @@ module Ci
latest_builds.where('stage_idx < ?', stage_idx) latest_builds.where('stage_idx < ?', stage_idx)
end end
def timeout
project.build_timeout
end
def triggered_by?(current_user) def triggered_by?(current_user)
user == current_user user == current_user
end end
......
module Ci
# The purpose of this class is to store Build related data that can be disposed.
# Data that should be persisted forever, should be stored with Ci::Build model.
class BuildMetadata < ActiveRecord::Base
extend Gitlab::Ci::Model
include Presentable
include ChronicDurationAttribute
self.table_name = 'ci_builds_metadata'
belongs_to :build, class_name: 'Ci::Build'
belongs_to :project
validates :build, presence: true
validates :project, presence: true
chronic_duration_attr_reader :timeout_human_readable, :timeout
enum timeout_source: {
unknown_timeout_source: 1,
project_timeout_source: 2,
runner_timeout_source: 3
}
def update_timeout_state
return unless build.runner.present?
project_timeout = project&.build_timeout
timeout = [project_timeout, build.runner.maximum_timeout].compact.min
timeout_source = timeout < project_timeout ? :runner_timeout_source : :project_timeout_source
update(timeout: timeout, timeout_source: timeout_source)
end
end
end
...@@ -3,13 +3,14 @@ module Ci ...@@ -3,13 +3,14 @@ module Ci
extend Gitlab::Ci::Model extend Gitlab::Ci::Model
include Gitlab::SQL::Pattern include Gitlab::SQL::Pattern
include RedisCacheable include RedisCacheable
include ChronicDurationAttribute
prepend EE::Ci::Runner prepend EE::Ci::Runner
RUNNER_QUEUE_EXPIRY_TIME = 60.minutes RUNNER_QUEUE_EXPIRY_TIME = 60.minutes
ONLINE_CONTACT_TIMEOUT = 1.hour ONLINE_CONTACT_TIMEOUT = 1.hour
UPDATE_DB_RUNNER_INFO_EVERY = 40.minutes UPDATE_DB_RUNNER_INFO_EVERY = 40.minutes
AVAILABLE_SCOPES = %w[specific shared active paused online].freeze AVAILABLE_SCOPES = %w[specific shared active paused online].freeze
FORM_EDITABLE = %i[description tag_list active run_untagged locked access_level].freeze FORM_EDITABLE = %i[description tag_list active run_untagged locked access_level maximum_timeout_human_readable].freeze
has_many :builds has_many :builds
has_many :runner_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :runner_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
...@@ -52,6 +53,12 @@ module Ci ...@@ -52,6 +53,12 @@ module Ci
cached_attr_reader :version, :revision, :platform, :architecture, :contacted_at, :ip_address cached_attr_reader :version, :revision, :platform, :architecture, :contacted_at, :ip_address
chronic_duration_attr :maximum_timeout_human_readable, :maximum_timeout
validates :maximum_timeout, allow_nil: true,
numericality: { greater_than_or_equal_to: 600,
message: 'needs to be at least 10 minutes' }
# Searches for runners matching the given query. # Searches for runners matching the given query.
# #
# This method uses ILIKE on PostgreSQL and LIKE on MySQL. # This method uses ILIKE on PostgreSQL and LIKE on MySQL.
......
module ChronicDurationAttribute
extend ActiveSupport::Concern
class_methods do
def chronic_duration_attr_reader(virtual_attribute, source_attribute)
define_method(virtual_attribute) do
chronic_duration_attributes[virtual_attribute] || output_chronic_duration_attribute(source_attribute)
end
end
def chronic_duration_attr_writer(virtual_attribute, source_attribute)
chronic_duration_attr_reader(virtual_attribute, source_attribute)
define_method("#{virtual_attribute}=") do |value|
chronic_duration_attributes[virtual_attribute] = value.presence || ''
begin
new_value = ChronicDuration.parse(value).to_i if value.present?
assign_attributes(source_attribute => new_value)
rescue ChronicDuration::DurationParseError
# ignore error as it will be caught by validation
end
end
validates virtual_attribute, allow_nil: true, duration: true
end
alias_method :chronic_duration_attr, :chronic_duration_attr_writer
end
def chronic_duration_attributes
@chronic_duration_attributes ||= {}
end
def output_chronic_duration_attribute(source_attribute)
value = attributes[source_attribute.to_s]
ChronicDuration.output(value, format: :short) if value
end
end
...@@ -562,18 +562,25 @@ class MergeRequest < ActiveRecord::Base ...@@ -562,18 +562,25 @@ class MergeRequest < ActiveRecord::Base
merge_request_diff(true) merge_request_diff(true)
end end
def viewable_diffs
@viewable_diffs ||= merge_request_diffs.viewable.to_a
end
def merge_request_diff_for(diff_refs_or_sha) def merge_request_diff_for(diff_refs_or_sha)
@merge_request_diffs_by_diff_refs_or_sha ||= Hash.new do |h, diff_refs_or_sha| matcher =
diffs = merge_request_diffs.viewable
h[diff_refs_or_sha] =
if diff_refs_or_sha.is_a?(Gitlab::Diff::DiffRefs) if diff_refs_or_sha.is_a?(Gitlab::Diff::DiffRefs)
diffs.find_by_diff_refs(diff_refs_or_sha) {
'start_commit_sha' => diff_refs_or_sha.start_sha,
'head_commit_sha' => diff_refs_or_sha.head_sha,
'base_commit_sha' => diff_refs_or_sha.base_sha
}
else else
diffs.find_by(head_commit_sha: diff_refs_or_sha) { 'head_commit_sha' => diff_refs_or_sha }
end
end end
@merge_request_diffs_by_diff_refs_or_sha[diff_refs_or_sha] viewable_diffs.find do |diff|
diff.attributes.slice(*matcher.keys) == matcher
end
end end
def version_params_for(diff_refs) def version_params_for(diff_refs)
......
...@@ -36,7 +36,7 @@ class GemnasiumService < Service ...@@ -36,7 +36,7 @@ class GemnasiumService < Service
after: data[:after], after: data[:after],
token: token, token: token,
api_key: api_key, api_key: api_key,
repo: project.repository.path_to_repo repo: project.repository.path_to_repo # Gitaly: fixed by https://gitlab.com/gitlab-org/security-products/gemnasium-migration/issues/9
) )
end end
end end
...@@ -100,10 +100,6 @@ class Repository ...@@ -100,10 +100,6 @@ class Repository
"#<#{self.class.name}:#{@disk_path}>" "#<#{self.class.name}:#{@disk_path}>"
end end
def create_hooks
Gitlab::Git::Repository.create_hooks(path_to_repo, Gitlab.config.gitlab_shell.hooks_path)
end
def commit(ref = 'HEAD') def commit(ref = 'HEAD')
return nil unless exists? return nil unless exists?
return ref if ref.is_a?(::Commit) return ref if ref.is_a?(::Commit)
......
class ProtectedBranchPolicy < BasePolicy
delegate { @subject.project }
rule { can?(:admin_project) }.policy do
enable :create_protected_branch
enable :update_protected_branch
enable :destroy_protected_branch
end
end
module Ci
class BuildMetadataPresenter < Gitlab::View::Presenter::Delegated
TIMEOUT_SOURCES = {
unknown_timeout_source: nil,
project_timeout_source: 'project',
runner_timeout_source: 'runner'
}.freeze
presents :metadata
def timeout_source
return unless metadata.timeout_source?
TIMEOUT_SOURCES[metadata.timeout_source.to_sym] ||
metadata.timeout_source
end
end
end
...@@ -5,6 +5,8 @@ class BuildDetailsEntity < JobEntity ...@@ -5,6 +5,8 @@ class BuildDetailsEntity < JobEntity
expose :runner, using: RunnerEntity expose :runner, using: RunnerEntity
expose :pipeline, using: PipelineEntity expose :pipeline, using: PipelineEntity
expose :metadata, using: BuildMetadataEntity
expose :erased_by, if: -> (*) { build.erased? }, using: UserEntity expose :erased_by, if: -> (*) { build.erased? }, using: UserEntity
expose :erase_path, if: -> (*) { build.erasable? && can?(current_user, :erase_build, build) } do |build| expose :erase_path, if: -> (*) { build.erasable? && can?(current_user, :erase_build, build) } do |build|
erase_project_job_path(project, build) erase_project_job_path(project, build)
......
class BuildMetadataEntity < Grape::Entity
expose :timeout_human_readable do |metadata|
metadata.timeout_human_readable unless metadata.timeout.nil?
end
expose :timeout_source do |metadata|
metadata.present.timeout_source
end
end
...@@ -7,8 +7,14 @@ class StatusEntity < Grape::Entity ...@@ -7,8 +7,14 @@ class StatusEntity < Grape::Entity
expose :details_path expose :details_path
expose :favicon do |status| expose :favicon do |status|
dir = 'ci_favicons' dir =
dir = File.join(dir, 'dev') if Rails.env.development? if Gitlab::Utils.to_boolean(ENV['CANARY'])
File.join('ci_favicons', 'canary')
elsif Rails.env.development?
File.join('ci_favicons', 'dev')
else
'ci_favicons'
end
ActionController::Base.helpers.image_path(File.join(dir, "#{status.favicon}.ico")) ActionController::Base.helpers.image_path(File.join(dir, "#{status.favicon}.ico"))
end end
......
module ProtectedBranches module ProtectedBranches
class CreateService < BaseService class CreateService < BaseService
attr_reader :protected_branch
def execute(skip_authorization: false) def execute(skip_authorization: false)
raise Gitlab::Access::AccessDeniedError unless skip_authorization || can?(current_user, :admin_project, project) raise Gitlab::Access::AccessDeniedError unless skip_authorization || authorized?
protected_branch.save
protected_branch
end
def authorized?
can?(current_user, :create_protected_branch, protected_branch)
end
private
project.protected_branches.create(params) def protected_branch
@protected_branch ||= project.protected_branches.new(params)
end end
end end
end end
module ProtectedBranches
class DestroyService < BaseService
def execute(protected_branch)
raise Gitlab::Access::AccessDeniedError unless can?(current_user, :destroy_protected_branch, protected_branch)
protected_branch.destroy
end
end
end
module ProtectedBranches module ProtectedBranches
class UpdateService < BaseService class UpdateService < BaseService
def execute(protected_branch) def execute(protected_branch)
raise Gitlab::Access::AccessDeniedError unless can?(current_user, :admin_project, project) raise Gitlab::Access::AccessDeniedError unless can?(current_user, :update_protected_branch, protected_branch)
protected_branch.update(params) protected_branch.update(params)
protected_branch protected_branch
......
module ProtectedTags
class DestroyService < BaseService
def execute(protected_tag)
protected_tag.destroy
end
end
end
...@@ -34,7 +34,8 @@ class VerifyPagesDomainService < BaseService ...@@ -34,7 +34,8 @@ class VerifyPagesDomainService < BaseService
# Prevent any pre-existing grace period from being truncated # Prevent any pre-existing grace period from being truncated
reverify = [domain.enabled_until, VERIFICATION_PERIOD.from_now].compact.max reverify = [domain.enabled_until, VERIFICATION_PERIOD.from_now].compact.max
domain.update!(verified_at: Time.now, enabled_until: reverify) domain.assign_attributes(verified_at: Time.now, enabled_until: reverify)
domain.save!(validate: false)
if was_disabled if was_disabled
notify(:enabled) notify(:enabled)
...@@ -47,7 +48,9 @@ class VerifyPagesDomainService < BaseService ...@@ -47,7 +48,9 @@ class VerifyPagesDomainService < BaseService
def unverify_domain! def unverify_domain!
if domain.verified? if domain.verified?
domain.update!(verified_at: nil) domain.assign_attributes(verified_at: nil)
domain.save!(validate: false)
notify(:verification_failed) notify(:verification_failed)
end end
...@@ -55,7 +58,8 @@ class VerifyPagesDomainService < BaseService ...@@ -55,7 +58,8 @@ class VerifyPagesDomainService < BaseService
end end
def disable_domain! def disable_domain!
domain.update!(verified_at: nil, enabled_until: nil) domain.assign_attributes(verified_at: nil, enabled_until: nil)
domain.save!(validate: false)
notify(:disabled) notify(:disabled)
......
...@@ -228,17 +228,9 @@ module ObjectStorage ...@@ -228,17 +228,9 @@ module ObjectStorage
raise 'Failed to update object store' unless updated raise 'Failed to update object store' unless updated
end end
def use_file def use_file(&blk)
if file_storage? with_exclusive_lease do
return yield path unsafe_use_file(&blk)
end
begin
cache_stored_file!
yield cache_path
ensure
FileUtils.rm_f(cache_path)
cache_storage.delete_dir!(cache_path(nil))
end end
end end
...@@ -248,12 +240,9 @@ module ObjectStorage ...@@ -248,12 +240,9 @@ module ObjectStorage
# new_store: Enum (Store::LOCAL, Store::REMOTE) # new_store: Enum (Store::LOCAL, Store::REMOTE)
# #
def migrate!(new_store) def migrate!(new_store)
uuid = Gitlab::ExclusiveLease.new(exclusive_lease_key, timeout: 1.hour.to_i).try_obtain with_exclusive_lease do
raise 'Already running' unless uuid
unsafe_migrate!(new_store) unsafe_migrate!(new_store)
ensure end
Gitlab::ExclusiveLease.cancel(exclusive_lease_key, uuid)
end end
def schedule_background_upload(*args) def schedule_background_upload(*args)
...@@ -385,6 +374,15 @@ module ObjectStorage ...@@ -385,6 +374,15 @@ module ObjectStorage
"object_storage_migrate:#{model.class}:#{model.id}" "object_storage_migrate:#{model.class}:#{model.id}"
end end
def with_exclusive_lease
uuid = Gitlab::ExclusiveLease.new(exclusive_lease_key, timeout: 1.hour.to_i).try_obtain
raise 'exclusive lease already taken' unless uuid
yield uuid
ensure
Gitlab::ExclusiveLease.cancel(exclusive_lease_key, uuid)
end
# #
# Move the file to another store # Move the file to another store
# #
...@@ -419,4 +417,18 @@ module ObjectStorage ...@@ -419,4 +417,18 @@ module ObjectStorage
raise e raise e
end end
end end
def unsafe_use_file
if file_storage?
return yield path
end
begin
cache_stored_file!
yield cache_path
ensure
FileUtils.rm_f(cache_path)
cache_storage.delete_dir!(cache_path(nil))
end
end
end end
...@@ -62,12 +62,16 @@ ...@@ -62,12 +62,16 @@
= link_to @project.ssh_url_to_repo, project_path(@project) = link_to @project.ssh_url_to_repo, project_path(@project)
- if @project.repository.exists? - if @project.repository.exists?
%li %li
%span.light fs: %span.light Gitaly storage name:
%strong %strong
= @project.repository.path_to_repo = @project.repository.storage
%li
%span.light Gitaly relative path:
%strong
= @project.repository.relative_path
%li %li
%span.light Storage: %span.light Storage used:
%strong= storage_counter(@project.statistics.storage_size) %strong= storage_counter(@project.statistics.storage_size)
( (
= storage_counter(@project.statistics.repository_size) = storage_counter(@project.statistics.repository_size)
......
...@@ -85,19 +85,6 @@ ...@@ -85,19 +85,6 @@
= link_to project_path_locks_path(@project) do = link_to project_path_locks_path(@project) do
#{ _('Locked Files') } #{ _('Locked Files') }
- if project_nav_tab? :container_registry
= nav_link(controller: %w[projects/registry/repositories]) do
= link_to project_container_registry_index_path(@project), class: 'shortcuts-container-registry' do
.nav-icon-container
= sprite_icon('disk')
%span.nav-item-name
Registry
%ul.sidebar-sub-level-items.is-fly-out-only
= nav_link(controller: %w[projects/registry/repositories], html_options: { class: "fly-out-top-item" } ) do
= link_to project_container_registry_index_path(@project) do
%strong.fly-out-top-item-name
#{ _('Registry') }
- if project_nav_tab? :issues - if project_nav_tab? :issues
= nav_link(controller: @project.issues_enabled? ? [:issues, :labels, :milestones, :boards] : :issues) do = nav_link(controller: @project.issues_enabled? ? [:issues, :labels, :milestones, :boards] : :issues) do
= link_to project_issues_path(@project), class: 'shortcuts-issues' do = link_to project_issues_path(@project), class: 'shortcuts-issues' do
...@@ -245,6 +232,19 @@ ...@@ -245,6 +232,19 @@
%span %span
Charts Charts
- if project_nav_tab? :container_registry
= nav_link(controller: %w[projects/registry/repositories]) do
= link_to project_container_registry_index_path(@project), class: 'shortcuts-container-registry' do
.nav-icon-container
= sprite_icon('disk')
%span.nav-item-name
Registry
%ul.sidebar-sub-level-items.is-fly-out-only
= nav_link(controller: %w[projects/registry/repositories], html_options: { class: "fly-out-top-item" } ) do
= link_to project_container_registry_index_path(@project) do
%strong.fly-out-top-item-name
#{ _('Registry') }
- if project_nav_tab? :wiki - if project_nav_tab? :wiki
= nav_link(controller: :wikis) do = nav_link(controller: :wikis) do
= link_to get_project_wiki_path(@project), class: 'shortcuts-wiki' do = link_to get_project_wiki_path(@project), class: 'shortcuts-wiki' do
......
%h4.prepend-top-20 %h4.prepend-top-20
= s_('ClusterIntegration|Enter the details for your Kubernetes cluster') = s_('ClusterIntegration|Enter the details for your Kubernetes cluster')
%p %p
- link_to_help_page = link_to(s_('ClusterIntegration|documentation'), help_page_path('user/project/clusters/index'), target: '_blank', rel: 'noopener noreferrer') - link_to_help_page = link_to(s_('ClusterIntegration|documentation'), help_page_path('user/project/clusters/index', anchor: 'adding-an-existing-kubernetes-cluster'), target: '_blank', rel: 'noopener noreferrer')
= s_('ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes').html_safe % { link_to_help_page: link_to_help_page } = s_('ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes').html_safe % { link_to_help_page: link_to_help_page }
...@@ -113,4 +113,4 @@ ...@@ -113,4 +113,4 @@
.js-build-options{ data: javascript_build_options } .js-build-options{ data: javascript_build_options }
#js-job-details-vue{ data: { endpoint: project_job_path(@project, @build, format: :json) } } #js-job-details-vue{ data: { endpoint: project_job_path(@project, @build, format: :json), runner_help_url: help_page_path('ci/runners/README.html', anchor: 'setting-maximum-job-timeout-for-a-runner') } }
...@@ -30,6 +30,7 @@ ...@@ -30,6 +30,7 @@
%button.js-promote-project-milestone-button.btn.btn-grouped{ data: { toggle: 'modal', %button.js-promote-project-milestone-button.btn.btn-grouped{ data: { toggle: 'modal',
target: '#promote-milestone-modal', target: '#promote-milestone-modal',
milestone_title: @milestone.title, milestone_title: @milestone.title,
group_name: @project.group.name,
url: promote_project_milestone_path(@milestone.project, @milestone), url: promote_project_milestone_path(@milestone.project, @milestone),
container: 'body' }, container: 'body' },
disabled: true, disabled: true,
......
...@@ -8,3 +8,5 @@ ...@@ -8,3 +8,5 @@
= render 'form', { f: f } = render 'form', { f: f }
.form-actions .form-actions
= f.submit 'Create New Domain', class: "btn btn-save" = f.submit 'Create New Domain', class: "btn btn-save"
.pull-right
= link_to _('Cancel'), project_pages_path(@project), class: 'btn btn-cancel'
...@@ -39,6 +39,12 @@ ...@@ -39,6 +39,12 @@
Description Description
.col-sm-10 .col-sm-10
= f.text_field :description, class: 'form-control' = f.text_field :description, class: 'form-control'
.form-group
= label_tag :maximum_timeout_human_readable, class: 'control-label' do
Maximum job timeout
.col-sm-10
= f.text_field :maximum_timeout_human_readable, class: 'form-control'
.help-block This timeout will take precedence when lower than Project-defined timeout
.form-group .form-group
= label_tag :tag_list, class: 'control-label' do = label_tag :tag_list, class: 'control-label' do
Tags Tags
......
...@@ -55,6 +55,9 @@ ...@@ -55,6 +55,9 @@
%tr %tr
%td Description %td Description
%td= @runner.description %td= @runner.description
%tr
%td Maximum job timeout
%td= @runner.maximum_timeout_human_readable
%tr %tr
%td Last contact %td Last contact
%td %td
......
...@@ -60,6 +60,7 @@ ...@@ -60,6 +60,7 @@
label_title: label.title, label_title: label.title,
label_color: label.color, label_color: label.color,
label_text_color: label.text_color, label_text_color: label.text_color,
group_name: label.project.group.name,
target: '#promote-label-modal', target: '#promote-label-modal',
container: 'body', container: 'body',
toggle: 'modal' } } toggle: 'modal' } }
......
...@@ -56,6 +56,7 @@ ...@@ -56,6 +56,7 @@
type: 'button', type: 'button',
data: { url: promote_project_milestone_path(milestone.project, milestone), data: { url: promote_project_milestone_path(milestone.project, milestone),
milestone_title: milestone.title, milestone_title: milestone.title,
group_name: @project.group.name,
target: '#promote-milestone-modal', target: '#promote-milestone-modal',
container: 'body', container: 'body',
toggle: 'modal' } } toggle: 'modal' } }
......
...@@ -28,16 +28,17 @@ class GitGarbageCollectWorker ...@@ -28,16 +28,17 @@ class GitGarbageCollectWorker
task = task.to_sym task = task.to_sym
cmd = command(task) cmd = command(task)
repo_path = project.repository.path_to_repo
description = "'#{cmd.join(' ')}' in #{repo_path}"
Gitlab::GitLogger.info(description)
gitaly_migrate(GITALY_MIGRATED_TASKS[task]) do |is_enabled| gitaly_migrate(GITALY_MIGRATED_TASKS[task]) do |is_enabled|
if is_enabled if is_enabled
gitaly_call(task, project.repository.raw_repository) gitaly_call(task, project.repository.raw_repository)
else else
repo_path = project.repository.path_to_repo
description = "'#{cmd.join(' ')}' in #{repo_path}"
Gitlab::GitLogger.info(description)
output, status = Gitlab::Popen.popen(cmd, repo_path) output, status = Gitlab::Popen.popen(cmd, repo_path)
Gitlab::GitLogger.error("#{description} failed:\n#{output}") unless status.zero? Gitlab::GitLogger.error("#{description} failed:\n#{output}") unless status.zero?
end end
end end
......
# Gitaly issue: https://gitlab.com/gitlab-org/gitaly/issues/1110
class RepositoryForkWorker class RepositoryForkWorker
include ApplicationWorker include ApplicationWorker
include Gitlab::ShellAdapter include Gitlab::ShellAdapter
......
---
title: Adds cancel btn to new pages domain page
merge_request: 18026
author: Jacopo Beschi @jacopo-beschi
type: added
---
title: Fixed bug in dropdown selector when selecting the same selection again
merge_request: 14631
author: bitsapien
type: fixed
---
title: Avoid validation errors when running the Pages domain verification service
merge_request: 17992
author:
type: fixed
---
title: Update asciidoctor-plantuml to 0.0.8
merge_request: 18022
author: Takuya Noguchi
type: performance
---
title: Remove unused index from events table.
merge_request: 18014
author:
type: other
---
title: Fix data race between ObjectStorage background_upload and Pages publishing
merge_request:
author:
type: fixed
---
title: Add yellow favicon when `CANARY=true` to differientate canary environment
merge_request: 12477
author:
type: changed
---
title: Add per-runner configured job timeout
merge_request: 17221
author:
type: added
---
title: Fix listing commit branch/tags that contain special characters
merge_request:
author:
type: fixed
---
title: Correct copy text for the promote milestone and label modals
merge_request: 17726
author:
type: fixed
---
title: Move 'Registry' after 'CI/CD' in project navigation sidebar
merge_request: 18018
author: Elias Werberich
type: changed
---
title: Reduce number of queries when viewing a merge request
merge_request:
author:
type: performance
---
title: Move TimeTrackingComparisonPane vue component
merge_request: 17931
author: George Tsiolis
type: performance
---
title: Add i18n and update specs for ShaMismatch vue component
merge_request: 17870
author: George Tsiolis
type: performance
--- ---
title: Make all workhorse gitaly calls opt-out title: Make all workhorse gitaly calls opt-out, take 2
merge_request: 18002 merge_request: 18043
author: author:
type: other type: other
---
title: Test if remote repository exists when importing wikis
merge_request:
author:
type: fixed
class AddMaximumTimeoutToCiRunners < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
add_column :ci_runners, :maximum_timeout, :integer
end
end
class CreateCiBuildsMetadataTable < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
create_table :ci_builds_metadata do |t|
t.integer :build_id, null: false
t.integer :project_id, null: false
t.integer :timeout
t.integer :timeout_source, null: false, default: 1
t.foreign_key :ci_builds, column: :build_id, on_delete: :cascade
t.foreign_key :projects, column: :project_id, on_delete: :cascade
t.index :build_id, unique: true
t.index :project_id
end
end
end
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class RemoveIndexFromEventsTable < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
remove_concurrent_index :events, :author_id
end
def down
add_concurrent_index :events, :author_id
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20180323150945) do ActiveRecord::Schema.define(version: 20180327101207) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -411,6 +411,16 @@ ActiveRecord::Schema.define(version: 20180323150945) do ...@@ -411,6 +411,16 @@ ActiveRecord::Schema.define(version: 20180323150945) do
add_index "ci_builds", ["updated_at"], name: "index_ci_builds_on_updated_at", using: :btree add_index "ci_builds", ["updated_at"], name: "index_ci_builds_on_updated_at", using: :btree
add_index "ci_builds", ["user_id"], name: "index_ci_builds_on_user_id", using: :btree add_index "ci_builds", ["user_id"], name: "index_ci_builds_on_user_id", using: :btree
create_table "ci_builds_metadata", force: :cascade do |t|
t.integer "build_id", null: false
t.integer "project_id", null: false
t.integer "timeout"
t.integer "timeout_source", default: 1, null: false
end
add_index "ci_builds_metadata", ["build_id"], name: "index_ci_builds_metadata_on_build_id", unique: true, using: :btree
add_index "ci_builds_metadata", ["project_id"], name: "index_ci_builds_metadata_on_project_id", using: :btree
create_table "ci_group_variables", force: :cascade do |t| create_table "ci_group_variables", force: :cascade do |t|
t.string "key", null: false t.string "key", null: false
t.text "value" t.text "value"
...@@ -549,6 +559,7 @@ ActiveRecord::Schema.define(version: 20180323150945) do ...@@ -549,6 +559,7 @@ ActiveRecord::Schema.define(version: 20180323150945) do
t.boolean "locked", default: false, null: false t.boolean "locked", default: false, null: false
t.integer "access_level", default: 0, null: false t.integer "access_level", default: 0, null: false
t.string "ip_address" t.string "ip_address"
t.integer "maximum_timeout"
end end
add_index "ci_runners", ["contacted_at"], name: "index_ci_runners_on_contacted_at", using: :btree add_index "ci_runners", ["contacted_at"], name: "index_ci_runners_on_contacted_at", using: :btree
...@@ -882,7 +893,6 @@ ActiveRecord::Schema.define(version: 20180323150945) do ...@@ -882,7 +893,6 @@ ActiveRecord::Schema.define(version: 20180323150945) do
add_index "events", ["action"], name: "index_events_on_action", using: :btree add_index "events", ["action"], name: "index_events_on_action", using: :btree
add_index "events", ["author_id", "project_id"], name: "index_events_on_author_id_and_project_id", using: :btree add_index "events", ["author_id", "project_id"], name: "index_events_on_author_id_and_project_id", using: :btree
add_index "events", ["author_id"], name: "index_events_on_author_id", using: :btree
add_index "events", ["project_id", "id"], name: "index_events_on_project_id_and_id", using: :btree add_index "events", ["project_id", "id"], name: "index_events_on_project_id_and_id", using: :btree
add_index "events", ["target_type", "target_id"], name: "index_events_on_target_type_and_target_id", using: :btree add_index "events", ["target_type", "target_id"], name: "index_events_on_target_type_and_target_id", using: :btree
...@@ -2606,6 +2616,8 @@ ActiveRecord::Schema.define(version: 20180323150945) do ...@@ -2606,6 +2616,8 @@ ActiveRecord::Schema.define(version: 20180323150945) do
add_foreign_key "ci_builds", "ci_pipelines", column: "auto_canceled_by_id", name: "fk_a2141b1522", on_delete: :nullify add_foreign_key "ci_builds", "ci_pipelines", column: "auto_canceled_by_id", name: "fk_a2141b1522", on_delete: :nullify
add_foreign_key "ci_builds", "ci_stages", column: "stage_id", name: "fk_3a9eaa254d", on_delete: :cascade add_foreign_key "ci_builds", "ci_stages", column: "stage_id", name: "fk_3a9eaa254d", on_delete: :cascade
add_foreign_key "ci_builds", "projects", name: "fk_befce0568a", on_delete: :cascade add_foreign_key "ci_builds", "projects", name: "fk_befce0568a", on_delete: :cascade
add_foreign_key "ci_builds_metadata", "ci_builds", column: "build_id", on_delete: :cascade
add_foreign_key "ci_builds_metadata", "projects", on_delete: :cascade
add_foreign_key "ci_group_variables", "namespaces", column: "group_id", name: "fk_33ae4d58d8", on_delete: :cascade add_foreign_key "ci_group_variables", "namespaces", column: "group_id", name: "fk_33ae4d58d8", on_delete: :cascade
add_foreign_key "ci_job_artifacts", "ci_builds", column: "job_id", on_delete: :cascade add_foreign_key "ci_job_artifacts", "ci_builds", column: "job_id", on_delete: :cascade
add_foreign_key "ci_job_artifacts", "projects", on_delete: :cascade add_foreign_key "ci_job_artifacts", "projects", on_delete: :cascade
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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