Commit bcd188c0 authored by GitLab Bot's avatar GitLab Bot

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

# Conflicts:
#	app/assets/stylesheets/pages/settings.scss
#	app/controllers/projects/clusters_controller.rb
#	app/models/group.rb
#	app/workers/project_cache_worker.rb
#	spec/features/admin/admin_settings_spec.rb
#	spec/features/projects/members/share_with_group_and_members_spec.rb
#	spec/features/protected_branches_spec.rb
#	spec/workers/project_cache_worker_spec.rb

[ci skip]
parents 05542079 2510985f
......@@ -375,10 +375,10 @@ package-and-qa:
<<: *single-script-job-variables
SCRIPT_NAME: trigger-build-docs
environment:
name: review-docs/$CI_COMMIT_REF_NAME
name: review-docs/$CI_COMMIT_REF_SLUG
# DOCS_REVIEW_APPS_DOMAIN and DOCS_GITLAB_REPO_SUFFIX are secret variables
# Discussion: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14236/diffs#note_40140693
url: http://$DOCS_GITLAB_REPO_SUFFIX-$CI_ENVIRONMENT_SLUG.$DOCS_REVIEW_APPS_DOMAIN/$DOCS_GITLAB_REPO_SUFFIX
url: http://$CI_ENVIRONMENT_SLUG.$DOCS_REVIEW_APPS_DOMAIN/$DOCS_GITLAB_REPO_SUFFIX
on_stop: review-docs-cleanup
# Trigger a manual docs build in gitlab-docs only on non docs-only branches.
......@@ -404,9 +404,8 @@ review-docs-deploy:
- gem install gitlab --no-ri --no-rdoc
- ./$SCRIPT_NAME deploy
only:
- /(^docs[\/-].*|.*-docs$)/
- branches@gitlab-org/gitlab-ce
- branches@gitlab-org/gitlab-ee
- /(^docs[\/-].*|.*-docs$)/@gitlab-org/gitlab-ce
- /(^docs[\/-].*|.*-docs$)/@gitlab-org/gitlab-ee
<<: *except-qa
# Cleanup remote environment of gitlab-docs
......@@ -414,7 +413,7 @@ review-docs-cleanup:
<<: *review-docs
stage: post-cleanup
environment:
name: review-docs/$CI_COMMIT_REF_NAME
name: review-docs/$CI_COMMIT_REF_SLUG
action: stop
when: manual
script:
......@@ -432,11 +431,9 @@ cloud-native-image:
variables:
GIT_DEPTH: "1"
cache: {}
before_script:
- gem install gitlab --no-rdoc --no-ri
- chmod 755 ./scripts/trigger-build-cloud-native
script:
- ./scripts/trigger-build-cloud-native
- gem install gitlab --no-ri --no-rdoc
- ./trigger-build cng
only:
- tags@gitlab-org/gitlab-ce
- tags@gitlab-org/gitlab-ee
......
......@@ -47,7 +47,7 @@ gem 'omniauth-google-oauth2', '~> 0.5.3'
gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos
gem 'omniauth-oauth2-generic', '~> 0.2.2'
gem 'omniauth-saml', '~> 1.10'
gem 'omniauth-shibboleth', '~> 1.2.0'
gem 'omniauth-shibboleth', '~> 1.3.0'
gem 'omniauth-twitter', '~> 1.4'
gem 'omniauth_crowd', '~> 2.2.0'
gem 'omniauth-authentiq', '~> 0.3.3'
......
......@@ -597,7 +597,7 @@ GEM
omniauth-saml (1.10.0)
omniauth (~> 1.3, >= 1.3.2)
ruby-saml (~> 1.7)
omniauth-shibboleth (1.2.1)
omniauth-shibboleth (1.3.0)
omniauth (>= 1.0.0)
omniauth-twitter (1.4.0)
omniauth-oauth (~> 1.1)
......@@ -1138,7 +1138,7 @@ DEPENDENCIES
omniauth-kerberos (~> 0.3.0)
omniauth-oauth2-generic (~> 0.2.2)
omniauth-saml (~> 1.10)
omniauth-shibboleth (~> 1.2.0)
omniauth-shibboleth (~> 1.3.0)
omniauth-twitter (~> 1.4)
omniauth_crowd (~> 2.2.0)
org-ruby (~> 0.9.12)
......
......@@ -601,7 +601,7 @@ GEM
omniauth-saml (1.10.0)
omniauth (~> 1.3, >= 1.3.2)
ruby-saml (~> 1.7)
omniauth-shibboleth (1.2.1)
omniauth-shibboleth (1.3.0)
omniauth (>= 1.0.0)
omniauth-twitter (1.4.0)
omniauth-oauth (~> 1.1)
......@@ -1148,7 +1148,7 @@ DEPENDENCIES
omniauth-kerberos (~> 0.3.0)
omniauth-oauth2-generic (~> 0.2.2)
omniauth-saml (~> 1.10)
omniauth-shibboleth (~> 1.2.0)
omniauth-shibboleth (~> 1.3.0)
omniauth-twitter (~> 1.4)
omniauth_crowd (~> 2.2.0)
org-ruby (~> 0.9.12)
......
......@@ -2,6 +2,7 @@
import _ from 'underscore';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import Icon from '~/vue_shared/components/icon.vue';
import FileIcon from '~/vue_shared/components/file_icon.vue';
import Tooltip from '~/vue_shared/directives/tooltip';
import { truncateSha } from '~/lib/utils/text_utility';
import { __, s__, sprintf } from '~/locale';
......@@ -12,6 +13,7 @@ export default {
ClipboardButton,
EditButton,
Icon,
FileIcon,
},
directives: {
Tooltip,
......@@ -139,18 +141,20 @@ export default {
:name="collapseIcon"
:size="16"
aria-hidden="true"
class="diff-toggle-caret"
class="diff-toggle-caret append-right-5"
@click.stop="handleToggle"
/>
<a
ref="titleWrapper"
:href="titleLink"
class="append-right-4"
>
<i
:class="`fa-${icon}`"
class="fa fa-fw"
<file-icon
:file-name="filePath"
:size="18"
aria-hidden="true"
></i>
css-classes="js-file-icon append-right-5"
/>
<span v-if="diffFile.renamedFile">
<strong
v-tooltip
......
......@@ -24,19 +24,21 @@ export default {
...mapGetters(['commit']),
parallelDiffLines() {
return this.diffLines.map(line => {
const parallelLine = Object.assign({}, line);
if (line.left) {
Object.assign(line, { left: trimFirstCharOfLineContent(line.left) });
parallelLine.left = trimFirstCharOfLineContent(line.left);
} else {
Object.assign(line, { left: { type: EMPTY_CELL_TYPE } });
parallelLine.left = { type: EMPTY_CELL_TYPE };
}
if (line.right) {
Object.assign(line, { right: trimFirstCharOfLineContent(line.right) });
parallelLine.right = trimFirstCharOfLineContent(line.right);
} else {
Object.assign(line, { right: { type: EMPTY_CELL_TYPE } });
parallelLine.right = { type: EMPTY_CELL_TYPE };
}
return line;
return parallelLine;
});
},
diffLinesLength() {
......
......@@ -155,18 +155,21 @@ export function addContextLines(options) {
}
}
export function trimFirstCharOfLineContent(line) {
if (!line.richText) {
return line;
}
const firstChar = line.richText.charAt(0);
if (firstChar === ' ' || firstChar === '+' || firstChar === '-') {
Object.assign(line, {
richText: line.richText.substring(1),
});
/**
* Trims the first char of the `richText` property when it's either a space or a diff symbol.
* @param {Object} line
* @returns {Object}
*/
export function trimFirstCharOfLineContent(line = {}) {
const parsedLine = Object.assign({}, line);
if (line.richText) {
const firstChar = parsedLine.richText.charAt(0);
if (firstChar === ' ' || firstChar === '+' || firstChar === '-') {
parsedLine.richText = line.richText.substring(1);
}
}
return line;
return parsedLine;
}
<script>
import _ from 'underscore';
import { s__ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
import Flash from '../../flash';
import MonitoringService from '../services/monitoring_service';
import GraphGroup from './graph_group.vue';
......@@ -13,6 +15,7 @@ export default {
Graph,
GraphGroup,
EmptyState,
Icon,
},
props: {
hasMetrics: {
......@@ -80,6 +83,14 @@ export default {
type: String,
required: true,
},
environmentsEndpoint: {
type: String,
required: true,
},
currentEnvironmentName: {
type: String,
required: true,
},
},
data() {
return {
......@@ -96,6 +107,7 @@ export default {
this.service = new MonitoringService({
metricsEndpoint: this.metricsEndpoint,
deploymentEndpoint: this.deploymentEndpoint,
environmentsEndpoint: this.environmentsEndpoint,
});
eventHub.$on('toggleAspectRatio', this.toggleAspectRatio);
eventHub.$on('hoverChanged', this.hoverChanged);
......@@ -122,7 +134,11 @@ export default {
this.service
.getDeploymentData()
.then(data => this.store.storeDeploymentData(data))
.catch(() => new Flash('Error getting deployment information.')),
.catch(() => Flash(s__('Metrics|There was an error getting deployment information.'))),
this.service
.getEnvironmentsData()
.then((data) => this.store.storeEnvironmentsData(data))
.catch(() => Flash(s__('Metrics|There was an error getting environments information.'))),
])
.then(() => {
if (this.store.groups.length < 1) {
......@@ -155,8 +171,41 @@ export default {
<template>
<div
v-if="!showEmptyState"
class="prometheus-graphs"
class="prometheus-graphs prepend-top-10"
>
<div class="environments d-flex align-items-center">
{{ s__('Metrics|Environment') }}
<div class="dropdown prepend-left-10">
<button
class="dropdown-menu-toggle"
data-toggle="dropdown"
type="button"
>
<span>
{{ currentEnvironmentName }}
</span>
<icon
name="chevron-down"
/>
</button>
<div class="dropdown-menu dropdown-menu-selectable dropdown-menu-drop-up">
<ul>
<li
v-for="environment in store.environmentsData"
:key="environment.latest.id"
>
<a
:href="environment.latest.metrics_path"
:class="{ 'is-active': environment.latest.name == currentEnvironmentName }"
class="dropdown-item"
>
{{ environment.latest.name }}
</a>
</li>
</ul>
</div>
</div>
</div>
<graph-group
v-for="(groupData, index) in store.groups"
:key="index"
......
import axios from '../../lib/utils/axios_utils';
import statusCodes from '../../lib/utils/http_status';
import { backOff } from '../../lib/utils/common_utils';
import { s__ } from '../../locale';
const MAX_REQUESTS = 3;
......@@ -23,9 +24,10 @@ function backOffRequest(makeRequestCallback) {
}
export default class MonitoringService {
constructor({ metricsEndpoint, deploymentEndpoint }) {
constructor({ metricsEndpoint, deploymentEndpoint, environmentsEndpoint }) {
this.metricsEndpoint = metricsEndpoint;
this.deploymentEndpoint = deploymentEndpoint;
this.environmentsEndpoint = environmentsEndpoint;
}
getGraphsData() {
......@@ -33,7 +35,7 @@ export default class MonitoringService {
.then(resp => resp.data)
.then((response) => {
if (!response || !response.data) {
throw new Error('Unexpected metrics data response from prometheus endpoint');
throw new Error(s__('Metrics|Unexpected metrics data response from prometheus endpoint'));
}
return response.data;
});
......@@ -47,9 +49,20 @@ export default class MonitoringService {
.then(resp => resp.data)
.then((response) => {
if (!response || !response.deployments) {
throw new Error('Unexpected deployment data response from prometheus endpoint');
throw new Error(s__('Metrics|Unexpected deployment data response from prometheus endpoint'));
}
return response.deployments;
});
}
getEnvironmentsData() {
return axios.get(this.environmentsEndpoint)
.then(resp => resp.data)
.then((response) => {
if (!response || !response.environments) {
throw new Error(s__('Metrics|There was an error fetching the environments data, please try again'));
}
return response.environments;
});
}
}
......@@ -24,6 +24,7 @@ export default class MonitoringStore {
constructor() {
this.groups = [];
this.deploymentData = [];
this.environmentsData = [];
}
storeMetrics(groups = []) {
......@@ -37,6 +38,10 @@ export default class MonitoringStore {
this.deploymentData = deploymentData;
}
storeEnvironmentsData(environmentsData = []) {
this.environmentsData = environmentsData;
}
getMetricsCount() {
return this.groups.reduce((count, group) => count + group.metrics.length, 0);
}
......
<script>
import { mapState, mapActions } from 'vuex';
import imageDiffHelper from '~/image_diff/helpers/index';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import DiffFileHeader from '~/diffs/components/diff_file_header.vue';
import SkeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue';
import { trimFirstCharOfLineContent } from '~/diffs/store/utils';
import { mapState, mapActions } from 'vuex';
import imageDiffHelper from '~/image_diff/helpers/index';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import DiffFileHeader from '~/diffs/components/diff_file_header.vue';
import SkeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue';
import { trimFirstCharOfLineContent } from '~/diffs/store/utils';
export default {
components: {
DiffFileHeader,
SkeletonLoadingContainer,
},
props: {
discussion: {
type: Object,
required: true,
export default {
components: {
DiffFileHeader,
SkeletonLoadingContainer,
},
},
data() {
return {
error: false,
};
},
computed: {
...mapState({
noteableData: state => state.notes.noteableData,
}),
hasTruncatedDiffLines() {
return this.discussion.truncatedDiffLines && this.discussion.truncatedDiffLines.length !== 0;
props: {
discussion: {
type: Object,
required: true,
},
},
isDiscussionsExpanded() {
return true; // TODO: @fatihacet - Fix this.
data() {
return {
error: false,
};
},
isCollapsed() {
return this.diffFile.collapsed || false;
},
isImageDiff() {
return !this.diffFile.text;
},
diffFileClass() {
const { text } = this.diffFile;
return text ? 'text-file' : 'js-image-file';
},
diffFile() {
return convertObjectPropsToCamelCase(this.discussion.diffFile, { deep: true });
},
imageDiffHtml() {
return this.discussion.imageDiffHtml;
},
currentUser() {
return this.noteableData.current_user;
},
userColorScheme() {
return window.gon.user_color_scheme;
},
normalizedDiffLines() {
const lines = this.discussion.truncatedDiffLines || [];
computed: {
...mapState({
noteableData: state => state.notes.noteableData,
}),
hasTruncatedDiffLines() {
return this.discussion.truncatedDiffLines &&
this.discussion.truncatedDiffLines.length !== 0;
},
isDiscussionsExpanded() {
return true; // TODO: @fatihacet - Fix this.
},
isCollapsed() {
return this.diffFile.collapsed || false;
},
isImageDiff() {
return !this.diffFile.text;
},
diffFileClass() {
const { text } = this.diffFile;
return text ? 'text-file' : 'js-image-file';
},
diffFile() {
return convertObjectPropsToCamelCase(this.discussion.diffFile, { deep: true });
},
imageDiffHtml() {
return this.discussion.imageDiffHtml;
},
currentUser() {
return this.noteableData.current_user;
},
userColorScheme() {
return window.gon.user_color_scheme;
},
normalizedDiffLines() {
if (this.discussion.truncatedDiffLines) {
return this.discussion.truncatedDiffLines.map(line =>
trimFirstCharOfLineContent(convertObjectPropsToCamelCase(line)),
);
}
return lines.map(line => trimFirstCharOfLineContent(convertObjectPropsToCamelCase(line)));
return [];
},
},
},
mounted() {
if (this.isImageDiff) {
const canCreateNote = false;
const renderCommentBadge = true;
imageDiffHelper.initImageDiff(this.$refs.fileHolder, canCreateNote, renderCommentBadge);
} else if (!this.hasTruncatedDiffLines) {
this.fetchDiff();
}
},
methods: {
...mapActions(['fetchDiscussionDiffLines']),
rowTag(html) {
return html.outerHTML ? 'tr' : 'template';
mounted() {
if (this.isImageDiff) {
const canCreateNote = false;
const renderCommentBadge = true;
imageDiffHelper.initImageDiff(this.$refs.fileHolder, canCreateNote, renderCommentBadge);
} else if (!this.hasTruncatedDiffLines) {
this.fetchDiff();
}
},
fetchDiff() {
this.error = false;
this.fetchDiscussionDiffLines(this.discussion)
.then(this.highlight)
.catch(() => {
this.error = true;
});
methods: {
...mapActions(['fetchDiscussionDiffLines']),
rowTag(html) {
return html.outerHTML ? 'tr' : 'template';
},
fetchDiff() {
this.error = false;
this.fetchDiscussionDiffLines(this.discussion)
.then(this.highlight)
.catch(() => {
this.error = true;
});
},
},
},
};
};
</script>
<template>
......
......@@ -20,6 +20,7 @@ export default class ShortcutsNavigation extends Shortcuts {
Mousetrap.bind('g s', () => findAndFollowLink('.shortcuts-snippets'));
Mousetrap.bind('g k', () => findAndFollowLink('.shortcuts-kubernetes'));
Mousetrap.bind('g e', () => findAndFollowLink('.shortcuts-environments'));
Mousetrap.bind('g l', () => findAndFollowLink('.shortcuts-metrics'));
Mousetrap.bind('i', () => findAndFollowLink('.shortcuts-new-issue'));
this.enabledHelp.push('.hidden-shortcut.project');
......
......@@ -29,8 +29,8 @@
methods: {
isValid(form) {
return !form ||
form.find('.js-vue-markdown-field').length ||
$(this.$el).closest('form') === form[0];
form.find('.js-vue-markdown-field').length &&
$(this.$el).closest('form')[0] === form[0];
},
previewMarkdownTab(event, form) {
......
......@@ -15,7 +15,7 @@
}
svg {
vertical-align: text-bottom;
vertical-align: middle;
}
}
......
......@@ -384,6 +384,23 @@
}
}
.prometheus-graphs {
.environments {
.dropdown-menu-toggle {
svg {
position: absolute;
right: 5%;
top: 25%;
}
}
.dropdown-menu-toggle,
.dropdown-menu {
width: 240px;
}
}
}
.environments-actions {
.external-url,
.monitoring-url,
......
......@@ -191,6 +191,7 @@
}
}
<<<<<<< HEAD
.nested-settings {
padding-left: 20px;
}
......@@ -213,6 +214,21 @@
.float-right {
margin-left: auto;
=======
.initialize-with-readme-setting {
.form-check {
margin-bottom: 10px;
.option-title {
font-weight: $gl-font-weight-normal;
display: inline-block;
color: $gl-text-color;
}
.option-description {
color: $project-option-descr-color;
}
>>>>>>> upstream/master
}
}
......
class Projects::ClustersController < Projects::ApplicationController
<<<<<<< HEAD
prepend EE::Projects::ClustersController
=======
>>>>>>> upstream/master
before_action :cluster, except: [:index, :new, :create_gcp, :create_user]
before_action :authorize_read_cluster!
before_action :generate_gcp_authorize_url, only: [:new]
......
......@@ -122,6 +122,16 @@ class Projects::EnvironmentsController < Projects::ApplicationController
end
end
def metrics_redirect
environment = project.default_environment
if environment
redirect_to environment_metrics_path(environment)
else
render :empty
end
end
def metrics
# Currently, this acts as a hint to load the metrics details into the cache
# if they aren't there already
......
......@@ -349,6 +349,7 @@ class ProjectsController < Projects::ApplicationController
:visibility_level,
:template_name,
:merge_method,
:initialize_with_readme,
project_feature_attributes: %i[
builds_access_level
......
......@@ -87,8 +87,12 @@ class Group < Namespace
end
def public_or_visible_to_user(user)
<<<<<<< HEAD
where(
'id IN (?) OR namespaces.visibility_level IN (?)',
=======
where('id IN (?) OR namespaces.visibility_level IN (?)',
>>>>>>> upstream/master
user.authorized_groups.select(:id),
Gitlab::VisibilityLevel.levels_for_user(user))
end
......
......@@ -1792,6 +1792,15 @@ class Project < ActiveRecord::Base
end
end
def default_environment
production_first = "(CASE WHEN name = 'production' THEN 0 ELSE 1 END), id ASC"
environments
.with_state(:available)
.reorder(production_first)
.first
end
def secret_variables_for(ref:, environment: nil)
# EE would use the environment
if protected_for?(ref)
......
......@@ -25,6 +25,8 @@ class DiffFileEntity < Grape::Entity
expose :can_modify_blob do |diff_file|
merge_request = options[:merge_request]
next unless diff_file.blob
if merge_request&.source_project && current_user
can_modify_blob?(diff_file.blob, merge_request.source_project, merge_request.source_branch)
else
......@@ -108,6 +110,7 @@ class DiffFileEntity < Grape::Entity
project = merge_request.target_project
next unless project
next unless diff_file.content_sha
project_blob_path(project, tree_join(diff_file.content_sha, diff_file.new_path))
end
......@@ -125,6 +128,8 @@ class DiffFileEntity < Grape::Entity
end
expose :context_lines_path, if: -> (diff_file, _) { diff_file.text? } do |diff_file|
next unless diff_file.content_sha
project_blob_diff_path(diff_file.repository.project, tree_join(diff_file.content_sha, diff_file.file_path))
end
......
......@@ -3,7 +3,7 @@ class DiscussionEntity < Grape::Entity
include NotesHelper
expose :id, :reply_id
expose :position, if: -> (d, _) { d.diff_discussion? }
expose :position, if: -> (d, _) { d.diff_discussion? && !d.legacy_diff_discussion? }
expose :line_code, if: -> (d, _) { d.diff_discussion? }
expose :expanded?, as: :expanded
expose :active?, as: :active, if: -> (d, _) { d.diff_discussion? }
......
......@@ -4,6 +4,8 @@ module Projects
def initialize(user, params)
@current_user, @params = user, params.dup
@skip_wiki = @params.delete(:skip_wiki)
@initialize_with_readme = Gitlab::Utils.to_boolean(@params.delete(:initialize_with_readme))
end
def execute
......@@ -13,7 +15,6 @@ module Projects
forked_from_project_id = params.delete(:forked_from_project_id)
import_data = params.delete(:import_data)
@skip_wiki = params.delete(:skip_wiki)
@project = Project.new(params)
......@@ -104,6 +105,8 @@ module Projects
setup_authorizations
current_user.invalidate_personal_projects_count
create_readme if @initialize_with_readme
end
# Refresh the current user's authorizations inline (so they can access the
......@@ -118,6 +121,17 @@ module Projects
end
end
def create_readme
commit_attrs = {
branch_name: 'master',
commit_message: 'Initial commit',
file_path: 'README.md',
file_content: "# #{@project.name}\n\n#{@project.description}"
}
Files::CreateService.new(@project, current_user, commit_attrs).execute
end
def skip_wiki?
!@project.feature_available?(:wiki, current_user) || @skip_wiki
end
......
# frozen_string_literal: true
class AttachmentUploader < GitlabUploader
include RecordsUploads::Concern
include ObjectStorage::Concern
......
# frozen_string_literal: true
class AvatarUploader < GitlabUploader
include UploaderHelper
include RecordsUploads::Concern
......
# frozen_string_literal: true
class FaviconUploader < AttachmentUploader
EXTENSION_WHITELIST = %w[png ico].freeze
......
# frozen_string_literal: true
class FileMover
attr_reader :secret, :file_name, :model, :update_field
......
# frozen_string_literal: true
# This class breaks the actual CarrierWave concept.
# Every uploader should use a base_dir that is model agnostic so we can build
# back URLs from base_dir-relative paths saved in the `Upload` model.
......@@ -117,7 +119,7 @@ class FileUploader < GitlabUploader
end
def markdown_link
markdown = "[#{markdown_name}](#{secure_url})"
markdown = +"[#{markdown_name}](#{secure_url})"
markdown.prepend("!") if image_or_video? || dangerous?
markdown
end
......
# frozen_string_literal: true
class GitlabUploader < CarrierWave::Uploader::Base
class_attribute :options
......
# frozen_string_literal: true
class JobArtifactUploader < GitlabUploader
extend Workhorse::UploadPath
include ObjectStorage::Concern
......
# frozen_string_literal: true
class LegacyArtifactUploader < GitlabUploader
extend Workhorse::UploadPath
include ObjectStorage::Concern
......
# frozen_string_literal: true
class LfsObjectUploader < GitlabUploader
extend Workhorse::UploadPath
include ObjectStorage::Concern
......
# frozen_string_literal: true
class NamespaceFileUploader < FileUploader
# Re-Override
def self.root
......
# frozen_string_literal: true
require 'fog/aws'
require 'carrierwave/storage/fog'
......
# frozen_string_literal: true
class PersonalFileUploader < FileUploader
# Re-Override
def self.root
......
# frozen_string_literal: true
module RecordsUploads
module Concern
extend ActiveSupport::Concern
......
# frozen_string_literal: true
# Extra methods for uploader
module UploaderHelper
IMAGE_EXT = %w[png jpg jpeg gif bmp tiff ico].freeze
......
# frozen_string_literal: true
module Workhorse
module UploadPath
def workhorse_upload_path
......
# frozen_string_literal: true
class AbstractPathValidator < ActiveModel::EachValidator
extend Gitlab::EncodingHelper
......
# frozen_string_literal: true
class CertificateFingerprintValidator < ActiveModel::EachValidator
FINGERPRINT_PATTERN = /\A([a-zA-Z0-9]{2}[\s\-:]?){16,}\z/.freeze
......
# frozen_string_literal: true
# UrlValidator
#
# Custom validator for private keys.
......
# frozen_string_literal: true
# UrlValidator
#
# Custom validator for private keys.
......
# frozen_string_literal: true
# ClusterNameValidator
#
# Custom validator for ClusterName.
......
# frozen_string_literal: true
# ColorValidator
#
# Custom validator for web color codes. It requires the leading hash symbol and
......
# frozen_string_literal: true
# CronTimezoneValidator
#
# Custom validator for CronTimezone.
......
# frozen_string_literal: true
# CronValidator
#
# Custom validator for Cron.
......
# frozen_string_literal: true
# DurationValidator
#
# Validate the format conforms with ChronicDuration
......
# frozen_string_literal: true
class EmailValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
record.errors.add(attribute, :invalid) unless value =~ Devise.email_regexp
......
# frozen_string_literal: true
class KeyRestrictionValidator < ActiveModel::EachValidator
FORBIDDEN = -1
......
# frozen_string_literal: true
# LineCodeValidator
#
# Custom validator for GitLab line codes.
......
# frozen_string_literal: true
# NamespaceNameValidator
#
# Custom validator for GitLab namespace name strings.
......
# frozen_string_literal: true
class NamespacePathValidator < AbstractPathValidator
extend Gitlab::EncodingHelper
......
# frozen_string_literal: true
class ProjectPathValidator < AbstractPathValidator
extend Gitlab::EncodingHelper
......
# frozen_string_literal: true
# PublicUrlValidator
#
# Custom validator for URLs. This validator works like UrlValidator but
......
# frozen_string_literal: true
class TopLevelGroupValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
if value&.subgroup?
......
# frozen_string_literal: true
# UrlValidator
#
# Custom validator for URLs.
......
# frozen_string_literal: true
# VariableDuplicatesValidator
#
# This validator is designed for especially the following condition
......@@ -22,8 +24,8 @@ class VariableDuplicatesValidator < ActiveModel::EachValidator
def validate_duplicates(record, attribute, values)
duplicates = values.reject(&:marked_for_destruction?).group_by(&:key).select { |_, v| v.many? }.map(&:first)
if duplicates.any?
error_message = "have duplicate values (#{duplicates.join(", ")})"
error_message += " for #{values.first.send(options[:scope])} scope" if options[:scope] # rubocop:disable GitlabSecurity/PublicSend
error_message = +"have duplicate values (#{duplicates.join(", ")})"
error_message << " for #{values.first.send(options[:scope])} scope" if options[:scope] # rubocop:disable GitlabSecurity/PublicSend
record.errors.add(attribute, error_message)
end
end
......
......@@ -179,6 +179,12 @@
%kbd e
%td
Go to environments
%tr
%td.shortcut
%kbd g
%kbd l
%td
Go to metrics
%tr
%td.shortcut
%kbd g
......
......@@ -196,7 +196,7 @@
- if project_nav_tab? :operations
= nav_link(controller: [:environments, :clusters, :user, :gcp]) do
= link_to project_environments_path(@project), class: 'shortcuts-operations' do
= link_to metrics_project_environments_path(@project), class: 'shortcuts-operations' do
.nav-icon-container
= sprite_icon('cloud-gear')
%span.nav-item-name
......@@ -204,14 +204,19 @@
%ul.sidebar-sub-level-items
= nav_link(controller: [:environments, :clusters, :user, :gcp], html_options: { class: "fly-out-top-item" } ) do
= link_to project_environments_path(@project) do
= link_to metrics_project_environments_path(@project) do
%strong.fly-out-top-item-name
= _('Operations')
%li.divider.fly-out-top-item
- if project_nav_tab? :environments
= nav_link(controller: :environments) do
= link_to project_environments_path(@project), title: 'Environments', class: 'shortcuts-environments' do
= nav_link(controller: :environments, action: [:metrics, :metrics_redirect]) do
= link_to metrics_project_environments_path(@project), title: _('Metrics'), class: 'shortcuts-metrics' do
%span
= _('Metrics')
= nav_link(controller: :environments, action: [:index, :folder, :show, :new, :edit, :create, :update, :stop, :terminal]) do
= link_to project_environments_path(@project), title: _('Environments'), class: 'shortcuts-environments' do
%span
= _('Environments')
......
......@@ -40,5 +40,15 @@
= link_to icon('question-circle'), help_page_path("public_access/public_access"), aria: { label: 'Documentation for Visibility Level' }, target: '_blank', rel: 'noopener noreferrer'
= render 'shared/visibility_level', f: f, visibility_level: visibility_level.to_i, can_change_visibility_level: true, form_model: @project, with_label: false
.form-group.row.initialize-with-readme-setting
%div{ :class => "col-sm-12" }
.form-check
= check_box_tag 'project[initialize_with_readme]', '1', false, class: 'form-check-input'
= label_tag 'project[initialize_with_readme]', class: 'form-check-label' do
.option-title
%strong Initialize repository with a README
.option-description
Allows you to immediately clone this project’s repository. Skip this if you plan to push up an existing repository.
= f.submit 'Create project', class: "btn btn-create project-submit", tabindex: 4
= link_to 'Cancel', dashboard_projects_path, class: 'btn btn-cancel'
.modal{ id: "revoke-modal-#{token.id}" }
.modal{ id: "revoke-modal-#{token.id}", tabindex: -1 }
.modal-dialog
.modal-content
.modal-header
......
- page_title _("Metrics")
.row
.col-sm-12
.svg-content
= image_tag 'illustrations/operations_metrics_empty.svg'
.row.empty-environments
.col-sm-12.text-center
%h4
= s_('Metrics|No deployed environments')
.state-description
= s_('Metrics|Check out the CI/CD documentation on deploying to an environment')
.prepend-top-10
= link_to s_("Metrics|Learn about environments"), help_page_path('ci/environments'), class: 'btn btn-success'
......@@ -2,15 +2,9 @@
- page_title "Metrics for environment", @environment.name
.prometheus-container{ class: container_class }
.top-area
.row
.col-sm-6
%h3
Environment:
= link_to @environment.name, environment_path(@environment)
#prometheus-graphs{ data: { "settings-path": edit_project_service_path(@project, 'prometheus'),
"clusters-path": project_clusters_path(@project),
"current-environment-name": @environment.name,
"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'),
......@@ -18,6 +12,7 @@
"empty-unable-to-connect-svg-path": image_path('illustrations/monitoring/unable_to_connect.svg'),
"metrics-endpoint": additional_metrics_project_environment_path(@project, @environment, format: :json),
"deployment-endpoint": project_environment_deployments_path(@project, @environment, format: :json),
"environments-endpoint": project_environments_path(@project, format: :json),
"project-path": project_path(@project),
"tags-path": project_tags_path(@project),
"has-metrics": "#{@environment.has_metrics?}" } }
......@@ -3,34 +3,34 @@
.d-none.d-sm-block
- if can?(current_user, :update_project_snippet, @snippet)
= link_to edit_project_snippet_path(@project, @snippet), class: "btn btn-grouped" do
Edit
= _('Edit')
- if can?(current_user, :update_project_snippet, @snippet)
= link_to project_snippet_path(@project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-inverted btn-remove", title: 'Delete Snippet' do
Delete
= link_to project_snippet_path(@project, @snippet), method: :delete, data: { confirm: _("Are you sure?") }, class: "btn btn-grouped btn-inverted btn-remove", title: _('Delete Snippet') do
= _('Delete')
- if can?(current_user, :create_project_snippet, @project)
= link_to new_project_snippet_path(@project), class: 'btn btn-grouped btn-inverted btn-create', title: "New snippet" do
New snippet
= link_to new_project_snippet_path(@project), class: 'btn btn-grouped btn-inverted btn-create', title: _("New snippet") do
= _('New snippet')
- if @snippet.submittable_as_spam_by?(current_user)
= link_to 'Submit as spam', mark_as_spam_project_snippet_path(@project, @snippet), method: :post, class: 'btn btn-grouped btn-spam', title: 'Submit as spam'
= link_to _('Submit as spam'), mark_as_spam_project_snippet_path(@project, @snippet), method: :post, class: 'btn btn-grouped btn-spam', title: _('Submit as spam')
- if can?(current_user, :create_project_snippet, @project) || can?(current_user, :update_project_snippet, @snippet)
.d-block.d-sm-none.dropdown
%button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } }
Options
= _('Options')
= icon('caret-down')
.dropdown-menu.dropdown-menu-full-width
%ul
- if can?(current_user, :create_project_snippet, @project)
%li
= link_to new_project_snippet_path(@project), title: "New snippet" do
New snippet
= link_to new_project_snippet_path(@project), title: _("New snippet") do
= _('New snippet')
- if can?(current_user, :update_project_snippet, @snippet)
%li
= link_to project_snippet_path(@project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do
Delete
= link_to project_snippet_path(@project, @snippet), method: :delete, data: { confirm: _("Are you sure?") }, title: _('Delete Snippet') do
= _('Delete')
- if can?(current_user, :update_project_snippet, @snippet)
%li
= link_to edit_project_snippet_path(@project, @snippet) do
Edit
= _('Edit')
- if @snippet.submittable_as_spam_by?(current_user)
%li
= link_to 'Submit as spam', mark_as_spam_project_snippet_path(@project, @snippet), method: :post
= link_to _('Submit as spam'), mark_as_spam_project_snippet_path(@project, @snippet), method: :post
- add_to_breadcrumbs "Snippets", project_snippets_path(@project)
- add_to_breadcrumbs _("Snippets"), project_snippets_path(@project)
- breadcrumb_title @snippet.to_reference
- page_title "Edit", "#{@snippet.title} (#{@snippet.to_reference})", "Snippets"
- page_title _("Edit"), "#{@snippet.title} (#{@snippet.to_reference})", _("Snippets")
%h3.page-title
Edit Snippet
= _("Edit Snippet")
%hr
= render "shared/snippets/form", url: project_snippet_path(@project, @snippet)
- page_title "Snippets"
- page_title _("Snippets")
- if current_user
.top-area
......@@ -7,6 +7,6 @@
.nav-controls
- if can?(current_user, :create_project_snippet, @project)
= link_to "New snippet", new_project_snippet_path(@project), class: "btn btn-new", title: "New snippet"
= link_to _("New snippet"), new_project_snippet_path(@project), class: "btn btn-new", title: _("New snippet")
= render 'snippets/snippets'
- add_to_breadcrumbs "Snippets", project_snippets_path(@project)
- breadcrumb_title "New"
- page_title "New Snippets"
- add_to_breadcrumbs _("Snippets"), project_snippets_path(@project)
- breadcrumb_title _("New")
- page_title _("New Snippets")
%h3.page-title
New Snippet
= _('New Snippet')
%hr
= render "shared/snippets/form", url: project_snippets_path(@project, @snippet)
- @content_class = "limit-container-width limited-inner-width-container" unless fluid_layout
- add_to_breadcrumbs "Snippets", project_snippets_path(@project)
- add_to_breadcrumbs _("Snippets"), project_snippets_path(@project)
- breadcrumb_title @snippet.to_reference
- page_title "#{@snippet.title} (#{@snippet.to_reference})", "Snippets"
- page_title "#{@snippet.title} (#{@snippet.to_reference})", _("Snippets")
= render 'shared/snippets/header'
......
......@@ -3,7 +3,10 @@
# Worker for updating any project specific caches.
class ProjectCacheWorker
include ApplicationWorker
<<<<<<< HEAD
prepend EE::Workers::ProjectCacheWorker
=======
>>>>>>> upstream/master
LEASE_TIMEOUT = 15.minutes.to_i
......
---
title: Do not use '-f' with 'rm' in gitlab-basics docs
merge_request: 18027
author: Elias Werberich
type: changed
---
title: Add option to add README when creating a project
merge_request: 20335
author:
type: added
---
title: Fixed bug when editing a comment in an issue,the preview mode is toggled in
the main textarea
merge_request: 20112
author: Constance Okoghenun
type: fixed
---
title: Add environment dropdown for the metrics page
merge_request: 19833
author:
type: changed
---
title: Improves performance of mr code, by fixing the state being mutated outside
of the store in the util function trimFirstCharOfLineContent and in map operations.
Avoids map operation in an empty array. Adds specs to the trimFirstCharOfLineContent
function
merge_request: 20380
author: filipa
type: performance
---
title: Close revoke deploy token modal on escape keypress
merge_request: 20347
author: George Tsiolis
type: changed
---
title: Enable frozen string in apps/validators/*.rb
merge_request: 20382
author: gfyoung
type: other
---
title: Enable frozen string in apps/validators/*.rb
merge_request: 20220
author: gfyoung
type: other
......@@ -265,6 +265,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end
collection do
get :metrics, action: :metrics_redirect
get :folder, path: 'folders/*id', constraints: { format: /(html|json)/ }
end
......
......@@ -43,7 +43,19 @@ exclude shibboleth URLs from rewriting, add "RewriteCond %{REQUEST_URI} !/Shibbo
RequestHeader set X_FORWARDED_PROTO 'https'
```
1. Edit /etc/gitlab/gitlab.rb configuration file, your shibboleth attributes should be in form of "HTTP_ATTRIBUTE" and you should adjust them to your need and environment. Add any other configuration you need.
1. Edit /etc/gitlab/gitlab.rb configuration file to enable OmniAuth and add
Shibboleth as an OmniAuth provider. User attributes will be sent from the
Apache reverse proxy to GitLab as headers with the names from the Shibboleth
attribute mapping. Therefore the values of the `args` hash
should be in the form of `"HTTP_ATTRIBUTE"`. The keys in the hash are arguments
to the [OmniAuth::Strategies::Shibboleth class](https://github.com/toyokazu/omniauth-shibboleth/blob/master/lib/omniauth/strategies/shibboleth.rb)
and are documented by the [omniauth-shibboleth gem](https://github.com/toyokazu/omniauth-shibboleth)
(take care to note the version of the gem packaged with GitLab). If some of
your users appear to be authenticated by Shibboleth and Apache, but GitLab
rejects their account with a URI that contains "e-mail is invalid" then your
Shibboleth Identity Provider or Attribute Authority may be asserting multiple
e-mail addresses. In this instance, you might consider setting the
`multi_values` argument to `first`.
File should look like this:
```
......@@ -58,14 +70,15 @@ gitlab_rails['omniauth_block_auto_created_users'] = false
gitlab_rails['omniauth_enabled'] = true
gitlab_rails['omniauth_providers'] = [
{
"name" => 'shibboleth',
"args" => {
"shib_session_id_field" => "HTTP_SHIB_SESSION_ID",
"name" => "'shibboleth"',
"label" => "Text for Login Button",
"args" => {
"shib_session_id_field" => "HTTP_SHIB_SESSION_ID",
"shib_application_id_field" => "HTTP_SHIB_APPLICATION_ID",
"uid_field" => 'HTTP_EPPN',
"name_field" => 'HTTP_CN',
"uid_field" => 'HTTP_EPPN',
"name_field" => 'HTTP_CN',
"info_fields" => { "email" => 'HTTP_MAIL'}
}
}
}
]
......
......@@ -247,6 +247,7 @@ module Gitlab
lines = highlighted_diff_lines
return if lines.empty?
return if blob.nil?
last_line = lines.last
......
......@@ -429,7 +429,7 @@ module Gitlab
def self.count_stack
return unless RequestStore.active?
stack_string = caller.drop(1).join("\n")
stack_string = Gitlab::Profiler.clean_backtrace(caller).drop(1).join("\n")
RequestStore.store[:stack_counter] ||= Hash.new
......
......@@ -8,8 +8,8 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-07-01 16:35+1000\n"
"PO-Revision-Date: 2018-07-01 16:35+1000\n"
"POT-Creation-Date: 2018-07-01 21:24+1000\n"
"PO-Revision-Date: 2018-07-01 21:24+1000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
......@@ -1815,6 +1815,9 @@ msgstr ""
msgid "Delete"
msgstr ""
msgid "Delete Snippet"
msgstr ""
msgid "Delete list"
msgstr ""
......@@ -2036,6 +2039,9 @@ msgstr ""
msgid "Edit Pipeline Schedule %{id}"
msgstr ""
msgid "Edit Snippet"
msgstr ""
msgid "Edit files in the editor and commit changes here"
msgstr ""
......@@ -2978,6 +2984,9 @@ msgstr ""
msgid "Nav|Sign out and sign in with a different account"
msgstr ""
msgid "New"
msgstr ""
msgid "New Identity"
msgstr ""
......@@ -2998,6 +3007,12 @@ msgstr ""
msgid "New Pipeline Schedule"
msgstr ""
msgid "New Snippet"
msgstr ""
msgid "New Snippets"
msgstr ""
msgid "New branch"
msgstr ""
......@@ -4301,6 +4316,9 @@ msgstr ""
msgid "Subgroups"
msgstr ""
msgid "Submit as spam"
msgstr ""
msgid "Subscribe"
msgstr ""
......
......@@ -22,14 +22,6 @@ module QA
element :squash_checkbox
end
def rebase!
click_element :mr_rebase_button
wait(reload: false) do
has_text?('Fast-forward merge without a merge commit')
end
end
def fast_forward_possible?
!has_text?('Fast-forward merge is not possible')
end
......@@ -40,7 +32,35 @@ module QA
has_selector?('.accept-merge-request')
end
def rebase!
# The rebase button is disabled on load
wait do
has_css?(element_selector_css(:mr_rebase_button))
end
# The rebase button is enabled via JS
wait(reload: false) do
!first(element_selector_css(:mr_rebase_button)).disabled?
end
click_element :mr_rebase_button
wait(reload: false) do
has_text?('Fast-forward merge without a merge commit')
end
end
def merge!
# The merge button is disabled on load
wait do
has_css?(element_selector_css(:merge_button))
end
# The merge button is enabled via JS
wait(reload: false) do
!first(element_selector_css(:merge_button)).disabled?
end
click_element :merge_button
wait(reload: false) do
......@@ -49,10 +69,16 @@ module QA
end
def mark_to_squash
wait(reload: true) do
# The squash checkbox is disabled on load
wait do
has_css?(element_selector_css(:squash_checkbox))
end
# The squash checkbox is enabled via JS
wait(reload: false) do
!first(element_selector_css(:squash_checkbox)).disabled?
end
click_element :squash_checkbox
end
end
......
......@@ -24,6 +24,8 @@ module QA
merge_request.visit!
expect(page).to have_text('to be squashed')
Page::MergeRequest::Show.perform do |merge_request_page|
merge_request_page.mark_to_squash
merge_request_page.merge!
......
......@@ -16,18 +16,14 @@ end
GITLAB_DOCS_REPO = 'gitlab-com/gitlab-docs'.freeze
#
# Truncate the remote docs branch name if it's more than 63 characters
# otherwise we hit the filesystem limit and the directory name where
# NGINX serves the site won't match the branch name.
# Truncate the remote docs branch name otherwise we hit the filesystem
# limit and the directory name where NGINX serves the site won't match
# the branch name.
#
def docs_branch
# The maximum string length a file can have on a filesystem (ext4)
# is 63 characters. Let's use something smaller to be 100% sure.
max = 42
# Prefix the remote branch with the slug of the project in order
# to avoid name conflicts in the rare case the branch name already
# exists in the docs repo and truncate to max length.
"#{slug}-#{ENV["CI_ENVIRONMENT_SLUG"]}"[0...max]
# is 63 characters. CI_ENVIRONMENT_SLUG is limited to 24 characters.
ENV["CI_ENVIRONMENT_SLUG"]
end
#
......
......@@ -277,6 +277,25 @@ describe Projects::EnvironmentsController do
end
end
describe 'GET #metrics_redirect' do
let(:project) { create(:project) }
it 'redirects to environment if it exists' do
environment = create(:environment, name: 'production', project: project)
get :metrics_redirect, namespace_id: project.namespace, project_id: project
expect(response).to redirect_to(environment_metrics_path(environment))
end
it 'redirects to empty page if no environment exists' do
get :metrics_redirect, namespace_id: project.namespace, project_id: project
expect(response).to be_ok
expect(response).to render_template 'empty'
end
end
describe 'GET #metrics' do
before do
allow(controller).to receive(:environment).and_return(environment)
......
......@@ -35,6 +35,7 @@ describe 'Admin updates settings' do
expect(find('#application_setting_visibility_level_20')).not_to be_checked
end
<<<<<<< HEAD
describe 'LDAP settings' do
context 'with LDAP enabled' do
it 'Change allow group owners to manage ldap' do
......@@ -60,6 +61,8 @@ describe 'Admin updates settings' do
end
end
=======
>>>>>>> upstream/master
it 'Modify import sources' do
expect(Gitlab::CurrentSettings.import_sources).not_to be_empty
......
......@@ -63,6 +63,14 @@ describe "User comments on issue", :js do
page.within(".current-note-edit-form") do
fill_in("note[note]", with: comment)
find('textarea').send_keys [:control, :shift, 'p']
expect(page).to have_selector('.current-note-edit-form .md-preview-holder')
expect(page.find('.current-note-edit-form .md-preview-holder p')).to have_content(comment)
end
expect(page).to have_selector('.new-note .note-textarea')
page.within(".current-note-edit-form") do
click_button("Save comment")
end
......
......@@ -7,8 +7,13 @@ describe 'Project > Members > Share with Group', :js do
let(:master) { create(:user) }
describe 'Share with group lock' do
<<<<<<< HEAD:spec/features/projects/members/share_with_group_and_members_spec.rb
shared_examples 'the project cannot be shared with groups' do
it 'user is only able to share with members' do
=======
shared_examples 'the project can be shared with groups' do
it 'the "Share with group" tab exists' do
>>>>>>> upstream/master:spec/features/projects/members/share_with_group_spec.rb
visit project_settings_members_path(project)
expect(page).not_to have_selector('#add-member-tab')
......@@ -18,8 +23,13 @@ describe 'Project > Members > Share with Group', :js do
end
end
<<<<<<< HEAD:spec/features/projects/members/share_with_group_and_members_spec.rb
shared_examples 'the project cannot be shared with members' do
it 'user is only able to share with groups' do
=======
shared_examples 'the project cannot be shared with groups' do
it 'the "Share with group" tab does not exist' do
>>>>>>> upstream/master:spec/features/projects/members/share_with_group_spec.rb
visit project_settings_members_path(project)
expect(page).not_to have_selector('#add-member-tab')
......
......@@ -48,6 +48,15 @@ describe 'New project' do
end
end
context 'Readme selector' do
it 'shows the initialize with Readme checkbox' do
visit new_project_path
expect(page).to have_css('input#project_initialize_with_readme')
expect(page).to have_content('Initialize repository with a README')
end
end
context 'Namespace selector' do
context 'with user namespace' do
before do
......
require 'rails_helper'
describe 'Repository Settings > User sees revoke deploy token modal', :js do
let(:project) { create(:project, :public, :repository) }
let(:user) { project.creator }
let(:role) { :developer }
let!(:deploy_token) { create(:deploy_token, :gitlab_deploy_token, projects: [project]) }
before do
project.add_role(user, role)
sign_in(user)
visit(project_settings_repository_path(project))
click_link('Revoke')
end
it 'shows the revoke deploy token modal' do
expect(page).to have_content('You are about to revoke')
end
it 'closes the revoke deploy token modal with escape keypress' do
find('.modal.show').send_keys(:escape)
expect(page).not_to have_content('You are about to revoke')
end
end
......@@ -110,6 +110,14 @@ describe 'User uses shortcuts', :js do
end
context 'when navigating to the Operations pages' do
it 'redirects to the Metrics page' do
find('body').native.send_key('g')
find('body').native.send_key('l')
expect(page).to have_active_navigation('Operations')
expect(page).to have_active_sub_navigation('Metrics')
end
it 'redirects to the Environments page' do
find('body').native.send_key('g')
find('body').native.send_key('e')
......
require 'spec_helper'
describe 'Protected Branches', :js do
<<<<<<< HEAD
include EE::ProtectedBranchHelpers
=======
>>>>>>> upstream/master
let(:user) { create(:user) }
let(:admin) { create(:admin) }
let(:project) { create(:project, :repository) }
......
......@@ -280,11 +280,11 @@ describe('diff_file_header', () => {
});
});
it('displays an icon in the title', () => {
it('displays an file icon in the title', () => {
vm = mountComponent(Component, props);
const icon = vm.$el.querySelector(`i[class="fa fa-fw fa-${vm.icon}"]`);
expect(icon).not.toBe(null);
expect(vm.$el.querySelector('svg.js-file-icon use').getAttribute('xlink:href')).toContain(
'ruby',
);
});
describe('file paths', () => {
......
......@@ -176,4 +176,35 @@ describe('DiffsStoreUtils', () => {
expect(linesWithReferences[1].metaData.newPos).toEqual(3);
});
});
describe('trimFirstCharOfLineContent', () => {
it('trims the line when it starts with a space', () => {
expect(utils.trimFirstCharOfLineContent({ richText: ' diff' })).toEqual({ richText: 'diff' });
});
it('trims the line when it starts with a +', () => {
expect(utils.trimFirstCharOfLineContent({ richText: '+diff' })).toEqual({ richText: 'diff' });
});
it('trims the line when it starts with a -', () => {
expect(utils.trimFirstCharOfLineContent({ richText: '-diff' })).toEqual({ richText: 'diff' });
});
it('does not trims the line when it starts with a letter', () => {
expect(utils.trimFirstCharOfLineContent({ richText: 'diff' })).toEqual({ richText: 'diff' });
});
it('does not modify the provided object', () => {
const lineObj = {
richText: ' diff',
};
utils.trimFirstCharOfLineContent(lineObj);
expect(lineObj).toEqual({ richText: ' diff' });
});
it('handles a undefined or null parameter', () => {
expect(utils.trimFirstCharOfLineContent()).toEqual({});
});
});
});
......@@ -2,7 +2,7 @@ import Vue from 'vue';
import MockAdapter from 'axios-mock-adapter';
import Dashboard from '~/monitoring/components/dashboard.vue';
import axios from '~/lib/utils/axios_utils';
import { metricsGroupsAPIResponse, mockApiEndpoint } from './mock_data';
import { metricsGroupsAPIResponse, mockApiEndpoint, environmentData } from './mock_data';
describe('Dashboard', () => {
let DashboardComponent;
......@@ -20,6 +20,8 @@ describe('Dashboard', () => {
emptyLoadingSvgPath: '/path/to/loading.svg',
emptyNoDataSvgPath: '/path/to/no-data.svg',
emptyUnableToConnectSvgPath: '/path/to/unable-to-connect.svg',
environmentsEndpoint: '/root/hello-prometheus/environments/35',
currentEnvironmentName: 'production',
};
beforeEach(() => {
......@@ -50,7 +52,7 @@ describe('Dashboard', () => {
mock.restore();
});
it('shows up a loading state', (done) => {
it('shows up a loading state', done => {
const component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
propsData: { ...propsData, hasMetrics: true },
......@@ -62,7 +64,7 @@ describe('Dashboard', () => {
});
});
it('hides the legend when showLegend is false', (done) => {
it('hides the legend when showLegend is false', done => {
const component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
propsData: { ...propsData, hasMetrics: true, showLegend: false },
......@@ -76,7 +78,7 @@ describe('Dashboard', () => {
});
});
it('hides the group panels when showPanels is false', (done) => {
it('hides the group panels when showPanels is false', done => {
const component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
propsData: { ...propsData, hasMetrics: true, showPanels: false },
......@@ -89,5 +91,40 @@ describe('Dashboard', () => {
done();
});
});
it('renders the dropdown with a number of environments', done => {
const component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
propsData: { ...propsData, hasMetrics: true, showPanels: false },
});
component.store.storeEnvironmentsData(environmentData);
setTimeout(() => {
const dropdownMenuEnvironments = component.$el.querySelectorAll('.dropdown-menu ul li a');
expect(dropdownMenuEnvironments.length).toEqual(component.store.environmentsData.length);
done();
});
});
it('renders the dropdown with a single is-active element', done => {
const component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
propsData: { ...propsData, hasMetrics: true, showPanels: false },
});
component.store.storeEnvironmentsData(environmentData);
setTimeout(() => {
const dropdownIsActiveElement = component.$el.querySelectorAll(
'.dropdown-menu ul li a.is-active',
);
expect(dropdownIsActiveElement.length).toEqual(1);
expect(dropdownIsActiveElement[0].textContent.trim()).toEqual(
component.currentEnvironmentName,
);
done();
});
});
});
});
......@@ -6542,3 +6542,44 @@ export function convertDatesMultipleSeries(multipleSeries) {
});
return convertedMultiple;
}
export const environmentData = [
{
name: 'production',
size: 1,
latest: {
id: 34,
name: 'production',
state: 'available',
external_url: 'http://root-autodevops-deploy.my-fake-domain.com',
environment_type: null,
stop_action: false,
metrics_path: '/root/hello-prometheus/environments/34/metrics',
environment_path: '/root/hello-prometheus/environments/34',
stop_path: '/root/hello-prometheus/environments/34/stop',
terminal_path: '/root/hello-prometheus/environments/34/terminal',
folder_path: '/root/hello-prometheus/environments/folders/production',
created_at: '2018-06-29T16:53:38.301Z',
updated_at: '2018-06-29T16:57:09.825Z',
},
},
{
name: 'review',
size: 1,
latest: {
id: 35,
name: 'review/noop-branch',
state: 'available',
external_url: 'http://root-autodevops-deploy-review-noop-branc-die93w.my-fake-domain.com',
environment_type: 'review',
stop_action: true,
metrics_path: '/root/hello-prometheus/environments/35/metrics',
environment_path: '/root/hello-prometheus/environments/35',
stop_path: '/root/hello-prometheus/environments/35/stop',
terminal_path: '/root/hello-prometheus/environments/35/terminal',
folder_path: '/root/hello-prometheus/environments/folders/review',
created_at: '2018-07-03T18:39:41.702Z',
updated_at: '2018-07-03T18:44:54.010Z',
},
},
];
......@@ -51,7 +51,7 @@ describe('Markdown field header component', () => {
spyOn(vm, '$emit');
$(document).triggerHandler('markdown-preview:show', [
$('<form><textarea class="markdown-area"></textarea></textarea></form>'),
$('<form><div class="js-vue-markdown-field"><textarea class="markdown-area"></textarea></div></form>'),
]);
expect(vm.$emit).not.toHaveBeenCalled();
......
......@@ -2631,6 +2631,28 @@ describe Project do
end
end
describe '#default_environment' do
let(:project) { create(:project) }
it 'returns production environment when it exists' do
production = create(:environment, name: "production", project: project)
create(:environment, name: 'staging', project: project)
expect(project.default_environment).to eq(production)
end
it 'returns first environment when no production environment exists' do
create(:environment, name: 'staging', project: project)
create(:environment, name: 'foo', project: project)
expect(project.default_environment).to eq(project.environments.first)
end
it 'returns nil when no available environment exists' do
expect(project.default_environment).to be_nil
end
end
describe '#secret_variables_for' do
let(:project) { create(:project) }
......
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