Skip to content
Snippets Groups Projects
Commit eabf8fd7 authored by GitLab Bot's avatar GitLab Bot
Browse files

Add latest changes from gitlab-org/gitlab@master

parent 20d564f1
No related branches found
No related tags found
No related merge requests found
Showing
with 53 additions and 453 deletions
Loading
Loading
@@ -84,7 +84,7 @@ gem 'net-ldap'
# API
gem 'grape', '~> 1.1.0'
gem 'grape-entity', '~> 0.7.1'
gem 'rack-cors', '~> 1.0.0', require: 'rack/cors'
gem 'rack-cors', '~> 1.0.6', require: 'rack/cors'
 
# GraphQL API
gem 'graphql', '~> 1.9.11'
Loading
Loading
Loading
Loading
@@ -28,8 +28,8 @@ export default function setupVueRepositoryList() {
},
});
 
router.afterEach(({ params: { pathMatch } }) => {
setTitle(pathMatch, ref, fullName);
router.afterEach(({ params: { path } }) => {
setTitle(path, ref, fullName);
});
 
const breadcrumbEl = document.getElementById('js-repo-breadcrumb');
Loading
Loading
@@ -48,9 +48,9 @@ export default function setupVueRepositoryList() {
newDirPath,
} = breadcrumbEl.dataset;
 
router.afterEach(({ params: { pathMatch = '/' } }) => {
updateFormAction('.js-upload-blob-form', uploadPath, pathMatch);
updateFormAction('.js-create-dir-form', newDirPath, pathMatch);
router.afterEach(({ params: { path = '/' } }) => {
updateFormAction('.js-upload-blob-form', uploadPath, path);
updateFormAction('.js-create-dir-form', newDirPath, path);
});
 
// eslint-disable-next-line no-new
Loading
Loading
@@ -61,7 +61,7 @@ export default function setupVueRepositoryList() {
render(h) {
return h(Breadcrumbs, {
props: {
currentPath: this.$route.params.pathMatch,
currentPath: this.$route.params.path,
canCollaborate: parseBoolean(canCollaborate),
canEditTree: parseBoolean(canEditTree),
newBranchPath,
Loading
Loading
@@ -84,7 +84,7 @@ export default function setupVueRepositoryList() {
render(h) {
return h(LastCommit, {
props: {
currentPath: this.$route.params.pathMatch,
currentPath: this.$route.params.path,
},
});
},
Loading
Loading
@@ -100,7 +100,7 @@ export default function setupVueRepositoryList() {
render(h) {
return h(TreeActionLink, {
props: {
path: historyLink + (this.$route.params.pathMatch || '/'),
path: `${historyLink}/${this.$route.params.path || ''}`,
text: __('History'),
},
});
Loading
Loading
@@ -117,7 +117,7 @@ export default function setupVueRepositoryList() {
render(h) {
return h(TreeActionLink, {
props: {
path: webIDEUrl(`/${projectPath}/edit/${ref}/-${this.$route.params.pathMatch || '/'}`),
path: webIDEUrl(`/${projectPath}/edit/${ref}/-/${this.$route.params.path || ''}`),
text: __('Web IDE'),
cssClass: 'qa-web-ide-button',
},
Loading
Loading
@@ -134,7 +134,7 @@ export default function setupVueRepositoryList() {
el: directoryDownloadLinks,
router,
render(h) {
const currentPath = this.$route.params.pathMatch || '/';
const currentPath = this.$route.params.path || '/';
 
if (currentPath !== '/') {
return h(DirectoryDownloadLinks, {
Loading
Loading
Loading
Loading
@@ -13,10 +13,10 @@ export default {
return { projectPath: '', loadingPath: null };
},
beforeRouteUpdate(to, from, next) {
this.preload(to.params.pathMatch, next);
this.preload(to.params.path, next);
},
methods: {
preload(path, next) {
preload(path = '/', next) {
this.loadingPath = path.replace(/^\//, '');
 
return this.$apollo
Loading
Loading
Loading
Loading
@@ -12,11 +12,11 @@ export default function createRouter(base, baseRef) {
base: joinPaths(gon.relative_url_root || '', base),
routes: [
{
path: `/-/tree/${escape(baseRef)}(/.*)?`,
path: `(/-)?/tree/${escape(baseRef)}/:path*`,
name: 'treePath',
component: TreePage,
props: route => ({
path: route.params.pathMatch && (route.params.pathMatch.replace(/^\//, '') || '/'),
path: route.params.path?.replace(/^\//, '') || '/',
}),
},
{
Loading
Loading
<script>
import * as d3 from 'd3';
import tooltip from '../directives/tooltip';
import Icon from './icon.vue';
import SvgGradient from './svg_gradient.vue';
import {
GRADIENT_COLORS,
GRADIENT_OPACITY,
INVERSE_GRADIENT_COLORS,
INVERSE_GRADIENT_OPACITY,
} from './bar_chart_constants';
/**
* Renders a bar chart that can be dragged(scrolled) when the number
* of elements to renders surpasses that of the available viewport space
* while keeping even padding and a width of 24px (customizable)
*
* It can render data with the following format:
* graphData: [{
* name: 'element' // x domain data
* value: 1 // y domain data
* }]
*
* Used in:
* - Contribution analytics - all of the rows describing pushes, merge requests and issues
*/
export default {
directives: {
tooltip,
},
components: {
Icon,
SvgGradient,
},
props: {
graphData: {
type: Array,
required: true,
},
barWidth: {
type: Number,
required: false,
default: 24,
},
yAxisLabel: {
type: String,
required: true,
},
},
data() {
return {
minX: -40,
minY: 0,
vbWidth: 0,
vbHeight: 0,
vpWidth: 0,
vpHeight: 200,
preserveAspectRatioType: 'xMidYMin meet',
containerMargin: {
leftRight: 30,
},
viewBoxMargin: {
topBottom: 100,
},
panX: 0,
xScale: {},
yScale: {},
zoom: {},
bars: {},
xGraphRange: 0,
isLoading: true,
paddingThreshold: 50,
showScrollIndicator: false,
showLeftScrollIndicator: false,
isGrabbed: false,
isPanAvailable: false,
gradientColors: GRADIENT_COLORS,
gradientOpacity: GRADIENT_OPACITY,
inverseGradientColors: INVERSE_GRADIENT_COLORS,
inverseGradientOpacity: INVERSE_GRADIENT_OPACITY,
maxTextWidth: 72,
rectYAxisLabelDims: {},
xAxisTextElements: {},
yAxisRectTransformPadding: 20,
yAxisTextTransformPadding: 10,
yAxisTextRotation: 90,
};
},
computed: {
svgViewBox() {
return `${this.minX} ${this.minY} ${this.vbWidth} ${this.vbHeight}`;
},
xAxisLocation() {
return `translate(${this.panX}, ${this.vbHeight})`;
},
barTranslationTransform() {
return `translate(${this.panX}, 0)`;
},
scrollIndicatorTransform() {
return `translate(${this.vbWidth - 80}, 0)`;
},
activateGrabCursor() {
return {
'svg-graph-container-with-grab': this.isPanAvailable,
'svg-graph-container-grabbed': this.isPanAvailable && this.isGrabbed,
};
},
yAxisLabelRectTransform() {
const rectWidth =
this.rectYAxisLabelDims.height != null ? this.rectYAxisLabelDims.height / 2 : 0;
const yCoord = this.vbHeight / 2 - rectWidth;
return `translate(${this.minX - this.yAxisRectTransformPadding}, ${yCoord})`;
},
yAxisLabelTextTransform() {
const rectWidth =
this.rectYAxisLabelDims.height != null ? this.rectYAxisLabelDims.height / 2 : 0;
const yCoord = this.vbHeight / 2 + rectWidth - 5;
return `translate(${this.minX + this.yAxisTextTransformPadding}, ${yCoord}) rotate(-${
this.yAxisTextRotation
})`;
},
},
mounted() {
if (!this.allValuesEmpty) {
this.draw();
}
},
methods: {
draw() {
// update viewport
this.vpWidth = this.$refs.svgContainer.clientWidth - this.containerMargin.leftRight;
// update viewbox
this.vbWidth = this.vpWidth;
this.vbHeight = this.vpHeight - this.viewBoxMargin.topBottom;
let padding = 0;
if (this.graphData.length * this.barWidth > this.vbWidth) {
this.xGraphRange = this.graphData.length * this.barWidth;
padding = this.calculatePadding(this.barWidth);
this.showScrollIndicator = true;
this.isPanAvailable = true;
} else {
this.xGraphRange = this.vbWidth - Math.abs(this.minX);
}
this.xScale = d3
.scaleBand()
.range([0, this.xGraphRange])
.round(true)
.paddingInner(padding);
this.yScale = d3.scaleLinear().rangeRound([this.vbHeight, 0]);
this.xScale.domain(this.graphData.map(d => d.name));
this.yScale.domain([0, d3.max(this.graphData.map(d => d.value))]);
// Zoom/Panning Function
this.zoom = d3
.zoom()
.translateExtent([[0, 0], [this.xGraphRange, this.vbHeight]])
.on('zoom', this.panGraph)
.on('end', this.removeGrabStyling);
const xAxis = d3.axisBottom().scale(this.xScale);
const yAxis = d3
.axisLeft()
.scale(this.yScale)
.ticks(4);
const renderedXAxis = d3
.select(this.$refs.baseSvg)
.select('.x-axis')
.call(xAxis);
this.xAxisTextElements = this.$refs.xAxis.querySelectorAll('text');
renderedXAxis.select('.domain').remove();
renderedXAxis
.selectAll('text')
.style('text-anchor', 'end')
.attr('dx', '-.3em')
.attr('dy', '-.95em')
.attr('class', 'tick-text')
.attr('transform', 'rotate(-90)');
renderedXAxis.selectAll('line').remove();
const { maxTextWidth } = this;
renderedXAxis.selectAll('text').each(function formatText() {
const axisText = d3.select(this);
let textLength = axisText.node().getComputedTextLength();
let textContent = axisText.text();
while (textLength > maxTextWidth && textContent.length > 0) {
textContent = textContent.slice(0, -1);
axisText.text(`${textContent}...`);
textLength = axisText.node().getComputedTextLength();
}
});
const width = this.vbWidth;
const renderedYAxis = d3
.select(this.$refs.baseSvg)
.select('.y-axis')
.call(yAxis);
renderedYAxis.selectAll('.tick').each(function createTickLines(d, i) {
if (i > 0) {
d3.select(this)
.select('line')
.attr('x2', width)
.attr('class', 'axis-tick');
}
});
// Add the panning capabilities
if (this.isPanAvailable) {
d3.select(this.$refs.baseSvg)
.call(this.zoom)
.on('wheel.zoom', null); // This disables the pan of the graph with the scroll of the mouse wheel
}
this.isLoading = false;
// Update the yAxisLabel coordinates
const labelDims = this.$refs.yAxisLabel.getBBox();
this.rectYAxisLabelDims = {
height: labelDims.width + 10,
};
},
panGraph() {
const allowedRightScroll = this.xGraphRange - this.vbWidth - this.paddingThreshold;
const graphMaxPan = Math.abs(d3.event.transform.x) < allowedRightScroll;
this.isGrabbed = true;
this.panX = d3.event.transform.x;
if (d3.event.transform.x === 0) {
this.showLeftScrollIndicator = false;
} else {
this.showLeftScrollIndicator = true;
this.showScrollIndicator = true;
}
if (!graphMaxPan) {
this.panX = -1 * (this.xGraphRange - this.vbWidth + this.paddingThreshold);
this.showScrollIndicator = false;
}
},
setTooltipTitle(data) {
return data !== null ? `${data.name}: ${data.value}` : '';
},
calculatePadding(desiredBarWidth) {
const widthWithMargin = this.vbWidth - Math.abs(this.minX);
const dividend = widthWithMargin - this.graphData.length * desiredBarWidth;
const divisor = widthWithMargin - desiredBarWidth;
return dividend / divisor;
},
removeGrabStyling() {
this.isGrabbed = false;
},
barHoveredIn(index) {
this.xAxisTextElements[index].classList.add('x-axis-text');
},
barHoveredOut(index) {
this.xAxisTextElements[index].classList.remove('x-axis-text');
},
},
};
</script>
<template>
<div ref="svgContainer" :class="activateGrabCursor" class="svg-graph-container">
<svg
ref="baseSvg"
class="svg-graph overflow-visible pt-5"
:width="vpWidth"
:height="vpHeight"
:viewBox="svgViewBox"
:preserveAspectRatio="preserveAspectRatioType"
>
<g ref="xAxis" :transform="xAxisLocation" class="x-axis" />
<g v-if="!isLoading">
<template v-for="(data, index) in graphData">
<rect
:key="index"
v-tooltip
:width="xScale.bandwidth()"
:x="xScale(data.name)"
:y="yScale(data.value)"
:height="vbHeight - yScale(data.value)"
:transform="barTranslationTransform"
:title="setTooltipTitle(data)"
class="bar-rect"
data-placement="top"
@mouseover="barHoveredIn(index)"
@mouseout="barHoveredOut(index)"
/>
</template>
</g>
<rect :height="vbHeight + 100" transform="translate(-100, -5)" width="100" fill="#fff" />
<g class="y-axis-label">
<line :x1="0" :x2="0" :y1="0" :y2="vbHeight" transform="translate(-35, 0)" stroke="black" />
<!-- Get text length and change the height of this rect accordingly -->
<rect
:height="rectYAxisLabelDims.height"
:transform="yAxisLabelRectTransform"
:width="30"
fill="#fff"
/>
<text ref="yAxisLabel" :transform="yAxisLabelTextTransform">{{ yAxisLabel }}</text>
</g>
<g class="y-axis" />
<g v-if="showScrollIndicator">
<rect
:height="vbHeight + 100"
:transform="`translate(${vpWidth - 60}, -5)`"
width="40"
fill="#fff"
/>
<icon
:x="vpWidth - 50"
:y="vbHeight / 2"
:width="14"
:height="14"
name="chevron-right"
class="animate-flicker"
/>
</g>
<!-- The line that shows up when the data elements surpass the available width -->
<g v-if="showScrollIndicator" :transform="scrollIndicatorTransform">
<rect :height="vbHeight" x="0" y="0" width="20" fill="url(#shadow-gradient)" />
</g>
<!-- Left scroll indicator -->
<g v-if="showLeftScrollIndicator" transform="translate(0, 0)">
<rect :height="vbHeight" x="0" y="0" width="20" fill="url(#left-shadow-gradient)" />
</g>
<svg-gradient
:colors="gradientColors"
:opacity="gradientOpacity"
identifier-name="shadow-gradient"
/>
<svg-gradient
:colors="inverseGradientColors"
:opacity="inverseGradientOpacity"
identifier-name="left-shadow-gradient"
/>
</svg>
</div>
</template>
export const GRADIENT_COLORS = ['#000', '#a7a7a7'];
export const GRADIENT_OPACITY = ['0', '0.4'];
export const INVERSE_GRADIENT_COLORS = ['#a7a7a7', '#000'];
export const INVERSE_GRADIENT_OPACITY = ['0.4', '0'];
Loading
Loading
@@ -5,13 +5,22 @@ class SentryIssue < ApplicationRecord
 
validates :issue, uniqueness: true, presence: true
validates :sentry_issue_identifier, presence: true
validate :ensure_sentry_issue_identifier_is_unique_per_project
 
after_create_commit :enqueue_sentry_sync_job
 
def self.for_project_and_identifier(project, identifier)
joins(:issue)
.where(issues: { project_id: project.id })
.find_by_sentry_issue_identifier(identifier)
.where(sentry_issue_identifier: identifier)
.order('issues.created_at').last
end
def ensure_sentry_issue_identifier_is_unique_per_project
if issue && self.class.for_project_and_identifier(issue.project, sentry_issue_identifier).present?
# Custom message because field is hidden
errors.add(:_, _('is already associated to a GitLab Issue. New issue will not be associated.'))
end
end
 
def enqueue_sentry_sync_job
Loading
Loading
---
title: Move contribution analytics chart to echarts
merge_request: 24272
author:
type: other
---
title: 'Geo: Add tables to prepare to replicate package files'
merge_request: 23447
author:
type: added
---
title: Fail upstream bridge on downstream pipeline creation failure.
merge_request: 24092
author:
type: fixed
---
title: Separate merge request entities into own class files
merge_request: 24373
author: Rajendra Kadam
type: added
Loading
Loading
@@ -55,8 +55,6 @@ module Gitlab
memo << ee_path.to_s
end
 
ee_paths << "ee/app/replicators"
# Eager load should load CE first
config.eager_load_paths.push(*ee_paths)
config.helpers_paths.push "#{config.root}/ee/app/helpers"
Loading
Loading
Loading
Loading
@@ -19,7 +19,6 @@ ActiveSupport::Inflector.inflections do |inflect|
group_view
job_artifact_registry
lfs_object_registry
package_file_registry
project_auto_devops
project_registry
project_statistics
Loading
Loading
# frozen_string_literal: true
class CreateGeoEvents < ActiveRecord::Migration[5.2]
DOWNTIME = false
def change
create_table :geo_events do |t|
t.string :replicable_name, limit: 255, null: false
t.string :event_name, limit: 255, null: false
t.jsonb :payload, default: {}, null: false
t.datetime_with_timezone :created_at, null: false
end
end
end
# frozen_string_literal: true
class AddGeoEventIdToGeoEventLog < ActiveRecord::Migration[5.2]
DOWNTIME = false
def change
add_column :geo_event_log, :geo_event_id, :integer
end
end
# frozen_string_literal: true
class AddGeoEventIdIndexToGeoEventLog < ActiveRecord::Migration[5.2]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_index :geo_event_log, :geo_event_id,
where: "(geo_event_id IS NOT NULL)",
using: :btree,
name: 'index_geo_event_log_on_geo_event_id'
end
def down
remove_concurrent_index :geo_event_log, :geo_event_id, name: 'index_geo_event_log_on_geo_event_id'
end
end
# frozen_string_literal: true
class AddGeoEventsForeignKey < ActiveRecord::Migration[5.2]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_foreign_key :geo_event_log, :geo_events,
column: :geo_event_id,
name: 'fk_geo_event_log_on_geo_event_id',
on_delete: :cascade
end
def down
remove_foreign_key_without_error :geo_event_log, column: :geo_event_id, name: 'fk_geo_event_log_on_geo_event_id'
end
end
Loading
Loading
@@ -1665,10 +1665,8 @@ ActiveRecord::Schema.define(version: 2020_02_04_131054) do
t.bigint "reset_checksum_event_id"
t.bigint "cache_invalidation_event_id"
t.bigint "container_repository_updated_event_id"
t.integer "geo_event_id"
t.index ["cache_invalidation_event_id"], name: "index_geo_event_log_on_cache_invalidation_event_id", where: "(cache_invalidation_event_id IS NOT NULL)"
t.index ["container_repository_updated_event_id"], name: "index_geo_event_log_on_container_repository_updated_event_id"
t.index ["geo_event_id"], name: "index_geo_event_log_on_geo_event_id", where: "(geo_event_id IS NOT NULL)"
t.index ["hashed_storage_attachments_event_id"], name: "index_geo_event_log_on_hashed_storage_attachments_event_id", where: "(hashed_storage_attachments_event_id IS NOT NULL)"
t.index ["hashed_storage_migrated_event_id"], name: "index_geo_event_log_on_hashed_storage_migrated_event_id", where: "(hashed_storage_migrated_event_id IS NOT NULL)"
t.index ["job_artifact_deleted_event_id"], name: "index_geo_event_log_on_job_artifact_deleted_event_id", where: "(job_artifact_deleted_event_id IS NOT NULL)"
Loading
Loading
@@ -1682,13 +1680,6 @@ ActiveRecord::Schema.define(version: 2020_02_04_131054) do
t.index ["upload_deleted_event_id"], name: "index_geo_event_log_on_upload_deleted_event_id", where: "(upload_deleted_event_id IS NOT NULL)"
end
 
create_table "geo_events", force: :cascade do |t|
t.string "replicable_name", limit: 255, null: false
t.string "event_name", limit: 255, null: false
t.jsonb "payload", default: {}, null: false
t.datetime_with_timezone "created_at", null: false
end
create_table "geo_hashed_storage_attachments_events", force: :cascade do |t|
t.integer "project_id", null: false
t.text "old_attachments_path", null: false
Loading
Loading
@@ -4649,7 +4640,6 @@ ActiveRecord::Schema.define(version: 2020_02_04_131054) do
add_foreign_key "geo_container_repository_updated_events", "container_repositories", name: "fk_212c89c706", on_delete: :cascade
add_foreign_key "geo_event_log", "geo_cache_invalidation_events", column: "cache_invalidation_event_id", name: "fk_42c3b54bed", on_delete: :cascade
add_foreign_key "geo_event_log", "geo_container_repository_updated_events", column: "container_repository_updated_event_id", name: "fk_6ada82d42a", on_delete: :cascade
add_foreign_key "geo_event_log", "geo_events", name: "fk_geo_event_log_on_geo_event_id", on_delete: :cascade
add_foreign_key "geo_event_log", "geo_hashed_storage_migrated_events", column: "hashed_storage_migrated_event_id", name: "fk_27548c6db3", on_delete: :cascade
add_foreign_key "geo_event_log", "geo_job_artifact_deleted_events", column: "job_artifact_deleted_event_id", name: "fk_176d3fbb5d", on_delete: :cascade
add_foreign_key "geo_event_log", "geo_lfs_object_deleted_events", column: "lfs_object_deleted_event_id", name: "fk_d5af95fcd9", on_delete: :cascade
Loading
Loading
Loading
Loading
@@ -17,7 +17,18 @@ All administrators at the time of creation of the project and group will be adde
as maintainers of the group and project, and as an admin, you'll be able to add new
members to the group in order to give them maintainer access to the project.
 
This project will be used for self-monitoring your GitLab instance.
This project will be used for self monitoring your GitLab instance.
## Activating or deactivating the self monitoring project
1. Navigate to **Admin Area > Settings > Metrics and profiling** and expand the **Self monitoring** section.
1. Toggle on or off the **Create Project** button to create or remove the "GitLab self monitoring" project.
1. Click **Save changes** for the changes to take effect.
If you activated the monitoring project, it should now be visible in **Projects > Your projects**.
CAUTION: **Warning:**
If you deactivate the self monitoring project, it will be permanently deleted.
 
## Connection to Prometheus
 
Loading
Loading
Loading
Loading
@@ -56,6 +56,7 @@ The following API resources are available in the project context:
| [Project milestones](milestones.md) | `/projects/:id/milestones` |
| [Project snippets](project_snippets.md) | `/projects/:id/snippets` |
| [Project templates](project_templates.md) | `/projects/:id/templates` |
| [Protected_environments](protected_environments.md) | `/projects/:id/protected_environments` |
| [Protected branches](protected_branches.md) | `/projects/:id/protected_branches` |
| [Protected tags](protected_tags.md) | `/projects/:id/protected_tags` |
| [Releases](releases/index.md) | `/projects/:id/releases` |
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment