Commit 572e10e8 authored by GitLab Bot's avatar GitLab Bot

Merge remote-tracking branch 'upstream/master' into ce-to-ee-2018-02-07

# Conflicts:
#	app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
#	app/assets/javascripts/filtered_search/filtered_search_manager.js
#	app/assets/javascripts/pages/groups/issues/index.js
#	app/assets/javascripts/pages/projects/issues/index/index.js
#	app/assets/javascripts/pages/search/init_filtered_search.js
#	app/controllers/projects/variables_controller.rb
#	app/models/ci/pipeline.rb
#	app/models/ci/runner.rb
#	app/models/group.rb
#	app/serializers/variable_entity.rb
#	app/services/lfs/lock_file_service.rb
#	app/services/lfs/unlock_file_service.rb
#	config/routes/project.rb
#	db/schema.rb
#	doc/administration/job_artifacts.md
#	doc/api/search.md
#	doc/ci/examples/browser_performance.md
#	doc/ci/variables/README.md
#	doc/topics/autodevops/index.md
#	doc/user/group/index.md
#	doc/user/permissions.md
#	doc/user/project/index.md
#	doc/user/project/issue_board.md
#	doc/user/project/issues/index.md
#	doc/user/project/merge_requests/index.md
#	lib/api/search.rb
#	locale/gitlab.pot
#	spec/javascripts/filtered_search/components/recent_searches_dropdown_content_spec.js
#	spec/javascripts/fixtures/projects.rb
#	spec/lib/gitlab/import_export/all_models.yml
#	spec/requests/api/search_spec.rb
#	spec/services/lfs/lock_file_service_spec.rb
#	spec/services/lfs/unlock_file_service_spec.rb

[ci skip]
parents bd50198b fb89f417
......@@ -32,6 +32,7 @@ export default class Clusters {
installIngressPath,
installRunnerPath,
installPrometheusPath,
managePrometheusPath,
clusterStatus,
clusterStatusReason,
helpPath,
......@@ -40,6 +41,7 @@ export default class Clusters {
this.store = new ClustersStore();
this.store.setHelpPaths(helpPath, ingressHelpPath);
this.store.setManagePrometheusPath(managePrometheusPath);
this.store.updateStatus(clusterStatus);
this.store.updateStatusReason(clusterStatusReason);
this.service = new ClustersService({
......@@ -95,6 +97,7 @@ export default class Clusters {
applications: this.state.applications,
helpPath: this.state.helpPath,
ingressHelpPath: this.state.ingressHelpPath,
managePrometheusPath: this.state.managePrometheusPath,
},
});
},
......
......@@ -32,6 +32,10 @@
type: String,
required: false,
},
manageLink: {
type: String,
required: false,
},
description: {
type: String,
required: true,
......@@ -89,6 +93,12 @@
return label;
},
showManageButton() {
return this.manageLink && this.status === APPLICATION_INSTALLED;
},
manageButtonLabel() {
return s__('ClusterIntegration|Manage');
},
hasError() {
return this.status === APPLICATION_ERROR ||
this.requestStatus === REQUEST_FAILURE;
......@@ -141,9 +151,21 @@
<div v-html="description"></div>
</div>
<div
class="table-section table-button-footer section-15 section-align-top"
class="table-section table-button-footer section-align-top"
:class="{ 'section-20': showManageButton, 'section-15': !showManageButton }"
role="gridcell"
>
<div
v-if="showManageButton"
class="btn-group table-action-buttons"
>
<a
class="btn"
:href="manageLink"
>
{{ manageButtonLabel }}
</a>
</div>
<div class="btn-group table-action-buttons">
<loading-button
class="js-cluster-application-install-button"
......
......@@ -23,13 +23,19 @@
required: false,
default: '',
},
managePrometheusPath: {
type: String,
required: false,
default: '',
},
},
computed: {
generalApplicationDescription() {
return sprintf(
_.escape(s__(`ClusterIntegration|Install applications on your Kubernetes cluster.
Read more about %{helpLink}`)),
{
_.escape(s__(
`ClusterIntegration|Install applications on your Kubernetes cluster.
Read more about %{helpLink}`,
)), {
helpLink: `<a href="${this.helpPath}">
${_.escape(s__('ClusterIntegration|installing applications'))}
</a>`,
......@@ -96,11 +102,12 @@
},
prometheusDescription() {
return sprintf(
_.escape(s__(`ClusterIntegration|Prometheus is an open-source monitoring system
with %{gitlabIntegrationLink} to monitor deployed applications.`)),
{
_.escape(s__(
`ClusterIntegration|Prometheus is an open-source monitoring system
with %{gitlabIntegrationLink} to monitor deployed applications.`,
)), {
gitlabIntegrationLink: `<a href="https://docs.gitlab.com/ce/user/project/integrations/prometheus.html"
target="_blank" rel="noopener noreferrer">
target="_blank" rel="noopener noreferrer">
${_.escape(s__('ClusterIntegration|GitLab Integration'))}</a>`,
},
false,
......@@ -149,6 +156,7 @@ target="_blank" rel="noopener noreferrer">
id="prometheus"
:title="applications.prometheus.title"
title-link="https://prometheus.io/docs/introduction/overview/"
:manage-link="managePrometheusPath"
:description="prometheusDescription"
:status="applications.prometheus.status"
:status-reason="applications.prometheus.statusReason"
......
......@@ -45,6 +45,10 @@ export default class ClusterStore {
this.state.ingressHelpPath = ingressHelpPath;
}
setManagePrometheusPath(managePrometheusPath) {
this.state.managePrometheusPath = managePrometheusPath;
}
updateStatus(status) {
this.state.status = status;
}
......
......@@ -73,11 +73,14 @@ class FilteredSearchDropdownManager {
gl: 'DropdownEmoji',
element: this.container.querySelector('#js-dropdown-my-reaction'),
},
<<<<<<< HEAD
weight: {
reference: null,
gl: 'DropdownNonUser',
element: this.container.querySelector('#js-dropdown-weight'),
},
=======
>>>>>>> upstream/master
};
supportedTokens.forEach((type) => {
......
......@@ -30,9 +30,12 @@ class FilteredSearchManager {
issues: 'issue-recent-searches',
merge_requests: 'merge-request-recent-searches',
};
<<<<<<< HEAD
// EE specific setup
this.initEE();
=======
>>>>>>> upstream/master
this.recentSearchesStore = new RecentSearchesStore({
isLocalStorageAvailable: RecentSearchesService.isAvailable(),
......
import axios from './axios_utils';
import { getLocationHash } from './url_utility';
import { convertToCamelCase } from './text_utility';
export const getPagePath = (index = 0) => $('body').attr('data-page').split(':')[index];
......@@ -395,6 +396,26 @@ export const spriteIcon = (icon, className = '') => {
return `<svg ${classAttribute}><use xlink:href="${gon.sprite_icons}#${icon}" /></svg>`;
};
/**
* This method takes in object with snake_case property names
* and returns new object with camelCase property names
*
* Reasoning for this method is to ensure consistent property
* naming conventions across JS code.
*/
export const convertObjectPropsToCamelCase = (obj = {}) => {
if (obj === null) {
return {};
}
return Object.keys(obj).reduce((acc, prop) => {
const result = acc;
result[convertToCamelCase(prop)] = obj[prop];
return acc;
}, {});
};
export const imagePath = imgUrl => `${gon.asset_host || ''}${gon.relative_url_root || ''}/assets/${imgUrl}`;
window.gl = window.gl || {};
......
......@@ -9,6 +9,20 @@ import {
window.timeago = timeago;
window.dateFormat = dateFormat;
/**
* Returns i18n month names array.
* If `abbreviated` is provided, returns abbreviated
* name.
*
* @param {Boolean} abbreviated
*/
const getMonthNames = (abbreviated) => {
if (abbreviated) {
return [s__('Jan'), s__('Feb'), s__('Mar'), s__('Apr'), s__('May'), s__('Jun'), s__('Jul'), s__('Aug'), s__('Sep'), s__('Oct'), s__('Nov'), s__('Dec')];
}
return [s__('January'), s__('February'), s__('March'), s__('April'), s__('May'), s__('June'), s__('July'), s__('August'), s__('September'), s__('October'), s__('November'), s__('December')];
};
/**
* Given a date object returns the day of the week in English
* @param {date} date
......@@ -157,7 +171,7 @@ export function timeIntervalInWords(intervalInSeconds) {
return text;
}
export function dateInWords(date, abbreviated = false) {
export function dateInWords(date, abbreviated = false, hideYear = false) {
if (!date) return date;
const month = date.getMonth();
......@@ -168,9 +182,115 @@ export function dateInWords(date, abbreviated = false) {
const monthName = abbreviated ? monthNamesAbbr[month] : monthNames[month];
if (hideYear) {
return `${monthName} ${date.getDate()}`;
}
return `${monthName} ${date.getDate()}, ${year}`;
}
/**
* Returns month name based on provided date.
*
* @param {Date} date
* @param {Boolean} abbreviated
*/
export const monthInWords = (date, abbreviated = false) => {
if (!date) {
return '';
}
return getMonthNames(abbreviated)[date.getMonth()];
};
/**
* Returns number of days in a month for provided date.
* courtesy: https://stacko(verflow.com/a/1185804/414749
*
* @param {Date} date
*/
export const totalDaysInMonth = (date) => {
if (!date) {
return 0;
}
return new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate();
};
/**
* Returns list of Dates referring to Sundays of the month
* based on provided date
*
* @param {Date} date
*/
export const getSundays = (date) => {
if (!date) {
return [];
}
const daysToSunday = ['Saturday', 'Friday', 'Thursday', 'Wednesday', 'Tuesday', 'Monday', 'Sunday'];
const month = date.getMonth();
const year = date.getFullYear();
const sundays = [];
const dateOfMonth = new Date(year, month, 1);
while (dateOfMonth.getMonth() === month) {
const dayName = getDayName(dateOfMonth);
if (dayName === 'Sunday') {
sundays.push(new Date(dateOfMonth.getTime()));
}
const daysUntilNextSunday = daysToSunday.indexOf(dayName) + 1;
dateOfMonth.setDate(dateOfMonth.getDate() + daysUntilNextSunday);
}
return sundays;
};
/**
* Returns list of Dates representing a timeframe of Months from month of provided date (inclusive)
* up to provided length
*
* For eg;
* If current month is January 2018 and `length` provided is `6`
* Then this method will return list of Date objects as follows;
*
* [ October 2017, November 2017, December 2017, January 2018, February 2018, March 2018 ]
*
* If current month is March 2018 and `length` provided is `3`
* Then this method will return list of Date objects as follows;
*
* [ February 2018, March 2018, April 2018 ]
*
* @param {Number} length
* @param {Date} date
*/
export const getTimeframeWindow = (length, date) => {
if (!length) {
return [];
}
const currentDate = date instanceof Date ? date : new Date();
const currentMonthIndex = Math.floor(length / 2);
const timeframe = [];
// Move date object backward to the first month of timeframe
currentDate.setDate(1);
currentDate.setMonth(currentDate.getMonth() - currentMonthIndex);
// Iterate and update date for the size of length
// and push date reference to timeframe list
for (let i = 0; i < length; i += 1) {
timeframe.push(new Date(currentDate.getTime()));
currentDate.setMonth(currentDate.getMonth() + 1);
}
// Change date of last timeframe item to last date of the month
timeframe[length - 1].setDate(totalDaysInMonth(timeframe[length - 1]));
return timeframe;
};
window.gl = window.gl || {};
window.gl.utils = {
...(window.gl.utils || {}),
......
......@@ -73,3 +73,10 @@ export function capitalizeFirstCharacter(text) {
* @returns {String}
*/
export const stripHtml = (string, replace = '') => string.replace(/<[^>]*>/g, replace);
/**
* Converts snake_case string to camelCase
*
* @param {*} string
*/
export const convertToCamelCase = string => string.replace(/(_\w)/g, s => s[1].toUpperCase());
......@@ -27,6 +27,7 @@
hasMetrics: convertPermissionToBoolean(metricsData.hasMetrics),
documentationPath: metricsData.documentationPath,
settingsPath: metricsData.settingsPath,
clustersPath: metricsData.clustersPath,
tagsPath: metricsData.tagsPath,
projectPath: metricsData.projectPath,
metricsEndpoint: metricsData.additionalMetrics,
......@@ -132,6 +133,7 @@
:selected-state="state"
:documentation-path="documentationPath"
:settings-path="settingsPath"
:clusters-path="clustersPath"
:empty-getting-started-svg-path="emptyGettingStartedSvgPath"
:empty-loading-svg-path="emptyLoadingSvgPath"
:empty-unable-to-connect-svg-path="emptyUnableToConnectSvgPath"
......
......@@ -10,6 +10,11 @@
required: false,
default: '',
},
clustersPath: {
type: String,
required: false,
default: '',
},
selectedState: {
type: String,
required: true,
......@@ -35,7 +40,10 @@
title: 'Get started with performance monitoring',
description: `Stay updated about the performance and health
of your environment by configuring Prometheus to monitor your deployments.`,
buttonText: 'Configure Prometheus',
buttonText: 'Install Prometheus on clusters',
buttonPath: this.clustersPath,
secondaryButtonText: 'Configure existing Prometheus',
secondaryButtonPath: this.settingsPath,
},
loading: {
svgUrl: this.emptyLoadingSvgPath,
......@@ -43,6 +51,7 @@
description: `Creating graphs uses the data from the Prometheus server.
If this takes a long time, ensure that data is available.`,
buttonText: 'View documentation',
buttonPath: this.documentationPath,
},
noData: {
svgUrl: this.emptyUnableToConnectSvgPath,
......@@ -50,12 +59,14 @@
description: `You are connected to the Prometheus server, but there is currently
no data to display.`,
buttonText: 'Configure Prometheus',
buttonPath: this.settingsPath,
},
unableToConnect: {
svgUrl: this.emptyUnableToConnectSvgPath,
title: 'Unable to connect to Prometheus server',
description: 'Ensure connectivity is available from the GitLab server to the ',
buttonText: 'View documentation',
buttonPath: this.documentationPath,
},
},
};
......@@ -65,13 +76,6 @@
return this.states[this.selectedState];
},
buttonPath() {
if (this.selectedState === 'gettingStarted') {
return this.settingsPath;
}
return this.documentationPath;
},
showButtonDescription() {
if (this.selectedState === 'unableToConnect') return true;
return false;
......@@ -99,11 +103,21 @@
</p>
<div class="state-button">
<a
v-if="currentState.buttonPath"
class="btn btn-success"
:href="buttonPath"
:href="currentState.buttonPath"
>
{{ currentState.buttonText }}
</a>
</div>
<div class="state-button">
<a
v-if="currentState.secondaryButtonPath"
class="btn"
:href="currentState.secondaryButtonPath"
>
{{ currentState.secondaryButtonText }}
</a>
</div>
</div>
</template>
......@@ -6,7 +6,10 @@ import FilteredSearchTokenKeysIssues from 'ee/filtered_search/filtered_search_to
export default () => {
initFilteredSearch({
page: FILTERED_SEARCH.ISSUES,
<<<<<<< HEAD
filteredSearchTokenKeys: FilteredSearchTokenKeysIssues,
=======
>>>>>>> upstream/master
});
projectSelect();
};
......@@ -11,7 +11,10 @@ import FilteredSearchTokenKeysIssues from 'ee/filtered_search/filtered_search_to
document.addEventListener('DOMContentLoaded', () => {
initFilteredSearch({
page: FILTERED_SEARCH.ISSUES,
<<<<<<< HEAD
filteredSearchTokenKeys: FilteredSearchTokenKeysIssues,
=======
>>>>>>> upstream/master
});
new IssuableIndex(ISSUABLE_INDEX.ISSUE);
......
<<<<<<< HEAD
export default ({ page, filteredSearchTokenKeys, stateFiltersSelector }) => {
const filteredSearchEnabled = gl.FilteredSearchManager && document.querySelector('.filtered-search');
if (filteredSearchEnabled) {
......@@ -6,6 +7,12 @@ export default ({ page, filteredSearchTokenKeys, stateFiltersSelector }) => {
filteredSearchTokenKeys,
stateFiltersSelector,
});
=======
export default ({ page }) => {
const filteredSearchEnabled = gl.FilteredSearchManager && document.querySelector('.filtered-search');
if (filteredSearchEnabled) {
const filteredSearchManager = new gl.FilteredSearchManager({ page });
>>>>>>> upstream/master
filteredSearchManager.setup();
}
};
/* eslint-disable no-return-assign, one-var, no-var, no-underscore-dangle, one-var-declaration-per-line, no-unused-vars, no-cond-assign, consistent-return, object-shorthand, prefer-arrow-callback, func-names, space-before-function-paren, prefer-template, quotes, class-methods-use-this, no-sequences, wrap-iife, no-lonely-if, no-else-return, no-param-reassign, vars-on-top, max-len */
import axios from './lib/utils/axios_utils';
import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from './lib/utils/common_utils';
/**
......@@ -146,23 +147,25 @@ export default class SearchAutocomplete {
this.loadingSuggestions = true;
return $.get(this.autocompletePath, {
project_id: this.projectId,
project_ref: this.projectRef,
term: term,
}, (response) => {
var firstCategory, i, lastCategory, len, suggestion;
return axios.get(this.autocompletePath, {
params: {
project_id: this.projectId,
project_ref: this.projectRef,
term: term,
},
}).then((response) => {
// Hide dropdown menu if no suggestions returns
if (!response.length) {
if (!response.data.length) {
this.disableAutocomplete();
return;
}
const data = [];
// List results
firstCategory = true;
for (i = 0, len = response.length; i < len; i += 1) {
suggestion = response[i];
let firstCategory = true;
let lastCategory;
for (let i = 0, len = response.data.length; i < len; i += 1) {
const suggestion = response.data[i];
// Add group header before list each group
if (lastCategory !== suggestion.category) {
if (!firstCategory) {
......@@ -177,7 +180,7 @@ export default class SearchAutocomplete {
lastCategory = suggestion.category;
}
data.push({
id: (suggestion.category.toLowerCase()) + "-" + suggestion.id,
id: `${suggestion.category.toLowerCase()}-${suggestion.id}`,
category: suggestion.category,
text: suggestion.label,
url: suggestion.url,
......@@ -187,13 +190,17 @@ export default class SearchAutocomplete {
if (data.length) {
data.push('separator');
data.push({
text: "Result name contains \"" + term + "\"",
url: "/search?search=" + term + "&project_id=" + (this.projectInputEl.val()) + "&group_id=" + (this.groupInputEl.val()),
text: `Result name contains "${term}"`,
url: `/search?search=${term}&project_id=${this.projectInputEl.val()}&group_id=${this.groupInputEl.val()}`,
});
}
return callback(data);
})
.always(() => { this.loadingSuggestions = false; });
callback(data);
this.loadingSuggestions = false;
}).catch(() => {
this.loadingSuggestions = false;
});
}
getCategoryContents() {
......
......@@ -215,6 +215,7 @@ $tooltip-font-size: 12px;
* Padding
*/
$gl-padding: 16px;
$gl-padding-8: 8px;
$gl-col-padding: 15px;
$gl-btn-padding: 10px;
$gl-input-padding: 10px;
......
......@@ -365,7 +365,7 @@
}
.prometheus-state {
max-width: 430px;
max-width: 460px;
margin: 10px auto;
text-align: center;
......@@ -373,6 +373,10 @@
max-width: 80vw;
margin: 0 auto;
}
.state-button {
padding: $gl-padding / 2;
}
}
.environments-actions {
......
......@@ -135,6 +135,17 @@
padding-top: 0;
}
.integration-settings-form {
.well {
padding: $gl-padding / 2;
box-shadow: none;
}
.svg-container {
max-width: 150px;
}
}
.token-token-container {
#impersonation-token-token {
width: 80%;
......
......@@ -16,10 +16,7 @@ module Ci
@builds = @config_processor.builds
@jobs = @config_processor.jobs
end
rescue
@error = 'Undefined error'
@status = false
ensure
render :show
end
end
......
......@@ -33,6 +33,7 @@ module ServiceParams
:issues_events,
:issues_url,
:jira_issue_transition_id,
:manual_configuration,
:merge_requests_events,
:mock_service_url,
:namespace,
......
......@@ -53,10 +53,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
set_pipeline_variables
# n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37432
Gitlab::GitalyClient.allow_n_plus_1_calls do
render
end
render
end
format.json do
......
class Projects::VariablesController < Projects::ApplicationController
<<<<<<< HEAD
prepend ::EE::Projects::VariablesController
=======
>>>>>>> upstream/master
before_action :authorize_admin_build!
def show
......
......@@ -28,7 +28,7 @@ class SnippetsFinder < UnionFinder
segments << items.public_to_user(current_user)
segments << authorized_to_user(items) if current_user
find_union(segments, Snippet)
find_union(segments, Snippet.includes(:author))
end
def authorized_to_user(items)
......
module GraphHelper
def get_refs(repo, commit)
refs = ""
# Commit::ref_names already strips the refs/XXX from important refs (e.g. refs/heads/XXX)
# so anything leftover is internally used by GitLab
commit_refs = commit.ref_names(repo).reject { |name| name.starts_with?('refs/') }
refs << commit_refs.join(' ')
def refs(repo, commit)
refs = commit.ref_names(repo).join(' ')
# append note count
notes_count = @graph.notes[commit.id]
......
......@@ -406,8 +406,13 @@ module Ci
return @config_processor if defined?(@config_processor)
@config_processor ||= begin
<<<<<<< HEAD
initialize_yaml_processor
rescue Gitlab::Ci::YamlProcessor::ValidationError, Psych::SyntaxError => e
=======
Gitlab::Ci::YamlProcessor.new(ci_yaml_file)
rescue Gitlab::Ci::YamlProcessor::ValidationError => e
>>>>>>> upstream/master
self.yaml_errors = e.message
nil
rescue
......
......@@ -3,7 +3,10 @@ module Ci
extend Gitlab::Ci::Model
include Gitlab::SQL::Pattern
include RedisCacheable
<<<<<<< HEAD
prepend EE::Ci::Runner
=======
>>>>>>> upstream/master
RUNNER_QUEUE_EXPIRY_TIME = 60.minutes
ONLINE_CONTACT_TIMEOUT = 1.hour
......
......@@ -10,10 +10,26 @@ module Clusters
default_value_for :version, VERSION
state_machine :status do
after_transition any => [:installed] do |application|
application.cluster.projects.each do |project|
project.find_or_initialize_service('prometheus').update(active: true)
end
end
end
def chart
'stable/prometheus'
end
def service_name
'prometheus-prometheus-server'
end
def service_port
80
end
def chart_values_file
"#{Rails.root}/vendor/#{name}/values.yaml"
end
......@@ -21,6 +37,22 @@ module Clusters
def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new(name, chart: chart, chart_values_file: chart_values_file)
end
def proxy_client
return unless kube_client
proxy_url = kube_client.proxy_url('service', service_name, service_port, Gitlab::Kubernetes::Helm::NAMESPACE)
# ensures headers containing auth data are appended to original k8s client options
options = kube_client.rest_client.options.merge(headers: kube_client.headers)
RestClient::Resource.new(proxy_url, options)
end
private
def kube_client
cluster&.kubeclient
end
end
end
end
......@@ -51,6 +51,9 @@ module Clusters
scope :enabled, -> { where(enabled: true) }
scope :disabled, -> { where(enabled: false) }
scope :for_environment, -> (env) { where(environment_scope: ['*', '', env.slug]) }
scope :for_all_environments, -> { where(environment_scope: ['*', '']) }
def status_name
if provider
provider.status_name
......
......@@ -36,10 +36,13 @@ class Group < Namespace
has_many :hooks, dependent: :destroy, class_name: 'GroupHook' # rubocop:disable Cop/ActiveRecordDependent
has_many :uploads, as: :model, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
<<<<<<< HEAD
# We cannot simply set `has_many :audit_events, as: :entity, dependent: :destroy`
# here since Group inherits from Namespace, the entity_type would be set to `Namespace`.
has_many :audit_events, -> { where(entity_type: Group) }, foreign_key: 'entity_id'
=======
>>>>>>> upstream/master
accepts_nested_attributes_for :variables, allow_destroy: true
validate :visibility_level_allowed_by_projects
......
......@@ -290,7 +290,7 @@ class MergeRequestDiff < ActiveRecord::Base
end
def keep_around_commits
[repository, merge_request.source_project.repository].each do |repo|
[repository, merge_request.source_project.repository].uniq.each do |repo|
repo.keep_around(start_commit_sha)
repo.keep_around(head_commit_sha)
repo.keep_around(base_commit_sha)
......
......@@ -7,11 +7,14 @@ class PrometheusService < MonitoringService
# Access to prometheus is directly through the API
prop_accessor :api_url
boolean_accessor :manual_configuration
with_options presence: true, if: :activated? do
with_options presence: true, if: :manual_configuration? do
validates :api_url, url: true
end
before_save :synchronize_service_state!
after_save :clear_reactive_cache!
def initialize_properties
......@@ -20,12 +23,20 @@ class PrometheusService < MonitoringService
end
end
def show_active_box?
false
end
def editable?
manual_configuration? || !prometheus_installed?
end
def title
'Prometheus'
end
def description
s_('PrometheusService|Prometheus monitoring')
s_('PrometheusService|Time-series monitoring service')
end
def self.to_param
......@@ -33,7 +44,15 @@ class PrometheusService < MonitoringService
end
def fields
return [] unless editable?
[
{
type: 'checkbox',
name: 'manual_configuration',
title: s_('PrometheusService|Active'),
required: true
},
{
type: 'text',
name: 'api_url',
......@@ -59,7 +78,7 @@ class PrometheusService < MonitoringService
end
def deployment_metrics(deployment)
metrics = with_reactive_cache(Gitlab::Prometheus::Queries::DeploymentQuery.name, deployment.id, &method(:rename_data_to_metrics))
metrics = with_reactive_cache(Gitlab::Prometheus::Queries::DeploymentQuery.name, deployment.environment.id, deployment.id, &method(:rename_data_to_metrics))
metrics&.merge(deployment_time: deployment.created_at.to_i) || {}
end
......@@ -68,7 +87,7 @@ class PrometheusService < MonitoringService
end
def additional_deployment_metrics(deployment)
with_reactive_cache(Gitlab::Prometheus::Queries::AdditionalMetricsDeploymentQuery.name, deployment.id, &:itself)
with_reactive_cache(Gitlab::Prometheus::Queries::AdditionalMetricsDeploymentQuery.name, deployment.environment.id, deployment.id, &:itself)
end
def matched_metrics
......@@ -79,6 +98,9 @@ class PrometheusService < MonitoringService
def calculate_reactive_cache(query_class_name, *args)
return unless active? && project && !project.pending_delete?
environment_id = args.first
client = client(environment_id)
data = Kernel.const_get(query_class_name).new(client).query(*args)
{
success: true,
......@@ -89,14 +111,55 @@ class PrometheusService < MonitoringService
{ success: false, result: err.message }
end
def client
@prometheus ||= Gitlab::PrometheusClient.new(api_url: api_url)
def client(environment_id = nil)
if manual_configuration?
Gitlab::PrometheusClient.new(RestClient::Resource.new(api_url))
else
cluster = cluster_with_prometheus(environment_id)
raise Gitlab::PrometheusError, "couldn't find cluster with Prometheus installed" unless cluster
rest_client = client_from_cluster(cluster)
raise Gitlab::PrometheusError, "couldn't create proxy Prometheus client" unless rest_client
Gitlab::PrometheusClient.new(rest_client)
end
end
def prometheus_installed?
return false if template?
return false unless project
project.clusters.enabled.any? { |cluster| cluster.application_prometheus&.installed? }
end
private
def cluster_with_prometheus(environment_id = nil)
clusters = if environment_id
::Environment.find_by(id: environment_id).try do |env|
# sort results by descending order based on environment_scope being longer
# thus more closely matching environment slug
project.clusters.enabled.for_environment(env).sort_by { |c| c.environment_scope&.length }.reverse!
end
else
project.clusters.enabled.for_all_environments
end
clusters&.detect { |cluster| cluster.application_prometheus&.installed? }
end
def client_from_cluster(cluster)
cluster.application_prometheus.proxy_client
end
def rename_data_to_metrics(metrics)
metrics[:metrics] = metrics.delete :data
metrics
end
def synchronize_service_state!
self.active = prometheus_installed? || manual_configuration?
true
end
end
......@@ -3,6 +3,7 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
include GitlabRoutingHelper
include MarkupHelper
include TreeHelper
include Gitlab::Utils::StrongMemoize
presents :merge_request
......@@ -43,7 +44,7 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
end
def revert_in_fork_path
if user_can_fork_project? && can_be_reverted?(current_user)
if user_can_fork_project? && cached_can_be_reverted?
continue_params = {
to: merge_request_path(merge_request),
notice: "#{edit_in_new_fork_notice} Try to cherry-pick this commit again.",
......@@ -157,7 +158,7 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
end
def can_revert_on_current_merge_request?
user_can_collaborate_with_project? && can_be_reverted?(current_user)
user_can_collaborate_with_project? && cached_can_be_reverted?
end
def can_cherry_pick_on_current_merge_request?
......@@ -170,6 +171,12 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
private
def cached_can_be_reverted?
strong_memoize(:can_be_reverted) do
can_be_reverted?(current_user)
end
end
def conflicts
@conflicts ||= MergeRequests::Conflicts::ListService.new(merge_request)
end
......
class VariableEntity < Grape::Entity
<<<<<<< HEAD
prepend ::EE::VariableEntity
=======
>>>>>>> upstream/master
expose :id
expose :key
expose :value
......
......@@ -7,6 +7,8 @@ module Ci
# stage.
#
class EnsureStageService < BaseService
EnsureStageError = Class.new(StandardError)
def execute(build)
@build = build
......@@ -22,8 +24,16 @@ module Ci
private
def ensure_stage
def ensure_stage(attempts: 2)
find_stage || create_stage
rescue ActiveRecord::RecordNotUnique
retry if (attempts -= 1) > 0
raise EnsureStageError, <<~EOS
We failed to find or create a unique pipeline stage after 2 retries.
This should never happen and is most likely the result of a bug in
the database load balancing code.
EOS
end
def find_stage
......
module Ci
class RetryBuildService < ::BaseService
CLONE_ACCESSORS = %i[pipeline project ref tag options commands name
allow_failure stage_id stage stage_idx trigger_request
allow_failure stage stage_id stage_idx trigger_request
yaml_variables when environment coverage_regex
description tag_list protected].freeze
......
module Lfs
class LockFileService < BaseService
<<<<<<< HEAD
prepend EE::Lfs::LockFileService
=======
>>>>>>> upstream/master
def execute
unless can?(current_user, :push_code, project)
raise Gitlab::GitAccess::UnauthorizedError, 'You have no permissions'
......
module Lfs
class UnlockFileService < BaseService
<<<<<<< HEAD
prepend EE::Lfs::UnlockFileService
=======
>>>>>>> upstream/master
def execute
unless can?(current_user, :push_code, project)
raise Gitlab::GitAccess::UnauthorizedError, 'You have no permissions'
......
......@@ -9,10 +9,7 @@ module MergeRequests
merge_request.source_branch = params[:source_branch]
merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch)
# n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37439
Gitlab::GitalyClient.allow_n_plus_1_calls do
create(merge_request)
end
create(merge_request)
end
def before_create(merge_request)
......
.layout-page{ class: page_with_sidebar_class }
- if defined?(nav) && nav
= render "layouts/nav/sidebar/#{nav}"
.content-wrapper
.content-wrapper{ class: "#{@content_wrapper_class}" }
= render 'shared/outdated_browser'
.mobile-overlay
.alert-wrapper
......
......@@ -14,7 +14,8 @@
cluster_status: @cluster.status_name,
cluster_status_reason: @cluster.status_reason,
help_path: help_page_path('user/project/clusters/index.md', anchor: 'installing-applications'),
ingress_help_path: help_page_path('user/project/clusters/index.md', anchor: 'getting-the-external-ip-address') } }
ingress_help_path: help_page_path('user/project/clusters/index.md', anchor: 'getting-the-external-ip-address'),
manage_prometheus_path: edit_project_service_path(@cluster.project, 'prometheus') } }
.js-cluster-application-notice
.flash-container
......
......@@ -13,6 +13,7 @@
= link_to @environment.name, environment_path(@environment)
#prometheus-graphs{ data: { "settings-path": edit_project_service_path(@project, 'prometheus'),
"clusters-path": project_clusters_path(@project),
"documentation-path": help_page_path('administration/monitoring/prometheus/index.md'),
"empty-getting-started-svg-path": image_path('illustrations/monitoring/getting_started.svg'),
"empty-loading-svg-path": image_path('illustrations/monitoring/loading.svg'),
......
......@@ -13,7 +13,7 @@
},
time: c.time,
space: c.spaces.first,
refs: get_refs(@graph.repo, c),
refs: refs(@graph.repo, c),
id: c.sha,
date: c.date,
message: c.message,
......
......@@ -9,7 +9,7 @@
%p= @service.description
.col-lg-9
= form_for(@service, as: :service, url: project_service_path(@project, @service.to_param), method: :put, html: { class: 'gl-show-field-errors form-horizontal js-integration-settings-form', data: { 'can-test' => @service.can_test?, 'test-url' => test_project_service_path(@project, @service) } }) do |form|
= form_for(@service, as: :service, url: project_service_path(@project, @service.to_param), method: :put, html: { class: 'gl-show-field-errors form-horizontal integration-settings-form js-integration-settings-form', data: { 'can-test' => @service.can_test?, 'test-url' => test_project_service_path(@project, @service) } }) do |form|
= render 'shared/service_settings', form: form, subject: @service
- if @service.editable?
.footer-block.row-content-block
......
%h4
= s_('PrometheusService|Auto configuration')
- if @service.manual_configuration?
.well
= s_('PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below')
- else
.container-fluid
.row
- if @service.prometheus_installed?
.col-sm-2
.svg-container
= image_tag 'illustrations/monitoring/getting_started.svg'
.col-sm-10
%p.text-success.prepend-top-default
= s_('PrometheusService|Prometheus is being automatically managed on your clusters')
= link_to s_('PrometheusService|Manage clusters'), project_clusters_path(@project), class: 'btn'
- else
.col-sm-2
= image_tag 'illustrations/monitoring/loading.svg'
.col-sm-10
%p.prepend-top-default
= s_('PrometheusService|Automatically deploy and configure Prometheus on your clusters to monitor your project’s environments')
= link_to s_('PrometheusService|Install Prometheus on clusters'), project_clusters_path(@project), class: 'btn btn-success'
%hr
%h4.append-bottom-default
= s_('PrometheusService|Manual configuration')
- unless @service.editable?
.well
= s_('PrometheusService|To enable manual configuration, uninstall Prometheus from your clusters')
- link_project = local_assigns.fetch(:link_project, false)
%li.snippet-row
= image_tag avatar_icon(snippet.author_email), class: "avatar s40 hidden-xs", alt: ''
= image_tag avatar_icon(snippet.author), class: "avatar s40 hidden-xs", alt: ''
.title
= link_to reliable_snippet_path(snippet) do
......
---
title: Fix N+1 query problem for snippets dashboard.
merge_request: 16944
author:
type: performance
---
title: 'Handle all Psych YAML parser exceptions (fixes #41209)'
merge_request:
author:
type: fixed
---
title: Remove duplicate calls of MergeRequest#can_be_reverted?
merge_request:
author:
type: performance
---
title: Implement multi server support and use kube proxy to connect to Prometheus
servers inside K8S cluster
merge_request: 16182
author:
type: added
......@@ -170,6 +170,7 @@ constraints(ProjectUrlConstrainer.new) do
end
end
<<<<<<< HEAD
## EE-specific
resources :path_locks, only: [:index, :destroy] do
collection do
......@@ -181,6 +182,8 @@ constraints(ProjectUrlConstrainer.new) do
get '/service_desk' => 'service_desk#show', as: :service_desk
put '/service_desk' => 'service_desk#update', as: :service_desk_refresh
=======
>>>>>>> upstream/master
resource :variables, only: [:show, :update]
resources :triggers, only: [:index, :create, :edit, :update, :destroy] do
......
class RemoveRedundantPipelineStages < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up(attempts: 100)
remove_redundant_pipeline_stages!
remove_outdated_index!
add_unique_index!
rescue ActiveRecord::RecordNotUnique
retry if (attempts -= 1) > 0
raise StandardError, <<~EOS
Failed to add an unique index to ci_stages, despite retrying the
migration 100 times.
See https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/16580.
EOS
end
def down
remove_concurrent_index :ci_stages, [:pipeline_id, :name], unique: true
add_concurrent_index :ci_stages, [:pipeline_id, :name]
end
private
def remove_outdated_index!
return unless index_exists?(:ci_stages, [:pipeline_id, :name])
remove_concurrent_index :ci_stages, [:pipeline_id, :name]
end
def add_unique_index!
add_concurrent_index :ci_stages, [:pipeline_id, :name], unique: true
end
def remove_redundant_pipeline_stages!
disable_statement_timeout
redundant_stages_ids = <<~SQL
SELECT id FROM ci_stages WHERE (pipeline_id, name) IN (
SELECT pipeline_id, name FROM ci_stages
GROUP BY pipeline_id, name HAVING COUNT(*) > 1
)
SQL
execute <<~SQL
UPDATE ci_builds SET stage_id = NULL WHERE stage_id IN (#{redundant_stages_ids})
SQL
if Gitlab::Database.postgresql?
execute <<~SQL
DELETE FROM ci_stages WHERE id IN (#{redundant_stages_ids})
SQL
else # We can't modify a table we are selecting from on MySQL
execute <<~SQL
DELETE a FROM ci_stages AS a, ci_stages AS b
WHERE a.pipeline_id = b.pipeline_id AND a.name = b.name
AND a.id <> b.id
SQL
end
end
end
......@@ -177,6 +177,7 @@ ActiveRecord::Schema.define(version: 20180206200543) do
t.integer "gitaly_timeout_default", default: 55, null: false
t.integer "gitaly_timeout_medium", default: 30, null: false
t.integer "gitaly_timeout_fast", default: 10, null: false
<<<<<<< HEAD
t.boolean "mirror_available", default: true, null: false
t.string "auto_devops_domain"
t.integer "default_project_creation", default: 2, null: false
......@@ -187,6 +188,10 @@ ActiveRecord::Schema.define(version: 20180206200543) do
t.integer "user_id", null: false
t.datetime "created_at"
t.datetime "updated_at"
=======
t.boolean "authorized_keys_enabled", default: true, null: false
t.string "auto_devops_domain"
>>>>>>> upstream/master
end
add_index "approvals", ["merge_request_id"], name: "index_approvals_on_merge_request_id", using: :btree
......@@ -543,7 +548,7 @@ ActiveRecord::Schema.define(version: 20180206200543) do
t.integer "lock_version"
end
add_index "ci_stages", ["pipeline_id", "name"], name: "index_ci_stages_on_pipeline_id_and_name", using: :btree
add_index "ci_stages", ["pipeline_id", "name"], name: "index_ci_stages_on_pipeline_id_and_name", unique: true, using: :btree
add_index "ci_stages", ["pipeline_id"], name: "index_ci_stages_on_pipeline_id", using: :btree
add_index "ci_stages", ["project_id"], name: "index_ci_stages_on_project_id", using: :btree
......@@ -1303,6 +1308,7 @@ ActiveRecord::Schema.define(version: 20180206200543) do
add_index "labels", ["title"], name: "index_labels_on_title", using: :btree
add_index "labels", ["type", "project_id"], name: "index_labels_on_type_and_project_id", using: :btree
<<<<<<< HEAD
create_table "ldap_group_links", force: :cascade do |t|
t.string "cn"
t.integer "group_access", null: false
......@@ -1313,6 +1319,8 @@ ActiveRecord::Schema.define(version: 20180206200543) do
t.string "filter"
end
=======
>>>>>>> upstream/master
create_table "lfs_file_locks", force: :cascade do |t|
t.integer "project_id", null: false
t.integer "user_id", null: false
......
......@@ -87,6 +87,7 @@ _The artifacts are stored by default in
### Using object storage
<<<<<<< HEAD
>**Notes:**
- [Introduced][ee-1762] in [GitLab Premium][eep] 9.4.
- Since version 9.5, artifacts are [browsable], when object storage is enabled.
......@@ -178,6 +179,12 @@ _The artifacts are stored by default in
artifacts will still be stored on the local disk. In the future
you will be given an option to define a default storage artifacts for all
new files.
=======
> Available in [GitLab Premium](https://about.gitlab.com/products/) and
[GitLab.com Silver](https://about.gitlab.com/gitlab-com/).
Use an [Object storage option][ee-os] like AWS S3 to store job artifacts.
>>>>>>> upstream/master
## Expiring artifacts
......
......@@ -19,8 +19,11 @@ GET /search
Search the expression within the specified scope. Currently these scopes are supported: projects, issues, merge_requests, milestones, snippet_titles, snippet_blobs.
<<<<<<< HEAD
If Elasticsearch is enabled additional scopes available are blobs, wiki_blobs and commits. Find more about [the feature](../integration/elasticsearch.md).
=======
>>>>>>> upstream/master
The response depends on the requested scope.
### Scope: projects
......@@ -283,6 +286,7 @@ Example response:
]
```
<<<<<<< HEAD
### Scope: wiki_blobs
This scope is available only if [Elasticsearch](../integration/elasticsearch.md) is enabled.
......@@ -363,6 +367,8 @@ Example response:
]
```
=======
>>>>>>> upstream/master
## Group Search API
......@@ -382,8 +388,11 @@ GET /groups/:id/-/search
Search the expression within the specified scope. Currently these scopes are supported: projects, issues, merge_requests, milestones.
<<<<<<< HEAD
If Elasticsearch is enabled additional scopes available are blobs, wiki_blobs and commits. Find more about [the feature](../integration/elasticsearch.md).
=======
>>>>>>> upstream/master
The response depends on the requested scope.
### Scope: projects
......@@ -584,6 +593,7 @@ Example response:
]
```
<<<<<<< HEAD
### Scope: wiki_blobs
This scope is available only if [Elasticsearch](../integration/elasticsearch.md) is enabled.
......@@ -665,6 +675,8 @@ Example response:
```
=======
>>>>>>> upstream/master
## Project Search API
Search within the specified project.
......
......@@ -26,7 +26,11 @@ Once you set up the Runner, add a new job to `.gitlab-ci.yml`, called `performan
This will create a `performance` job in your CI/CD pipeline and will run Sitespeed.io against the webpage you define. The GitLab plugin for Sitespeed.io downloaded in order to export the results to JSON. For further customization options of Sitespeed.io, including the ability to provide a list of URLs to test, please consult their [documentation](https://www.sitespeed.io/documentation/sitespeed.io/configuration/).
For [GitLab Premium](https://about.gitlab.com/products/) users, a performance score can be automatically
<<<<<<< HEAD
extracted and shown right in the merge request widget. [Learn more on performance diffs in merge requests](../../user/project/merge_requests/browser_performance_testing.md).
=======
extracted and shown right in the merge request widget. Learn more about [Browser Performance Testing](https://docs.gitlab.com/ee/user/project/merge_requests/browser_performance_testing.html).
>>>>>>> upstream/master
## Performance testing on Review Apps
......
......@@ -467,7 +467,11 @@ export CI_REGISTRY_PASSWORD="longalfanumstring"
[ee-2112]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2112
[ce-13784]: https://gitlab.com/gitlab-org/gitlab-ce/issues/13784 "Simple protection of CI secret variables"
<<<<<<< HEAD
[premium]: https://about.gitlab.com/products/ "Available only in GitLab Premium"
=======
[eep]: https://about.gitlab.com/products/ "Available only in GitLab Premium"
>>>>>>> upstream/master
[envs]: ../environments.md
[protected branches]: ../../user/project/protected_branches.md
[protected tags]: ../../user/project/protected_tags.md
......
......@@ -213,7 +213,11 @@ report is created, it's uploaded as an artifact which you can later download and
check out.
In GitLab Ultimate, any security warnings are also
<<<<<<< HEAD
[shown in the merge request widget](../../user/project/merge_requests/sast.md).
=======
[shown in the merge request widget](https://docs.gitlab.com/ee/user/project/merge_requests/sast.html).
>>>>>>> upstream/master
### Auto SAST for Docker images
......@@ -226,7 +230,11 @@ created, it's uploaded as an artifact which you can later download and
check out.
In GitLab Ultimate, any security warnings are also
<<<<<<< HEAD
[shown in the merge request widget](../../user/project/merge_requests/sast_docker.md).
=======
[shown in the merge request widget](https://docs.gitlab.com/ee/user/project/merge_requests/sast_docker.html).
>>>>>>> upstream/master
### Auto Review Apps
......@@ -265,7 +273,11 @@ issues. Once the report is created, it's uploaded as an artifact which you can
later download and check out.
In GitLab Ultimate, any security warnings are also
<<<<<<< HEAD
[shown in the merge request widget](../../user/project/merge_requests/dast.md).
=======
[shown in the merge request widget](https://docs.gitlab.com/ee/user/project/merge_requests/dast.html).
>>>>>>> upstream/master
### Auto Browser Performance Testing
......@@ -279,8 +291,12 @@ Auto Browser Performance Testing utilizes the [Sitespeed.io container](https://h
/direction
```
<<<<<<< HEAD
In GitLab Premium, performance differences between the source
and target branches are [shown in the merge request widget](../../user/project/merge_requests/browser_performance_testing.md).
=======
In GitLab Premium, performance differences between the source and target branches are [shown in the merge request widget](https://docs.gitlab.com/ee/user/project/merge_requests/browser_performance_testing.html).
>>>>>>> upstream/master
### Auto Deploy
......
......@@ -102,6 +102,11 @@ running:
kubectl get svc ruby-app-nginx-ingress-controller -o jsonpath='{.status.loadBalancer.ingress[0].ip}'
```
NOTE: **Note:**
If your ingress controller has been installed in a different way, you can find
how to get the external IP address in the
[Cluster documentation](../../user/project/clusters/index.md#getting-the-external-ip-address).
Use this IP address to configure your DNS. This part heavily depends on your
preferences and domain provider. But in case you are not sure, just create an
A record with a wildcard host like `*.<your-domain>`.
......
......@@ -268,6 +268,7 @@ To enable this feature, navigate to the group settings page. Select
![Checkbox for share with group lock](img/share_with_group_lock.png)
#### Member Lock
<<<<<<< HEAD
> Available in [GitLab Starter](https://about.gitlab.com/products/) and
[GitLab.com Bronze](https://about.gitlab.com/gitlab-com/).
......@@ -291,12 +292,23 @@ and **Save group**.
This will disable the option for all users who previously had permissions to
operate project memberships so no new users can be added. Furthermore, any
request to add new user to project through API will not be possible.
=======
> Available in [GitLab Starter](https://about.gitlab.com/products/) and
[GitLab.com Bronze](https://about.gitlab.com/gitlab-com/).
With **Member Lock** it is possible to lock membership in project to the
level of members in group.
Learn more about [Member Lock](https://docs.gitlab.com/ee/user/group/index.html#member-lock).
>>>>>>> upstream/master
### Advanced settings
- **Projects**: view all projects within that group, add members to each project,
access each project's settings, and remove any project from the same screen.
- **Webhooks**: configure [webhooks](../project/integrations/webhooks.md)
<<<<<<< HEAD
and [push rules](../../push_rules/push_rules.md) to your group
(Push Rules is available in [GitLab Starter][ee].)
- **Audit Events**: view [Audit Events](../../administration/audit_events.md)
......@@ -314,3 +326,9 @@ you have an overview of the contributions (pushes, merge requests,
and issues) performed my your group members.
[ee]: https://about.gitlab.com/products/
=======
and [push rules](https://docs.gitlab.com/ee/push_rules/push_rules.html#push-rules) to your group (Push Rules is available in [GitLab Starter](https://about.gitlab.com/products/).)
- **Audit Events**: view [Audit Events](https://docs.gitlab.com/ee/administration/audit_events.html#audit-events)
for the group (GitLab admins only, available in [GitLab Starter][ee]).
- **Pipelines quota**: keep track of the [pipeline quota](../admin_area/settings/continuous_integration.md) for the group
>>>>>>> upstream/master
......@@ -123,7 +123,11 @@ to learn more.
### File Locking permissions
<<<<<<< HEAD
> Available in [GitLab Premium](https://about.gitlab.com/products/) and [GitLab.com Silver](https://about.gitlab.com/gitlab-com/).
=======
> Available in [GitLab Premium](https://about.gitlab.com/products/).
>>>>>>> upstream/master
The user that locks a file or directory is the only one that can edit and push their changes back to the repository where the locked objects are located.
......@@ -283,6 +287,11 @@ Read through the documentation on [LDAP users permissions](../administration/aut
## Auditor users permissions
<<<<<<< HEAD
=======
## Auditor users permissions
>>>>>>> upstream/master
> Available in [GitLab Premium](https://about.gitlab.com/products/).
An Auditor user should be able to access all projects and groups of a GitLab instance
......
......@@ -134,6 +134,41 @@ added directly to your configured cluster. Those applications are needed for
| [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) | 10.2+ | Ingress can provide load balancing, SSL termination, and name-based virtual hosting. It acts as a web proxy for your applications and is useful if you want to use [Auto DevOps](../../../topics/autodevops/index.md) or deploy your own web apps. |
| [Prometheus](https://prometheus.io/docs/introduction/overview/) | 10.4+ | Prometheus is an open-source monitoring and alerting system useful to supervise your deployed applications |
## Getting the external IP address
NOTE: **Note:**
You need a load balancer installed in your cluster in order to obtain the
external IP address with the following procedure. It can be deployed using the
**Ingress** application described in the previous section.
In order to publish your web application, you first need to find the external IP
address associated to your load balancer.
If the cluster is on GKE, click on the **Google Kubernetes Engine** link in the
**Advanced settings**, or go directly to the
[Google Kubernetes Engine dashboard](https://console.cloud.google.com/kubernetes/)
and select the proper project and cluster. Then click on **Connect** and execute
the `gcloud` command in a local terminal or using the **Cloud Shell**.
If the cluster is not on GKE, follow the specific instructions for your
Kubernetes provider to configure `kubectl` with the right credentials.
If you installed the Ingress using the **Applications** section, run the following command:
```bash
kubectl get svc --namespace=gitlab-managed-apps ingress-nginx-ingress-controller -o jsonpath='{.status.loadBalancer.ingress[0].ip} '
```
Otherwise, you can list the IP addresses of all load balancers:
```bash
kubectl get svc --all-namespaces -o jsonpath='{range.items[?(@.status.loadBalancer.ingress)]}{.status.loadBalancer.ingress[*].ip} '
```
The output is the external IP address of your cluster. This information can then
be used to set up DNS entries and forwarding rules that allow external access to
your deployed applications.
## Setting the environment scope
When adding more than one clusters, you need to differentiate them with an
......
......@@ -17,7 +17,11 @@ When you create a project in GitLab, you'll have access to a large number of
- [Issue tracker](issues/index.md): Discuss implementations with your team within issues
- [Issue Boards](issue_board.md): Organize and prioritize your workflow
<<<<<<< HEAD
- [Multiple Issue Boards](issue_board.md#multiple-issue-boards) (**Starter/Premium**): Allow your teams to create their own workflows (Issue Boards) for the same project
=======
- [Multiple Issue Boards](https://docs.gitlab.com/ee/user/project/issue_board.html#multiple-issue-boards) (**Starter/Premium**): Allow your teams to create their own workflows (Issue Boards) for the same project
>>>>>>> upstream/master
- [Repositories](repository/index.md): Host your code in a fully
integrated platform
- [Branches](repository/branches/index.md): use Git branching strategies to
......@@ -36,7 +40,11 @@ integrated platform
- [Multiple Issue Boards](issue_board.md#multiple-issue-boards) (**Starter/Premium**): Allow your teams to create their own workflows (Issue Boards) for the same project
- [Merge Requests](merge_requests/index.md): Apply your branching
strategy and get reviewed by your team
<<<<<<< HEAD
- [Merge Request Approvals](merge_requests/merge_request_approvals.md) (**Starter/Premium**): Ask for approval before
=======
- [Merge Request Approvals](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html) (**Starter/Premium**): Ask for approval before
>>>>>>> upstream/master
implementing a change
- [Fix merge conflicts from the UI](merge_requests/resolve_conflicts.md):
Your Git diff tool right from GitLab's UI
......
......@@ -33,8 +33,13 @@ You create issues, host code, perform reviews, build, test,
and deploy from one single platform. Issue Boards help you to visualize
and manage the entire process _in_ GitLab.
<<<<<<< HEAD
With [Multiple Issue Boards](#multiple-issue-boards), available
only in [GitLab Enterprise Edition](https://about.gitlab.com/products/),
=======
With [Multiple Issue Boards](https://docs.gitlab.com/ee/user/project/issue_board.html#multiple-issue-boards), available
only in [GitLab Ultimate](https://about.gitlab.com/products/),
>>>>>>> upstream/master
you go even further, as you can not only keep yourself and your project
organized from a broader perspective with one Issue Board per project,
but also allow your team members to organize their own workflow by creating
......
......@@ -34,7 +34,11 @@ your project public, open to collaboration.
### Streamline collaboration
<<<<<<< HEAD
With [Multiple Assignees for Issues](multiple_assignees_for_issues.md),
=======
With [Multiple Assignees for Issues](https://docs.gitlab.com/ee/user/project/issues/multiple_assignees_for_issues.html),
>>>>>>> upstream/master
available in [GitLab Starter](https://about.gitlab.com/products/)
you can streamline collaboration and allow shared responsibilities to be clearly displayed.
All assignees are shown across your workflows and receive notifications (as they
......@@ -141,6 +145,7 @@ Find GitLab Issue Boards by navigating to your **Project's Dashboard** > **Issue
Read through the documentation for [Issue Boards](../issue_board.md)
to find out more about this feature.
<<<<<<< HEAD
### Export Issues to CSV
> Available in [GitLab Starter](https://about.gitlab.com/products/) and
......@@ -160,6 +165,10 @@ and appear in a block below the issue description. Issues can be across groups
and projects.
Read more about [Related Issues](related_issues.md).
=======
With [GitLab Starter](https://about.gitlab.com/products/), you can also
create various boards per project with [Multiple Issue Boards](https://docs.gitlab.com/ee/user/project/issue_board.html#multiple-issue-boards).
>>>>>>> upstream/master
### External Issue Tracker
......
......@@ -31,11 +31,18 @@ With GitLab merge requests, you can:
With **[GitLab Enterprise Edition][ee]**, you can also:
<<<<<<< HEAD
- View the deployment process across projects with [Multi-Project Pipeline Graphs](../../../ci/multi_project_pipeline_graphs.md) (available only in GitLab Premium)
- Request [approvals](#merge-request-approvals) from your managers (available in GitLab Starter)
- [Squash and merge](#squash-and-merge) for a cleaner commit history (available in GitLab Starter)
- Analyze the impact of your changes with [Code Quality reports](#code-quality-reports) (available in GitLab Starter)
- Determine the performance impact of changes with [Browser Performance Testing](#browser-performance-testing) (available in GitLab Premium)
=======
- View the deployment process across projects with [Multi-Project Pipeline Graphs](https://docs.gitlab.com/ee/ci/multi_project_pipeline_graphs.html#multi-project-pipeline-graphs) (available only in GitLab Premium)
- Request [approvals](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html) from your managers (available in GitLab Starter)
- [Squash and merge](https://docs.gitlab.com/ee/user/project/merge_requests/squash_and_merge.html) for a cleaner commit history (available in GitLab Starter)
- Analise the impact of your changes with [Code Quality reports](https://docs.gitlab.com/ee/user/project/merge_requests/code_quality_diff.html) (available in GitLab Starter)
>>>>>>> upstream/master
## Use cases
......@@ -43,10 +50,17 @@ A. Consider you are a software developer working in a team:
1. You checkout a new branch, and submit your changes through a merge request
1. You gather feedback from your team
<<<<<<< HEAD
1. You work on the implementation optimizing code with [Code Quality reports](#code-quality-reports)
1. You build and test your changes with GitLab CI/CD
1. You request the [approval](#merge-request-approvals) from your manager
1. Your manager pushes a commit with his final review, [approves the merge request](#merge-request-approvals), and set it to [merge when pipeline succeeds](#merge-when-pipeline-succeeds)
=======
1. You work on the implementation optimizing code with [Code Quality reports](https://docs.gitlab.com/ee/user/project/merge_requests/code_quality_diff.html) (available in GitLab Starter)
1. You build and test your changes with GitLab CI/CD
1. You request the approval from your manager
1. Your manager pushes a commit with his final review, [approves the merge request](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html), and set it to [merge when pipeline succeeds](#merge-when-pipeline-succeeds) (Merge Request Approvals are available in GitLab Starter)
>>>>>>> upstream/master
1. Your changes get deployed to production with [manual actions](../../../ci/yaml/README.md#manual-actions) for GitLab CI/CD
1. Your implementations were successfully shipped to your customer
......@@ -56,8 +70,13 @@ B. Consider you're a web developer writing a webpage for your company's website:
1. You gather feedback from your reviewers
1. Your changes are previewed with [Review Apps](../../../ci/review_apps/index.md)
1. You request your web designers for their implementation
<<<<<<< HEAD
1. You request the [approval](#merge-request-approvals) from your manager
1. Once approved, your merge request is [squashed and merged](#squash-and-merge), and [deployed to staging with GitLab Pages](https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/)
=======
1. You request the [approval](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html) from your manager (available in GitLab Starter)
1. Once approved, your merge request is [squashed and merged](https://docs.gitlab.com/ee/user/project/merge_requests/squash_and_merge.html), and [deployed to staging with GitLab Pages](https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/) (Squash and Merge is available in GitLab Starter)
>>>>>>> upstream/master
1. Your production team [cherry picks](#cherry-pick-changes) the merge commit into production
## Merge requests per project
......@@ -358,9 +377,13 @@ git checkout origin/merge-requests/1
[products]: https://about.gitlab.com/products/ "GitLab products page"
[protected branches]: ../protected_branches.md
<<<<<<< HEAD
[ci]: ../../../ci/README.md
[cc]: https://codeclimate.com/
[cd]: https://hub.docker.com/r/codeclimate/codeclimate/
[ee]: https://about.gitlab.com/products/ "GitLab Enterprise Edition"
[sitespeed]: https://www.sitespeed.io
[sitespeed-container]: https://hub.docker.com/r/sitespeedio/sitespeed.io/
=======
[ee]: https://about.gitlab.com/products/ "GitLab Enterprise Edition"
>>>>>>> upstream/master
......@@ -316,6 +316,47 @@ or various static site generators. Contributions are very welcome.
Visit the GitLab Pages group for a full list of example projects:
<https://gitlab.com/groups/pages>.
### Serving compressed assets
Most modern browsers support downloading files in a compressed format. This
speeds up downloads by reducing the size of files.
Before serving an uncompressed file, Pages will check whether the same file
exists with a `.gz` extension. If it does, and the browser supports receiving
compressed files, it will serve that version instead of the uncompressed one.
To take advantage of this feature, the artifact you upload to the Pages should
have this structure:
```
public/
├─┬ index.html
│ └ index.html.gz
├── css/
│   └─┬ main.css
│ └ main.css.gz
└── js/
└─┬ main.js
└ main.js.gz
```
This can be achieved by including a `script:` command like this in your
`.gitlab-ci.yml` pages job:
```yaml
pages:
# Other directives
script:
- # build the public/ directory first
- find public -type f -iregex '.*\.\(htm\|html\|txt\|text\|js\|css\)$' -execdir gzip -f --keep {} \;
```
By pre-compressing the files and including both versions in the artifact, Pages
can serve requests for both compressed and uncompressed content without
needing to compress files on-demand.
### Add a custom domain to your Pages website
For a complete guide on Pages domains, read through the article
......
......@@ -68,7 +68,7 @@ You can live preview changes submitted to a new branch with
With [GitLab Enterprise Edition](https://about.gitlab.com/products/)
subscriptions, you can also request
[approval](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html#merge-request-approvals) from your managers.
[approval](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html) from your managers.
To create, delete, and [branches](branches/index.md) via GitLab's UI:
......
......@@ -18,8 +18,11 @@ module API
snippet_blobs: Entities::Snippet
}.freeze
<<<<<<< HEAD
ELASTICSEARCH_SCOPES = %w(wiki_blobs blobs commits).freeze
=======
>>>>>>> upstream/master
def search(additional_params = {})
search_params = {
scope: params[:scope],
......@@ -35,12 +38,15 @@ module API
end
def process_results(results)
<<<<<<< HEAD
return [] if results.empty?
if results.is_a?(Elasticsearch::Model::Response::Response)
return paginate(results).map { |blob| Gitlab::Elastic::SearchResults.parse_search_result(blob) }
end
=======
>>>>>>> upstream/master
case params[:scope]
when 'wiki_blobs'
paginate(results).map { |blob| Gitlab::ProjectSearchResults.parse_search_result(blob) }
......@@ -58,6 +64,7 @@ module API
def entity
SCOPE_ENTITY[params[:scope].to_sym]
end
<<<<<<< HEAD
def check_elasticsearch_scope!
if ELASTICSEARCH_SCOPES.include?(params[:scope]) && !elasticsearch?
......@@ -68,6 +75,8 @@ module API
def elasticsearch?
Gitlab::CurrentSettings.elasticsearch_search?
end
=======
>>>>>>> upstream/master
end
resource :search do
......@@ -79,6 +88,7 @@ module API
requires :scope,
type: String,
desc: 'The scope of search, available scopes:
<<<<<<< HEAD
projects, issues, merge_requests, milestones, snippet_titles, snippet_blobs,
if Elasticsearch enabled: wiki_blobs, blobs, commits',
values: %w(projects issues merge_requests milestones snippet_titles snippet_blobs
......@@ -88,6 +98,13 @@ module API
get do
check_elasticsearch_scope!
=======
projects, issues, merge_requests, milestones, snippet_titles, snippet_blobs',
values: %w(projects issues merge_requests milestones snippet_titles snippet_blobs)
use :pagination
end
get do
>>>>>>> upstream/master
present search, with: entity
end
end
......@@ -102,6 +119,7 @@ module API
requires :scope,
type: String,
desc: 'The scope of search, available scopes:
<<<<<<< HEAD
projects, issues, merge_requests, milestones,
if Elasticsearch enabled: wiki_blobs, blobs, commits',
values: %w(projects issues merge_requests milestones wiki_blobs blobs commits)
......@@ -109,6 +127,13 @@ module API
end
get ':id/-/search' do
check_elasticsearch_scope!
=======
projects, issues, merge_requests, milestones',
values: %w(projects issues merge_requests milestones)
use :pagination
end
get ':id/-/search' do
>>>>>>> upstream/master
find_group!(params[:id])
present search(group_id: params[:id]), with: entity
......
......@@ -6,6 +6,8 @@ module Gitlab
def initialize(config)
@config = YAML.safe_load(config, [Symbol], [], true)
rescue Psych::Exception => e
raise FormatError, e.message
end
def valid?
......
......@@ -85,7 +85,7 @@ module Gitlab
begin
Gitlab::Ci::YamlProcessor.new(content, opts)
nil
rescue ValidationError, Psych::SyntaxError => e
rescue ValidationError => e
e.message
end
end
......
......@@ -402,15 +402,6 @@ module Gitlab
end
end
# Get a collection of Rugged::Reference objects for this commit.
#
# Ex.
# commit.ref(repo)
#
def refs(repo)
repo.refs_hash[id]
end
# Get ref names collection
#
# Ex.
......@@ -418,7 +409,7 @@ module Gitlab
#
def ref_names(repo)
refs(repo).map do |ref|
ref.name.sub(%r{^refs/(heads|remotes|tags)/}, "")
ref.sub(%r{^refs/(heads|remotes|tags)/}, "")
end
end
......@@ -553,6 +544,15 @@ module Gitlab
date: Google::Protobuf::Timestamp.new(seconds: author_or_committer[:time].to_i)
)
end
# Get a collection of Gitlab::Git::Ref objects for this commit.
#
# Ex.
# commit.ref(repo)
#
def refs(repo)
repo.refs_hash[id]
end
end
end
end
......@@ -631,21 +631,18 @@ module Gitlab
end
end
# Get refs hash which key is SHA1
# and value is a Rugged::Reference
# Get refs hash which key is is the commit id
# and value is a Gitlab::Git::Tag or Gitlab::Git::Branch
# Note that both inherit from Gitlab::Git::Ref
def refs_hash
# Initialize only when first call
if @refs_hash.nil?
@refs_hash = Hash.new { |h, k| h[k] = [] }
rugged.references.each do |r|
# Symbolic/remote references may not have an OID; skip over them
target_oid = r.target.try(:oid)
if target_oid
sha = rev_parse_target(target_oid).oid
@refs_hash[sha] << r
end
end
return @refs_hash if @refs_hash
@refs_hash = Hash.new { |h, k| h[k] = [] }
(tags + branches).each do |ref|
next unless ref.target && ref.name
@refs_hash[ref.dereferenced_target.id] << ref.name
end
@refs_hash
......
......@@ -25,9 +25,8 @@ module Gitlab
@repository.exists?
end
# Disabled because of https://gitlab.com/gitlab-org/gitaly/merge_requests/539
def write_page(name, format, content, commit_details)
@repository.gitaly_migrate(:wiki_write_page, status: Gitlab::GitalyClient::MigrationStatus::DISABLED) do |is_enabled|
@repository.gitaly_migrate(:wiki_write_page) do |is_enabled|
if is_enabled
gitaly_write_page(name, format, content, commit_details)
gollum_wiki.clear_cache
......@@ -48,9 +47,8 @@ module Gitlab
end
end
# Disable because of https://gitlab.com/gitlab-org/gitlab-ce/issues/42094
def update_page(page_path, title, format, content, commit_details)
@repository.gitaly_migrate(:wiki_update_page, status: Gitlab::GitalyClient::MigrationStatus::DISABLED) do |is_enabled|
@repository.gitaly_migrate(:wiki_update_page) do |is_enabled|
if is_enabled
gitaly_update_page(page_path, title, format, content, commit_details)
gollum_wiki.clear_cache
......
......@@ -222,14 +222,25 @@ module Gitlab
end
def find_commit(revision)
request = Gitaly::FindCommitRequest.new(
repository: @gitaly_repo,
revision: encode_binary(revision)
)
response = GitalyClient.call(@repository.storage, :commit_service, :find_commit, request, timeout: GitalyClient.medium_timeout)
response.commit
if RequestStore.active?
# We don't use RequeStstore.fetch(key) { ... } directly because `revision`
# can be a branch name, so we can't use it as a key as it could point
# to another commit later on (happens a lot in tests).
key = {
storage: @gitaly_repo.storage_name,
relative_path: @gitaly_repo.relative_path,
commit_id: revision
}
return RequestStore[key] if RequestStore.exist?(key)
commit = call_find_commit(revision)
return unless commit
key[:commit_id] = commit.id
RequestStore[key] = commit
else
call_find_commit(revision)
end
end
def patch(revision)
......@@ -346,6 +357,17 @@ module Gitlab
def encode_repeated(a)
Google::Protobuf::RepeatedField.new(:bytes, a.map { |s| encode_binary(s) } )
end
def call_find_commit(revision)
request = Gitaly::FindCommitRequest.new(
repository: @gitaly_repo,
revision: encode_binary(revision)
)
response = GitalyClient.call(@repository.storage, :commit_service, :find_commit, request, timeout: GitalyClient.medium_timeout)
response.commit
end
end
end
end
......@@ -4,7 +4,7 @@ module Gitlab
class AdditionalMetricsDeploymentQuery < BaseQuery
include QueryAdditionalMetrics
def query(deployment_id)
def query(environment_id, deployment_id)
Deployment.find_by(id: deployment_id).try do |deployment|
query_metrics(
common_query_context(
......
......@@ -2,7 +2,7 @@ module Gitlab
module Prometheus
module Queries
class DeploymentQuery < BaseQuery
def query(deployment_id)
def query(environment_id, deployment_id)
Deployment.find_by(id: deployment_id).try do |deployment|
environment_slug = deployment.environment.slug
......
......@@ -3,10 +3,10 @@ module Gitlab
# Helper methods to interact with Prometheus network services & resources
class PrometheusClient
attr_reader :api_url
attr_reader :rest_client, :headers
def initialize(api_url:)
@api_url = api_url
def initialize(rest_client)
@rest_client = rest_client
end
def ping
......@@ -40,37 +40,40 @@ module Gitlab
private
def json_api_get(type, args = {})
get(join_api_url(type, args))
path = ['api', 'v1', type].join('/')
get(path, args)
rescue JSON::ParserError
raise PrometheusError, 'Parsing response failed'
rescue Errno::ECONNREFUSED
raise PrometheusError, 'Connection refused'
end
def join_api_url(type, args = {})
url = URI.parse(api_url)
rescue URI::Error
raise PrometheusError, "Invalid API URL: #{api_url}"
else
url.path = [url.path.sub(%r{/+\z}, ''), 'api', 'v1', type].join('/')
url.query = args.to_query
url.to_s
end
def get(url)
handle_response(HTTParty.get(url))
def get(path, args)
response = rest_client[path].get(params: args)
handle_response(response)
rescue SocketError
raise PrometheusError, "Can't connect to #{url}"
raise PrometheusError, "Can't connect to #{rest_client.url}"
rescue OpenSSL::SSL::SSLError
raise PrometheusError, "#{url} contains invalid SSL data"
rescue HTTParty::Error
raise PrometheusError, "#{rest_client.url} contains invalid SSL data"
rescue RestClient::ExceptionWithResponse => ex
handle_exception_response(ex.response)
rescue RestClient::Exception
raise PrometheusError, "Network connection error"
end
def handle_response(response)
if response.code == 200 && response['status'] == 'success'
response['data'] || {}
elsif response.code == 400
raise PrometheusError, response['error'] || 'Bad data received'
json_data = JSON.parse(response.body)
if response.code == 200 && json_data['status'] == 'success'
json_data['data'] || {}
else
raise PrometheusError, "#{response.code} - #{response.body}"
end
end
def handle_exception_response(response)
if response.code == 400
json_data = JSON.parse(response.body)
raise PrometheusError, json_data['error'] || 'Bad data received'
else
raise PrometheusError, "#{response.code} - #{response.body}"
end
......
......@@ -2018,7 +2018,7 @@ msgstr ""
msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
msgstr ""
msgid "PrometheusService|Prometheus monitoring"
msgid "PrometheusService|Time-series monitoring service"
msgstr ""
msgid "PrometheusService|View environments"
......
......@@ -2018,7 +2018,7 @@ msgstr ""
msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
msgstr ""
msgid "PrometheusService|Prometheus monitoring"
msgid "PrometheusService|Time-series monitoring service"
msgstr ""
msgid "PrometheusService|View environments"
......
......@@ -2018,7 +2018,7 @@ msgstr ""
msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
msgstr ""
msgid "PrometheusService|Prometheus monitoring"
msgid "PrometheusService|Time-series monitoring service"
msgstr ""
msgid "PrometheusService|View environments"
......
......@@ -2018,7 +2018,7 @@ msgstr ""
msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
msgstr ""
msgid "PrometheusService|Prometheus monitoring"
msgid "PrometheusService|Time-series monitoring service"
msgstr ""
msgid "PrometheusService|View environments"
......
......@@ -2018,7 +2018,7 @@ msgstr ""
msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
msgstr ""
msgid "PrometheusService|Prometheus monitoring"
msgid "PrometheusService|Time-series monitoring service"
msgstr ""
msgid "PrometheusService|View environments"
......
......@@ -8,8 +8,13 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
<<<<<<< HEAD
"POT-Creation-Date: 2018-02-07 14:09+0100\n"
"PO-Revision-Date: 2018-02-07 14:09+0100\n"
=======
"POT-Creation-Date: 2018-02-07 13:35+0100\n"
"PO-Revision-Date: 2018-02-07 13:35+0100\n"
>>>>>>> upstream/master
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
......@@ -638,6 +643,7 @@ msgstr ""
msgid "CiVariable|All environments"
msgstr ""
<<<<<<< HEAD
msgid "CiVariable|Create wildcard"
msgstr ""
......@@ -647,6 +653,11 @@ msgstr ""
msgid "CiVariable|New environment"
msgstr ""
=======
msgid "CiVariable|Error occured while saving variables"
msgstr ""
>>>>>>> upstream/master
msgid "CiVariable|Protected"
msgstr ""
......@@ -833,9 +844,12 @@ msgstr ""
msgid "ClusterIntegration|More information"
msgstr ""
<<<<<<< HEAD
msgid "ClusterIntegration|Multiple Kubernetes clusters are available in GitLab Enterprise Edition Premium and Ultimate"
msgstr ""
=======
>>>>>>> upstream/master
msgid "ClusterIntegration|Note:"
msgstr ""
......@@ -1901,9 +1915,12 @@ msgstr ""
msgid "Login"
msgstr ""
<<<<<<< HEAD
msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
msgstr ""
=======
>>>>>>> upstream/master
msgid "Manage labels"
msgstr ""
......@@ -1979,9 +1996,12 @@ msgstr ""
msgid "Move issue"
msgstr ""
<<<<<<< HEAD
msgid "Multiple issue boards"
msgstr ""
=======
>>>>>>> upstream/master
msgid "Name new label"
msgstr ""
......@@ -2044,9 +2064,12 @@ msgstr ""
msgid "No assignee"
msgstr ""
<<<<<<< HEAD
msgid "No changes"
msgstr ""
=======
>>>>>>> upstream/master
msgid "No connection could be made to a Gitaly Server, please check your logs!"
msgstr ""
......@@ -2509,7 +2532,7 @@ msgstr ""
msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
msgstr ""
msgid "PrometheusService|Prometheus monitoring"
msgid "PrometheusService|Time-series monitoring service"
msgstr ""
msgid "PrometheusService|View environments"
......@@ -2530,9 +2553,12 @@ msgstr ""
msgid "Push events"
msgstr ""
<<<<<<< HEAD
msgid "PushRule|Committer restriction"
msgstr ""
=======
>>>>>>> upstream/master
msgid "Quick actions can be used in the issues description and comment boxes."
msgstr ""
......@@ -2753,9 +2779,12 @@ msgstr ""
msgid "Something went wrong trying to change the confidentiality of this issue"
msgstr ""
<<<<<<< HEAD
msgid "Something went wrong trying to change the locked state of this ${this.issuableDisplayName}"
msgstr ""
=======
>>>>>>> upstream/master
msgid "Something went wrong when toggling the button"
msgstr ""
......@@ -3308,9 +3337,12 @@ msgstr[1] ""
msgid "Time|s"
msgstr ""
<<<<<<< HEAD
msgid "Title"
msgstr ""
=======
>>>>>>> upstream/master
msgid "Todo"
msgstr ""
......@@ -3332,12 +3364,15 @@ msgstr ""
msgid "Total test time for all commits/merges"
msgstr ""
<<<<<<< HEAD
msgid "Track activity with Contribution Analytics."
msgstr ""
msgid "Track groups of issues that share a theme, across projects and milestones"
msgstr ""
=======
>>>>>>> upstream/master
msgid "Track time with quick actions"
msgstr ""
......@@ -3650,6 +3685,7 @@ msgstr ""
msgid "branch name"
msgstr ""
<<<<<<< HEAD
msgid "by"
msgstr ""
......@@ -3701,6 +3737,8 @@ msgstr ""
msgid "commit"
msgstr ""
=======
>>>>>>> upstream/master
msgid "confidentiality|You are going to turn off the confidentiality. This means <strong>everyone</strong> will be able to see and leave a comment on this issue."
msgstr ""
......@@ -3878,9 +3916,12 @@ msgstr ""
msgid "spendCommand|%{slash_command} will update the sum of the time spent."
msgstr ""
<<<<<<< HEAD
msgid "to help your contributors communicate effectively!"
msgstr ""
=======
>>>>>>> upstream/master
msgid "username"
msgstr ""
......
......@@ -2018,7 +2018,7 @@ msgstr "Nessuna metrica è stata monitorata. Per iniziare a monitorare, rilascia
msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
msgstr ""
msgid "PrometheusService|Prometheus monitoring"
msgid "PrometheusService|Time-series monitoring service"
msgstr ""
msgid "PrometheusService|View environments"
......
......@@ -2004,7 +2004,7 @@ msgstr ""
msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
msgstr ""
msgid "PrometheusService|Prometheus monitoring"
msgid "PrometheusService|Time-series monitoring service"
msgstr ""
msgid "PrometheusService|View environments"
......
......@@ -2004,7 +2004,7 @@ msgstr ""
msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
msgstr ""
msgid "PrometheusService|Prometheus monitoring"
msgid "PrometheusService|Time-series monitoring service"
msgstr ""
msgid "PrometheusService|View environments"
......
......@@ -2018,7 +2018,7 @@ msgstr ""
msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
msgstr ""
msgid "PrometheusService|Prometheus monitoring"
msgid "PrometheusService|Time-series monitoring service"
msgstr ""
msgid "PrometheusService|View environments"
......
......@@ -2032,7 +2032,7 @@ msgstr ""
msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
msgstr ""
msgid "PrometheusService|Prometheus monitoring"
msgid "PrometheusService|Time-series monitoring service"
msgstr ""
msgid "PrometheusService|View environments"
......
......@@ -2018,7 +2018,7 @@ msgstr "Nenhuma métrica está sendo monitorada. Para inicar o monitoramento, fa
msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
msgstr "URL da API base do Prometheus. como http://prometheus.example.com/"
msgid "PrometheusService|Prometheus monitoring"
msgid "PrometheusService|Time-series monitoring service"
msgstr "Monitoramento com Prometheus"
msgid "PrometheusService|View environments"
......
......@@ -2032,7 +2032,7 @@ msgstr ""
msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
msgstr ""
msgid "PrometheusService|Prometheus monitoring"
msgid "PrometheusService|Time-series monitoring service"
msgstr ""
msgid "PrometheusService|View environments"
......
......@@ -2032,7 +2032,7 @@ msgstr "Жодні метрики не відслідковуються. Для
msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
msgstr "Базова адреса Prometheus API, наприклад http://prometheus.example.com/"
msgid "PrometheusService|Prometheus monitoring"
msgid "PrometheusService|Time-series monitoring service"
msgstr "Моніторинг Prometheus"
msgid "PrometheusService|View environments"
......
......@@ -2004,7 +2004,7 @@ msgstr "没有监测指标。要开始监测,请部署到环境中。"
msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
msgstr "Prometheus API 地址,例如 http://prometheus.example.com/"
msgid "PrometheusService|Prometheus monitoring"
msgid "PrometheusService|Time-series monitoring service"
msgstr "Prometheus 监测"
msgid "PrometheusService|View environments"
......
......@@ -2004,7 +2004,7 @@ msgstr ""
msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
msgstr ""
msgid "PrometheusService|Prometheus monitoring"
msgid "PrometheusService|Time-series monitoring service"
msgstr ""
msgid "PrometheusService|View environments"
......
......@@ -2004,7 +2004,7 @@ msgstr ""
msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
msgstr ""
msgid "PrometheusService|Prometheus monitoring"
msgid "PrometheusService|Time-series monitoring service"
msgstr ""
msgid "PrometheusService|View environments"
......
......@@ -297,7 +297,8 @@ FactoryBot.define do
project.create_prometheus_service(
active: true,
properties: {
api_url: 'https://prometheus.example.com'
api_url: 'https://prometheus.example.com/',
manual_configuration: true
}
)
end
......
......@@ -30,7 +30,8 @@ FactoryBot.define do
project
active true
properties({
api_url: 'https://prometheus.example.com/'
api_url: 'https://prometheus.example.com/',
manual_configuration: true
})
end
......
require 'spec_helper'
# Remove skip_gitaly_mock flag when gitaly_update_page implements moving pages
describe 'User views a wiki page', :skip_gitaly_mock do
let(:user) { create(:user) }
let(:project) { create(:project, namespace: user.namespace) }
let(:wiki_page) do
create(:wiki_page,
wiki: project.wiki,
attrs: { title: 'home', content: 'Look at this [image](image.jpg)\n\n ![alt text](image.jpg)' })
end
before do
project.add_master(user)
sign_in(user)
end
describe 'User views a wiki page' do
shared_examples 'wiki page user view' do
let(:user) { create(:user) }
let(:project) { create(:project, namespace: user.namespace) }
let(:wiki_page) do
create(:wiki_page,
wiki: project.wiki,
attrs: { title: 'home', content: 'Look at this [image](image.jpg)\n\n ![alt text](image.jpg)' })
end
context 'when wiki is empty' do
before do
visit(project_wikis_path(project))
project.add_master(user)
sign_in(user)
end
click_on('New page')
context 'when wiki is empty' do
before do
visit(project_wikis_path(project))
page.within('#modal-new-wiki') do
fill_in(:new_wiki_path, with: 'one/two/three-test')
click_on('Create page')
end
click_on('New page')
page.within('.wiki-form') do
fill_in(:wiki_content, with: 'wiki content')
click_on('Create page')
page.within('#modal-new-wiki') do
fill_in(:new_wiki_path, with: 'one/two/three-test')
click_on('Create page')
end
page.within('.wiki-form') do
fill_in(:wiki_content, with: 'wiki content')
click_on('Create page')
end
end
end
it 'shows the history of a page that has a path', :js do
expect(current_path).to include('one/two/three-test')
it 'shows the history of a page that has a path', :js do
expect(current_path).to include('one/two/three-test')
first(:link, text: 'Three').click
click_on('Page history')
first(:link, text: 'Three').click
click_on('Page history')
expect(current_path).to include('one/two/three-test')
expect(current_path).to include('one/two/three-test')
page.within(:css, '.nav-text') do
expect(page).to have_content('History')
page.within(:css, '.nav-text') do
expect(page).to have_content('History')
end
end
end
it 'shows an old version of a page', :js do
expect(current_path).to include('one/two/three-test')
expect(find('.wiki-pages')).to have_content('Three')
it 'shows an old version of a page', :js do
expect(current_path).to include('one/two/three-test')
expect(find('.wiki-pages')).to have_content('Three')
first(:link, text: 'Three').click
first(:link, text: 'Three').click
expect(find('.nav-text')).to have_content('Three')
expect(find('.nav-text')).to have_content('Three')
click_on('Edit')
click_on('Edit')
expect(current_path).to include('one/two/three-test')
expect(page).to have_content('Edit Page')
expect(current_path).to include('one/two/three-test')
expect(page).to have_content('Edit Page')
fill_in('Content', with: 'Updated Wiki Content')
fill_in('Content', with: 'Updated Wiki Content')
click_on('Save changes')
click_on('Page history')
click_on('Save changes')
click_on('Page history')
page.within(:css, '.nav-text') do
expect(page).to have_content('History')
end
page.within(:css, '.nav-text') do
expect(page).to have_content('History')
end
find('a[href*="?version_id"]')
find('a[href*="?version_id"]')
end
end
end
context 'when a page does not have history' do
before do
visit(project_wiki_path(project, wiki_page))
end
context 'when a page does not have history' do
before do
visit(project_wiki_path(project, wiki_page))
end
it 'shows all the pages' do
expect(page).to have_content(user.name)
expect(find('.wiki-pages')).to have_content(wiki_page.title.capitalize)
end
it 'shows all the pages' do
expect(page).to have_content(user.name)
expect(find('.wiki-pages')).to have_content(wiki_page.title.capitalize)
end
it 'shows a file stored in a page' do
gollum_file_double = double('Gollum::File',
mime_type: 'image/jpeg',
name: 'images/image.jpg',
path: 'images/image.jpg',
raw_data: '')
wiki_file = Gitlab::Git::WikiFile.new(gollum_file_double)
it 'shows a file stored in a page' do
gollum_file_double = double('Gollum::File',
mime_type: 'image/jpeg',
name: 'images/image.jpg',
path: 'images/image.jpg',
raw_data: '')
wiki_file = Gitlab::Git::WikiFile.new(gollum_file_double)
allow(wiki_file).to receive(:mime_type).and_return('image/jpeg')
allow_any_instance_of(ProjectWiki).to receive(:find_file).with('image.jpg', nil).and_return(wiki_file)
allow(wiki_file).to receive(:mime_type).and_return('image/jpeg')
allow_any_instance_of(ProjectWiki).to receive(:find_file).with('image.jpg', nil).and_return(wiki_file)
expect(page).to have_xpath('//img[@data-src="image.jpg"]')
expect(page).to have_link('image', href: "#{project.wiki.wiki_base_path}/image.jpg")
expect(page).to have_xpath('//img[@data-src="image.jpg"]')
expect(page).to have_link('image', href: "#{project.wiki.wiki_base_path}/image.jpg")
click_on('image')
click_on('image')
expect(current_path).to match('wikis/image.jpg')
expect(page).not_to have_xpath('/html') # Page should render the image which means there is no html involved
end
expect(current_path).to match('wikis/image.jpg')
expect(page).not_to have_xpath('/html') # Page should render the image which means there is no html involved
end
it 'shows the creation page if file does not exist' do
expect(page).to have_link('image', href: "#{project.wiki.wiki_base_path}/image.jpg")
it 'shows the creation page if file does not exist' do
expect(page).to have_link('image', href: "#{project.wiki.wiki_base_path}/image.jpg")
click_on('image')
click_on('image')
expect(current_path).to match('wikis/image.jpg')
expect(page).to have_content('New Wiki Page')
expect(page).to have_content('Create page')
expect(current_path).to match('wikis/image.jpg')
expect(page).to have_content('New Wiki Page')
expect(page).to have_content('Create page')
end
end
end
context 'when a page has history' do
before do
wiki_page.update(message: 'updated home', content: 'updated [some link](other-page)')
end
context 'when a page has history' do
before do
wiki_page.update(message: 'updated home', content: 'updated [some link](other-page)')
end
it 'shows the page history' do
visit(project_wiki_path(project, wiki_page))
it 'shows the page history' do
visit(project_wiki_path(project, wiki_page))
expect(page).to have_selector('a.btn', text: 'Edit')
expect(page).to have_selector('a.btn', text: 'Edit')
click_on('Page history')
click_on('Page history')
expect(page).to have_content(user.name)
expect(page).to have_content("#{user.username} created page: home")
expect(page).to have_content('updated home')
expect(page).to have_content(user.name)
expect(page).to have_content("#{user.username} created page: home")
expect(page).to have_content('updated home')
end
it 'does not show the "Edit" button' do
visit(project_wiki_path(project, wiki_page, version_id: wiki_page.versions.last.id))
expect(page).not_to have_selector('a.btn', text: 'Edit')
end
end
it 'does not show the "Edit" button' do
visit(project_wiki_path(project, wiki_page, version_id: wiki_page.versions.last.id))
it 'opens a default wiki page', :js do
visit(project_path(project))
expect(page).not_to have_selector('a.btn', text: 'Edit')
find('.shortcuts-wiki').click
expect(page).to have_content('Home · Create Page')
end
end
it 'opens a default wiki page', :js do
visit(project_path(project))
find('.shortcuts-wiki').click
context 'when Gitaly is enabled' do
it_behaves_like 'wiki page user view'
end
expect(page).to have_content('Home · Create Page')
context 'when Gitaly is disabled', :skip_gitaly_mock do
it_behaves_like 'wiki page user view'
end
end
......@@ -7,10 +7,10 @@ describe GraphHelper do
let(:graph) { Network::Graph.new(project, 'master', commit, '') }
it 'filters our refs used by GitLab' do
allow(commit).to receive(:ref_names).and_return(['refs/merge-requests/abc', 'master', 'refs/tmp/xyz'])
self.instance_variable_set(:@graph, graph)
refs = get_refs(project.repository, commit)
expect(refs).to eq('master')
refs = refs(project.repository, commit)
expect(refs).to match('master')
end
end
end
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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