diff options
88 files changed, 1245 insertions, 288 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e1ffeaebd5..50348332c36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 11.10.3 (2019-04-30) + +### Security (1 change) + +- Allow to see project events only with api scope token. + + ## 11.10.2 (2019-04-25) ### Security (4 changes) @@ -632,6 +639,13 @@ entry. - Removes EE differences for jobs/getters.js. +## 11.8.10 (2019-04-30) + +### Security (1 change) + +- Allow to see project events only with api scope token. + + ## 11.8.8 (2019-04-23) ### Fixed (5 changes) @@ -417,7 +417,7 @@ group :ed25519 do end # Gitaly GRPC client -gem 'gitaly-proto', '~> 1.22.0', require: 'gitaly' +gem 'gitaly-proto', '~> 1.26.0', require: 'gitaly' gem 'grpc', '~> 1.19.0' diff --git a/Gemfile.lock b/Gemfile.lock index c5ad2357434..3b03a8ef691 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -283,7 +283,7 @@ GEM gettext_i18n_rails (>= 0.7.1) po_to_json (>= 1.0.0) rails (>= 3.2.0) - gitaly-proto (1.22.0) + gitaly-proto (1.26.0) grpc (~> 1.0) github-markup (1.7.0) gitlab-default_value_for (3.1.1) @@ -1056,7 +1056,7 @@ DEPENDENCIES gettext (~> 3.2.2) gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails_js (~> 1.3) - gitaly-proto (~> 1.22.0) + gitaly-proto (~> 1.26.0) github-markup (~> 1.7.0) gitlab-default_value_for (~> 3.1.1) gitlab-labkit (~> 0.1.2) diff --git a/app/assets/javascripts/blob/balsamiq_viewer.js b/app/assets/javascripts/blob/balsamiq_viewer.js index b88e69a07bf..2e537d8c000 100644 --- a/app/assets/javascripts/blob/balsamiq_viewer.js +++ b/app/assets/javascripts/blob/balsamiq_viewer.js @@ -1,8 +1,9 @@ import Flash from '../flash'; import BalsamiqViewer from './balsamiq/balsamiq_viewer'; +import { __ } from '~/locale'; function onError() { - const flash = new Flash('Balsamiq file could not be loaded.'); + const flash = new Flash(__('Balsamiq file could not be loaded.')); return flash; } diff --git a/app/assets/javascripts/blob/blob_file_dropzone.js b/app/assets/javascripts/blob/blob_file_dropzone.js index cd3251ad1ca..9010cd0c3c1 100644 --- a/app/assets/javascripts/blob/blob_file_dropzone.js +++ b/app/assets/javascripts/blob/blob_file_dropzone.js @@ -5,6 +5,7 @@ import Dropzone from 'dropzone'; import { visitUrl } from '../lib/utils/url_utility'; import { HIDDEN_CLASS } from '../lib/utils/constants'; import csrf from '../lib/utils/csrf'; +import { sprintf, __ } from '~/locale'; Dropzone.autoDiscover = false; @@ -73,7 +74,7 @@ export default class BlobFileDropzone { .html(errorMessage) .text(); $('.dropzone-alerts') - .html(`Error uploading file: "${stripped}"`) + .html(sprintf(__('Error uploading file: %{stripped}'), { stripped })) .show(); this.removeFile(file); }, @@ -84,7 +85,7 @@ export default class BlobFileDropzone { e.stopPropagation(); if (dropzone[0].dropzone.getQueuedFiles().length === 0) { // eslint-disable-next-line no-alert - alert('Please select a file'); + alert(__('Please select a file')); return false; } toggleLoading(submitButton, submitButtonLoadingIcon, true); diff --git a/app/assets/javascripts/blob/sketch/index.js b/app/assets/javascripts/blob/sketch/index.js index 57c1baa9886..dbff03dc734 100644 --- a/app/assets/javascripts/blob/sketch/index.js +++ b/app/assets/javascripts/blob/sketch/index.js @@ -1,5 +1,6 @@ import JSZip from 'jszip'; import JSZipUtils from 'jszip-utils'; +import { __ } from '~/locale'; export default class SketchLoader { constructor(container) { @@ -56,10 +57,10 @@ export default class SketchLoader { const errorMsg = document.createElement('p'); errorMsg.className = 'prepend-top-default append-bottom-default text-center'; - errorMsg.textContent = ` + errorMsg.textContent = __(` Cannot show preview. For previews on sketch files, they must have the file format introduced by Sketch version 43 and above. - `; + `); this.container.appendChild(errorMsg); this.removeLoadingIcon(); diff --git a/app/assets/javascripts/blob/template_selectors/dockerfile_selector.js b/app/assets/javascripts/blob/template_selectors/dockerfile_selector.js index 4718b642617..659d57e6a6f 100644 --- a/app/assets/javascripts/blob/template_selectors/dockerfile_selector.js +++ b/app/assets/javascripts/blob/template_selectors/dockerfile_selector.js @@ -1,11 +1,12 @@ import FileTemplateSelector from '../file_template_selector'; +import { __ } from '~/locale'; export default class DockerfileSelector extends FileTemplateSelector { constructor({ mediator }) { super(mediator); this.config = { key: 'dockerfile', - name: 'Dockerfile', + name: __('Dockerfile'), pattern: /(Dockerfile)/, type: 'dockerfiles', dropdown: '.js-dockerfile-selector', diff --git a/app/assets/javascripts/blob/viewer/index.js b/app/assets/javascripts/blob/viewer/index.js index d0359fc5fe9..d246a1f6064 100644 --- a/app/assets/javascripts/blob/viewer/index.js +++ b/app/assets/javascripts/blob/viewer/index.js @@ -2,6 +2,7 @@ import $ from 'jquery'; import Flash from '../../flash'; import { handleLocationHash } from '../../lib/utils/common_utils'; import axios from '../../lib/utils/axios_utils'; +import { __ } from '~/locale'; export default class BlobViewer { constructor() { @@ -26,7 +27,7 @@ export default class BlobViewer { promise .then(module => module.default(viewer)) .catch(error => { - Flash('Error loading file viewer.'); + Flash(__('Error loading file viewer.')); throw error; }); @@ -106,16 +107,19 @@ export default class BlobViewer { if (!this.copySourceBtn) return; if (this.simpleViewer.getAttribute('data-loaded')) { - this.copySourceBtn.setAttribute('title', 'Copy source to clipboard'); + this.copySourceBtn.setAttribute('title', __('Copy source to clipboard')); this.copySourceBtn.classList.remove('disabled'); } else if (this.activeViewer === this.simpleViewer) { this.copySourceBtn.setAttribute( 'title', - 'Wait for the source to load to copy it to the clipboard', + __('Wait for the source to load to copy it to the clipboard'), ); this.copySourceBtn.classList.add('disabled'); } else { - this.copySourceBtn.setAttribute('title', 'Switch to the source to copy it to the clipboard'); + this.copySourceBtn.setAttribute( + 'title', + __('Switch to the source to copy it to the clipboard'), + ); this.copySourceBtn.classList.add('disabled'); } @@ -158,7 +162,7 @@ export default class BlobViewer { this.toggleCopyButtonState(); }) - .catch(() => new Flash('Error loading viewer')); + .catch(() => new Flash(__('Error loading viewer'))); } static loadViewer(viewerParam) { diff --git a/app/assets/javascripts/ide/ide_router.js b/app/assets/javascripts/ide/ide_router.js index 518a9cf7a0f..8c84b98a108 100644 --- a/app/assets/javascripts/ide/ide_router.js +++ b/app/assets/javascripts/ide/ide_router.js @@ -3,6 +3,7 @@ import VueRouter from 'vue-router'; import { joinPaths } from '~/lib/utils/url_utility'; import flash from '~/flash'; import store from './stores'; +import { __ } from '~/locale'; Vue.use(VueRouter); @@ -94,7 +95,7 @@ router.beforeEach((to, from, next) => { }) .catch(e => { flash( - 'Error while loading the project data. Please try again.', + __('Error while loading the project data. Please try again.'), 'alert', document, null, diff --git a/app/assets/javascripts/ide/stores/modules/file_templates/getters.js b/app/assets/javascripts/ide/stores/modules/file_templates/getters.js index 628babe6a01..f10891a8e5b 100644 --- a/app/assets/javascripts/ide/stores/modules/file_templates/getters.js +++ b/app/assets/javascripts/ide/stores/modules/file_templates/getters.js @@ -1,4 +1,5 @@ import { activityBarViews } from '../../../constants'; +import { __ } from '~/locale'; export const templateTypes = () => [ { @@ -10,11 +11,11 @@ export const templateTypes = () => [ key: 'gitignores', }, { - name: 'LICENSE', + name: __('LICENSE'), key: 'licenses', }, { - name: 'Dockerfile', + name: __('Dockerfile'), key: 'dockerfiles', }, ]; diff --git a/app/assets/javascripts/integrations/integration_settings_form.js b/app/assets/javascripts/integrations/integration_settings_form.js index 08b858305ab..a7746bb3a0b 100644 --- a/app/assets/javascripts/integrations/integration_settings_form.js +++ b/app/assets/javascripts/integrations/integration_settings_form.js @@ -1,6 +1,7 @@ import $ from 'jquery'; import axios from '../lib/utils/axios_utils'; import flash from '../flash'; +import { __ } from '~/locale'; export default class IntegrationSettingsForm { constructor(formSelector) { @@ -65,10 +66,10 @@ export default class IntegrationSettingsForm { * Toggle Submit button label based on Integration status and ability to test service */ toggleSubmitBtnLabel(serviceActive) { - let btnLabel = 'Save changes'; + let btnLabel = __('Save changes'); if (serviceActive && this.canTestService) { - btnLabel = 'Test settings and save changes'; + btnLabel = __('Test settings and save changes'); } this.$submitBtnLabel.text(btnLabel); @@ -105,7 +106,7 @@ export default class IntegrationSettingsForm { if (data.test_failed) { flashActions = { - title: 'Save anyway', + title: __('Save anyway'), clickHandler: e => { e.preventDefault(); this.$form.submit(); @@ -121,7 +122,7 @@ export default class IntegrationSettingsForm { this.toggleSubmitBtnState(false); }) .catch(() => { - flash('Something went wrong on our end.'); + flash(__('Something went wrong on our end.')); this.toggleSubmitBtnState(false); }); } diff --git a/app/assets/javascripts/projects/project_new.js b/app/assets/javascripts/projects/project_new.js index 2164e386fdb..ea82ff4e340 100644 --- a/app/assets/javascripts/projects/project_new.js +++ b/app/assets/javascripts/projects/project_new.js @@ -1,6 +1,7 @@ import $ from 'jquery'; import { addSelectOnFocusBehaviour } from '../lib/utils/common_utils'; import { slugifyWithHyphens } from '../lib/utils/text_utility'; +import { s__ } from '~/locale'; let hasUserDefinedProjectPath = false; @@ -114,71 +115,71 @@ const bindEvents = () => { const value = $(this).val(); const templates = { rails: { - text: 'Ruby on Rails', + text: s__('ProjectTemplates|Ruby on Rails'), icon: '.template-option .icon-rails', }, express: { - text: 'NodeJS Express', + text: s__('ProjectTemplates|NodeJS Express'), icon: '.template-option .icon-express', }, spring: { - text: 'Spring', + text: s__('ProjectTemplates|Spring'), icon: '.template-option .icon-spring', }, iosswift: { - text: 'iOS (Swift)', + text: s__('ProjectTemplates|iOS (Swift)'), icon: '.template-option svg.icon-gitlab', }, dotnetcore: { - text: '.NET Core', + text: s__('ProjectTemplates|.NET Core'), icon: '.template-option .icon-dotnet', }, android: { - text: 'Android', + text: s__('ProjectTemplates|Android'), icon: '.template-option svg.icon-android', }, gomicro: { - text: 'Go Micro', + text: s__('ProjectTemplates|Go Micro'), icon: '.template-option .icon-gomicro', }, hugo: { - text: 'Pages/Hugo', + text: s__('ProjectTemplates|Pages/Hugo'), icon: '.template-option .icon-hugo', }, jekyll: { - text: 'Pages/Jekyll', + text: s__('ProjectTemplates|Pages/Jekyll'), icon: '.template-option .icon-jekyll', }, plainhtml: { - text: 'Pages/Plain HTML', + text: s__('ProjectTemplates|Pages/Plain HTML'), icon: '.template-option .icon-plainhtml', }, gitbook: { - text: 'Pages/GitBook', + text: s__('ProjectTemplates|Pages/GitBook'), icon: '.template-option .icon-gitbook', }, hexo: { - text: 'Pages/Hexo', + text: s__('ProjectTemplates|Pages/Hexo'), icon: '.template-option .icon-hexo', }, nfhugo: { - text: 'Netlify/Hugo', + text: s__('ProjectTemplates|Netlify/Hugo'), icon: '.template-option .icon-netlify', }, nfjekyll: { - text: 'Netlify/Jekyll', + text: s__('ProjectTemplates|Netlify/Jekyll'), icon: '.template-option .icon-netlify', }, nfplainhtml: { - text: 'Netlify/Plain HTML', + text: s__('ProjectTemplates|Netlify/Plain HTML'), icon: '.template-option .icon-netlify', }, nfgitbook: { - text: 'Netlify/GitBook', + text: s__('ProjectTemplates|Netlify/GitBook'), icon: '.template-option .icon-netlify', }, nfhexo: { - text: 'Netlify/Hexo', + text: s__('ProjectTemplates|Netlify/Hexo'), icon: '.template-option .icon-netlify', }, }; diff --git a/app/assets/javascripts/protected_tags/protected_tag_access_dropdown.js b/app/assets/javascripts/protected_tags/protected_tag_access_dropdown.js index b803da798d5..def2f091947 100644 --- a/app/assets/javascripts/protected_tags/protected_tag_access_dropdown.js +++ b/app/assets/javascripts/protected_tags/protected_tag_access_dropdown.js @@ -1,3 +1,5 @@ +import { __ } from '~/locale'; + export default class ProtectedTagAccessDropdown { constructor(options) { this.options = options; @@ -15,7 +17,7 @@ export default class ProtectedTagAccessDropdown { if ($el.is('.is-active')) { return item.text; } - return 'Select'; + return __('Select'); }, clicked(options) { options.e.preventDefault(); diff --git a/app/assets/javascripts/protected_tags/protected_tag_create.js b/app/assets/javascripts/protected_tags/protected_tag_create.js index fddf2674cbb..03a5fe6b353 100644 --- a/app/assets/javascripts/protected_tags/protected_tag_create.js +++ b/app/assets/javascripts/protected_tags/protected_tag_create.js @@ -1,6 +1,7 @@ import $ from 'jquery'; import ProtectedTagAccessDropdown from './protected_tag_access_dropdown'; import CreateItemDropdown from '../create_item_dropdown'; +import { __ } from '~/locale'; export default class ProtectedTagCreate { constructor() { @@ -27,7 +28,7 @@ export default class ProtectedTagCreate { // Protected tag dropdown this.createItemDropdown = new CreateItemDropdown({ $dropdown: this.$form.find('.js-protected-tag-select'), - defaultToggleLabel: 'Protected Tag', + defaultToggleLabel: __('Protected Tag'), fieldName: 'protected_tag[name]', onSelect: this.onSelectCallback, getData: ProtectedTagCreate.getProtectedTags, diff --git a/app/assets/javascripts/protected_tags/protected_tag_edit.js b/app/assets/javascripts/protected_tags/protected_tag_edit.js index c52497e62f2..70bfd71abce 100644 --- a/app/assets/javascripts/protected_tags/protected_tag_edit.js +++ b/app/assets/javascripts/protected_tags/protected_tag_edit.js @@ -1,6 +1,7 @@ import flash from '../flash'; import axios from '../lib/utils/axios_utils'; import ProtectedTagAccessDropdown from './protected_tag_access_dropdown'; +import { __ } from '~/locale'; export default class ProtectedTagEdit { constructor(options) { @@ -47,7 +48,11 @@ export default class ProtectedTagEdit { .catch(() => { this.$allowedToCreateDropdownButton.enable(); - flash('Failed to update tag!', 'alert', document.querySelector('.js-protected-tags-list')); + flash( + __('Failed to update tag!'), + 'alert', + document.querySelector('.js-protected-tags-list'), + ); }); } } diff --git a/app/assets/javascripts/raven/index.js b/app/assets/javascripts/raven/index.js index edc2293915f..4dd0175e528 100644 --- a/app/assets/javascripts/raven/index.js +++ b/app/assets/javascripts/raven/index.js @@ -4,8 +4,11 @@ const index = function index() { RavenConfig.init({ sentryDsn: gon.sentry_dsn, currentUserId: gon.current_user_id, - whitelistUrls: [gon.gitlab_url], - isProduction: process.env.NODE_ENV, + whitelistUrls: + process.env.NODE_ENV === 'production' + ? [gon.gitlab_url] + : [gon.gitlab_url, 'webpack-internal://'], + environment: gon.sentry_environment, release: gon.revision, tags: { revision: gon.revision, diff --git a/app/assets/javascripts/raven/raven_config.js b/app/assets/javascripts/raven/raven_config.js index 338006ce2b9..b4a8c263954 100644 --- a/app/assets/javascripts/raven/raven_config.js +++ b/app/assets/javascripts/raven/raven_config.js @@ -61,7 +61,7 @@ const RavenConfig = { release: this.options.release, tags: this.options.tags, whitelistUrls: this.options.whitelistUrls, - environment: this.options.isProduction ? 'production' : 'development', + environment: this.options.environment, ignoreErrors: this.IGNORE_ERRORS, ignoreUrls: this.IGNORE_URLS, shouldSendCallback: this.shouldSendSample.bind(this), diff --git a/app/assets/javascripts/u2f/error.js b/app/assets/javascripts/u2f/error.js index 1a98564ff55..ca0fc0700ad 100644 --- a/app/assets/javascripts/u2f/error.js +++ b/app/assets/javascripts/u2f/error.js @@ -1,3 +1,5 @@ +import { __ } from '~/locale'; + export default class U2FError { constructor(errorCode, u2fFlowType) { this.errorCode = errorCode; @@ -8,15 +10,17 @@ export default class U2FError { message() { if (this.errorCode === window.u2f.ErrorCodes.BAD_REQUEST && this.httpsDisabled) { - return 'U2F only works with HTTPS-enabled websites. Contact your administrator for more details.'; + return __( + 'U2F only works with HTTPS-enabled websites. Contact your administrator for more details.', + ); } else if (this.errorCode === window.u2f.ErrorCodes.DEVICE_INELIGIBLE) { if (this.u2fFlowType === 'authenticate') { - return 'This device has not been registered with us.'; + return __('This device has not been registered with us.'); } if (this.u2fFlowType === 'register') { - return 'This device has already been registered with us.'; + return __('This device has already been registered with us.'); } } - return 'There was a problem communicating with your device.'; + return __('There was a problem communicating with your device.'); } } diff --git a/app/assets/javascripts/vue_shared/directives/tooltip.js b/app/assets/javascripts/vue_shared/directives/tooltip.js index 549d27e96d9..2d1f7a1cfd0 100644 --- a/app/assets/javascripts/vue_shared/directives/tooltip.js +++ b/app/assets/javascripts/vue_shared/directives/tooltip.js @@ -1,4 +1,5 @@ import $ from 'jquery'; +import '~/commons/bootstrap'; export default { bind(el) { diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 48f4d7a586d..e88c46144ef 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -36,10 +36,10 @@ class ProjectsController < Projects::ApplicationController # rubocop: disable CodeReuse/ActiveRecord def new - namespace = Namespace.find_by(id: params[:namespace_id]) if params[:namespace_id] - return access_denied! if namespace && !can?(current_user, :create_projects, namespace) + @namespace = Namespace.find_by(id: params[:namespace_id]) if params[:namespace_id] + return access_denied! if @namespace && !can?(current_user, :create_projects, @namespace) - @project = Project.new(namespace_id: namespace&.id) + @project = Project.new(namespace_id: @namespace&.id) end # rubocop: enable CodeReuse/ActiveRecord diff --git a/app/graphql/resolvers/full_path_resolver.rb b/app/graphql/resolvers/full_path_resolver.rb index 0f1a64b6c58..972f318c806 100644 --- a/app/graphql/resolvers/full_path_resolver.rb +++ b/app/graphql/resolvers/full_path_resolver.rb @@ -7,14 +7,14 @@ module Resolvers prepended do argument :full_path, GraphQL::ID_TYPE, required: true, - description: 'The full path of the project or namespace, e.g., "gitlab-org/gitlab-ce"' + description: 'The full path of the project, group or namespace, e.g., "gitlab-org/gitlab-ce"' end def model_by_full_path(model, full_path) BatchLoader.for(full_path).batch(key: model) do |full_paths, loader, args| # `with_route` avoids an N+1 calculating full_path - args[:key].where_full_path_in(full_paths).with_route.each do |project| - loader.call(project.full_path, project) + args[:key].where_full_path_in(full_paths).with_route.each do |model_instance| + loader.call(model_instance.full_path, model_instance) end end end diff --git a/app/graphql/resolvers/group_resolver.rb b/app/graphql/resolvers/group_resolver.rb new file mode 100644 index 00000000000..4260e18829e --- /dev/null +++ b/app/graphql/resolvers/group_resolver.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module Resolvers + class GroupResolver < BaseResolver + prepend FullPathResolver + + type Types::GroupType, null: true + + def resolve(full_path:) + model_by_full_path(Group, full_path) + end + end +end diff --git a/app/graphql/types/group_type.rb b/app/graphql/types/group_type.rb new file mode 100644 index 00000000000..a2d615ee732 --- /dev/null +++ b/app/graphql/types/group_type.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module Types + class GroupType < NamespaceType + graphql_name 'Group' + + authorize :read_group + + expose_permissions Types::PermissionTypes::Group + + field :web_url, GraphQL::STRING_TYPE, null: true + + field :avatar_url, GraphQL::STRING_TYPE, null: true, resolve: -> (group, args, ctx) do + group.avatar_url(only_path: false) + end + + if ::Group.supports_nested_objects? + field :parent, GroupType, null: true + end + end +end diff --git a/app/graphql/types/namespace_type.rb b/app/graphql/types/namespace_type.rb new file mode 100644 index 00000000000..36d8ee8c878 --- /dev/null +++ b/app/graphql/types/namespace_type.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Types + class NamespaceType < BaseObject + graphql_name 'Namespace' + + field :id, GraphQL::ID_TYPE, null: false + + field :name, GraphQL::STRING_TYPE, null: false + field :path, GraphQL::STRING_TYPE, null: false + field :full_name, GraphQL::STRING_TYPE, null: false + field :full_path, GraphQL::ID_TYPE, null: false + + field :description, GraphQL::STRING_TYPE, null: true + field :visibility, GraphQL::STRING_TYPE, null: true + field :lfs_enabled, GraphQL::BOOLEAN_TYPE, null: true, method: :lfs_enabled? + field :request_access_enabled, GraphQL::BOOLEAN_TYPE, null: true + end +end diff --git a/app/graphql/types/permission_types/group.rb b/app/graphql/types/permission_types/group.rb new file mode 100644 index 00000000000..29833993ce6 --- /dev/null +++ b/app/graphql/types/permission_types/group.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Types + module PermissionTypes + class Group < BasePermissionType + graphql_name 'GroupPermissions' + + abilities :read_group + end + end +end diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb index fbb4eddd13c..baea6658e05 100644 --- a/app/graphql/types/project_type.rb +++ b/app/graphql/types/project_type.rb @@ -66,6 +66,9 @@ module Types field :only_allow_merge_if_all_discussions_are_resolved, GraphQL::BOOLEAN_TYPE, null: true field :printing_merge_request_link_enabled, GraphQL::BOOLEAN_TYPE, null: true + field :namespace, Types::NamespaceType, null: false + field :group, Types::GroupType, null: true + field :merge_requests, Types::MergeRequestType.connection_type, null: true, diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb index 0f655ab9d03..40d7de1a49a 100644 --- a/app/graphql/types/query_type.rb +++ b/app/graphql/types/query_type.rb @@ -9,6 +9,11 @@ module Types resolver: Resolvers::ProjectResolver, description: "Find a project" + field :group, Types::GroupType, + null: true, + resolver: Resolvers::GroupResolver, + description: "Find a group" + field :metadata, Types::MetadataType, null: true, resolver: Resolvers::MetadataResolver, diff --git a/app/models/application_setting_implementation.rb b/app/models/application_setting_implementation.rb index b413ffddb9d..557215ff4dc 100644 --- a/app/models/application_setting_implementation.rb +++ b/app/models/application_setting_implementation.rb @@ -183,6 +183,22 @@ module ApplicationSettingImplementation clientside_sentry_dsn.strip! if clientside_sentry_dsn.present? end + def sentry_enabled + Gitlab.config.sentry.enabled || read_attribute(:sentry_enabled) + end + + def sentry_dsn + Gitlab.config.sentry.dsn || read_attribute(:sentry_dsn) + end + + def clientside_sentry_enabled + Gitlab.config.sentry.enabled || read_attribute(:clientside_sentry_enabled) + end + + def clientside_sentry_dsn + Gitlab.config.sentry.dsn || read_attribute(:clientside_sentry_dsn) + end + def performance_bar_allowed_group Group.find_by_id(performance_bar_allowed_group_id) end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index a5b62659b24..c2a1487fc6e 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -1054,6 +1054,16 @@ class MergeRequest < ApplicationRecord @environments[current_user] end + ## + # This method is for looking for active environments which created via pipelines for merge requests. + # Since deployments run on a merge request ref (e.g. `refs/merge-requests/:iid/head`), + # we cannot look up environments with source branch name. + def environments + return Environment.none unless actual_head_pipeline&.triggered_by_merge_request? + + actual_head_pipeline.environments + end + def state_human_name if merged? "Merged" diff --git a/app/services/ci/stop_environments_service.rb b/app/services/ci/stop_environments_service.rb index 973ae5ce5aa..d9a800791f2 100644 --- a/app/services/ci/stop_environments_service.rb +++ b/app/services/ci/stop_environments_service.rb @@ -9,12 +9,11 @@ module Ci return unless @ref.present? - environments.each do |environment| - next unless environment.stop_action_available? - next unless can?(current_user, :stop_environment, environment) + environments.each { |environment| stop(environment) } + end - environment.stop_with_action!(current_user) - end + def execute_for_merge_request(merge_request) + merge_request.environments.each { |environment| stop(environment) } end private @@ -24,5 +23,12 @@ module Ci .new(project, current_user, ref: @ref, recently_updated: true) .execute end + + def stop(environment) + return unless environment.stop_action_available? + return unless can?(current_user, :stop_environment, environment) + + environment.stop_with_action!(current_user) + end end end diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb index b8334a87f6d..a9dd26c02ad 100644 --- a/app/services/merge_requests/base_service.rb +++ b/app/services/merge_requests/base_service.rb @@ -24,6 +24,11 @@ module MergeRequests end end + def cleanup_environments(merge_request) + Ci::StopEnvironmentsService.new(merge_request.source_project, current_user) + .execute_for_merge_request(merge_request) + end + private def handle_wip_event(merge_request) diff --git a/app/services/merge_requests/close_service.rb b/app/services/merge_requests/close_service.rb index 04527bb9713..e77051bb1c9 100644 --- a/app/services/merge_requests/close_service.rb +++ b/app/services/merge_requests/close_service.rb @@ -17,6 +17,7 @@ module MergeRequests execute_hooks(merge_request, 'close') invalidate_cache_counts(merge_request, users: merge_request.assignees) merge_request.update_project_counter_caches + cleanup_environments(merge_request) end merge_request diff --git a/app/services/merge_requests/post_merge_service.rb b/app/services/merge_requests/post_merge_service.rb index f26e3bee06f..c13f7dd5088 100644 --- a/app/services/merge_requests/post_merge_service.rb +++ b/app/services/merge_requests/post_merge_service.rb @@ -18,6 +18,7 @@ module MergeRequests invalidate_cache_counts(merge_request, users: merge_request.assignees) merge_request.update_project_counter_caches delete_non_latest_diffs(merge_request) + cleanup_environments(merge_request) end private diff --git a/app/views/admin/application_settings/_logging.html.haml b/app/views/admin/application_settings/_logging.html.haml index 41b787515b5..006ebf09576 100644 --- a/app/views/admin/application_settings/_logging.html.haml +++ b/app/views/admin/application_settings/_logging.html.haml @@ -1,6 +1,13 @@ = form_for @application_setting, url: admin_application_settings_path(anchor: 'js-logging-settings'), html: { class: 'fieldset-form' } do |f| = form_errors(@application_setting) + %p + %strong + NOTE: + These settings will be removed from the UI in a GitLab 12.0 release and made available within gitlab.yml. + The specific client side DSN setting is already handled as a component from a Sentry perspective and will be removed. + In addition, you will be able to define a Sentry Environment to differentiate between multiple deployments. For example, development, staging, and production. + %fieldset .form-group .form-check diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml index 0d8d7123a01..9293aa1b309 100644 --- a/app/views/projects/issues/_issue.html.haml +++ b/app/views/projects/issues/_issue.html.haml @@ -39,6 +39,8 @@ - presented_labels_sorted_by_title(issue.labels, issue.project).each do |label| = link_to_label(label, css_class: 'label-link') + = render_if_exists "projects/issues/issue_weight", issue: issue + .issuable-meta %ul.controls - if issue.closed? diff --git a/changelogs/unreleased/bw-add-graphql-groups.yml b/changelogs/unreleased/bw-add-graphql-groups.yml new file mode 100644 index 00000000000..f72ee1cf2b7 --- /dev/null +++ b/changelogs/unreleased/bw-add-graphql-groups.yml @@ -0,0 +1,5 @@ +--- +title: Add initial GraphQL query for Groups +merge_request: 27492 +author: +type: added diff --git a/changelogs/unreleased/feat-sentry-environment.yml b/changelogs/unreleased/feat-sentry-environment.yml new file mode 100644 index 00000000000..44ea19375f8 --- /dev/null +++ b/changelogs/unreleased/feat-sentry-environment.yml @@ -0,0 +1,5 @@ +--- +title: Allow Sentry configuration to be passed on gitlab.yml +merge_request: 27091 +author: Roger Meier +type: added diff --git a/changelogs/unreleased/fix-environment-on-stop-not-work.yml b/changelogs/unreleased/fix-environment-on-stop-not-work.yml new file mode 100644 index 00000000000..72e58b26c4d --- /dev/null +++ b/changelogs/unreleased/fix-environment-on-stop-not-work.yml @@ -0,0 +1,5 @@ +--- +title: "`on_stop` is not automatically triggered with pipelines for merge requests" +merge_request: 27618 +author: +type: fixed diff --git a/changelogs/unreleased/refactor-58830-migrate-sidebar-spec-to-jest.yml b/changelogs/unreleased/refactor-58830-migrate-sidebar-spec-to-jest.yml new file mode 100644 index 00000000000..20a4be8c9ad --- /dev/null +++ b/changelogs/unreleased/refactor-58830-migrate-sidebar-spec-to-jest.yml @@ -0,0 +1,5 @@ +--- +title: 'Refactored Karma spec files to Jest' +merge_request: 27688 +author: Martin Hobert +type: other diff --git a/changelogs/unreleased/secure-disallow-read-user-scope-to-read-project-events.yml b/changelogs/unreleased/secure-disallow-read-user-scope-to-read-project-events.yml deleted file mode 100644 index 4a91bfa8827..00000000000 --- a/changelogs/unreleased/secure-disallow-read-user-scope-to-read-project-events.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Allow to see project events only with api scope token -merge_request: -author: -type: security diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index bdac5b2a6a1..06530194907 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -315,6 +315,13 @@ production: &base # path: shared/registry # issuer: gitlab-issuer + + ## Error Reporting and Logging with Sentry + sentry: + # enabled: false + # dsn: https://<key>@sentry.io/<project> + # environment: 'production' # e.g. development, staging, production + # # 2. GitLab CI settings # ========================== diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index e9b36873d75..154de3bc1b0 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -136,6 +136,8 @@ Settings.gitlab['ssh_host'] ||= Settings.gitlab.host Settings.gitlab['https'] = false if Settings.gitlab['https'].nil? Settings.gitlab['port'] ||= ENV['GITLAB_PORT'] || (Settings.gitlab.https ? 443 : 80) Settings.gitlab['relative_url_root'] ||= ENV['RAILS_RELATIVE_URL_ROOT'] || '' +# / is not a valid relative URL root +Settings.gitlab['relative_url_root'] = '' if Settings.gitlab['relative_url_root'] == '/' Settings.gitlab['protocol'] ||= Settings.gitlab.https ? "https" : "http" Settings.gitlab['email_enabled'] ||= true if Settings.gitlab['email_enabled'].nil? Settings.gitlab['email_from'] ||= ENV['GITLAB_EMAIL_FROM'] || "gitlab@#{Settings.gitlab.host}" @@ -216,6 +218,14 @@ Settings.registry['host_port'] ||= [Settings.registry['host'], Settings.regi Settings.registry['path'] = Settings.absolute(Settings.registry['path'] || File.join(Settings.shared['path'], 'registry')) # +# Error Reporting and Logging with Sentry +# +Settings['sentry'] ||= Settingslogic.new({}) +Settings.sentry['enabled'] ||= false +Settings.sentry['dsn'] ||= nil +Settings.sentry['environment'] ||= nil + +# # Pages # Settings['pages'] ||= Settingslogic.new({}) diff --git a/config/initializers/sentry.rb b/config/initializers/sentry.rb index 680cfa6f0ed..e5589ce0ad1 100644 --- a/config/initializers/sentry.rb +++ b/config/initializers/sentry.rb @@ -14,6 +14,7 @@ def configure_sentry Raven.configure do |config| config.dsn = Gitlab::CurrentSettings.current_application_settings.sentry_dsn config.release = Gitlab.revision + config.current_environment = Gitlab.config.sentry.environment.presence # Sanitize fields based on those sanitized from Rails. config.sanitize_fields = Rails.application.config.filter_parameters.map(&:to_s) diff --git a/doc/README.md b/doc/README.md index 14a1eeffda0..dd4909ce303 100644 --- a/doc/README.md +++ b/doc/README.md @@ -218,12 +218,12 @@ scales to run your tests faster. The following documentation relates to the DevOps **Verify** stage: -| Verify Topics | Description | -|:---------------------------------------------------|:-----------------------------------------------------------------------------| -| [GitLab CI/CD](ci/README.md) | Explore the features and capabilities of Continuous Integration with GitLab. | -| [JUnit test reports](ci/junit_test_reports.md) | Display JUnit test reports on merge requests. | -| [Pipeline Graphs](ci/pipelines.md#visualizing-pipelines) | Visualize builds. | -| [Review Apps](ci/review_apps/index.md) | Preview changes to your application right from a merge request. | +| Verify Topics | Description | +|:---------------------------------------------------------|:-----------------------------------------------------------------------------| +| [GitLab CI/CD](ci/README.md) | Explore the features and capabilities of Continuous Integration with GitLab. | +| [JUnit test reports](ci/junit_test_reports.md) | Display JUnit test reports on merge requests. | +| [Pipeline Graphs](ci/pipelines.md#visualizing-pipelines) | Visualize builds. | +| [Review Apps](ci/review_apps/index.md) | Preview changes to your application right from a merge request. | <div align="right"> <a type="button" class="btn btn-default" href="#overview"> @@ -288,7 +288,7 @@ The following documentation relates to the DevOps **Configure** stage: | [GitLab ChatOps](ci/chatops/README.md) | Interact with CI/CD jobs through chat services. | | [Installing Applications](user/project/clusters/index.md#installing-applications) | Deploy Helm, Ingress, and Prometheus on Kubernetes. | | [Mattermost slash commands](user/project/integrations/mattermost_slash_commands.md) | Enable and use slash commands from within Mattermost. | -| [Protected variables](ci/variables/README.md#protected-environment-variables) | Restrict variables to protected branches and tags. | +| [Protected variables](ci/variables/README.md#protected-environment-variables) | Restrict variables to protected branches and tags. | | [Serverless](user/project/clusters/serverless/index.md) | Run serverless workloads on Kubernetes. | | [Slack slash commands](user/project/integrations/slack_slash_commands.md) | Enable and use slash commands from within Slack. | @@ -418,7 +418,7 @@ We have the following documentation to rapidly uplift your GitLab knowledge: | Topic | Description | |:-----------------------------------------------------------------------------------------------------------------------|:---------------------------------------------------------------| -| [GitLab Basics](gitlab-basics/README.md) | Start working on the command line and with GitLab. | +| [GitLab basics guides](gitlab-basics/README.md) | Start working on the command line and with GitLab. | | [GitLab Workflow](workflow/README.md) and [overview](https://about.gitlab.com/2016/10/25/gitlab-workflow-an-overview/) | Enhance your workflow with the best of GitLab Workflow. | | [Get started with GitLab CI/CD](ci/quick_start/README.md) | Quickly implement GitLab CI/CD. | | [Auto DevOps](topics/autodevops/index.md) | Learn more about GitLab's Auto DevOps. | diff --git a/doc/administration/container_registry.md b/doc/administration/container_registry.md index 05beb724d4d..48f599fa7e6 100644 --- a/doc/administration/container_registry.md +++ b/doc/administration/container_registry.md @@ -388,6 +388,9 @@ desired. **Omnibus GitLab installations** +> **Note:** +`regionendpoint` is only required when configuring an S3 compatible service such as Minio, by entering a URL such as http://127.0.0.1:9000 + To configure the storage driver in Omnibus: 1. Edit `/etc/gitlab/gitlab.rb`: @@ -398,7 +401,8 @@ To configure the storage driver in Omnibus: 'accesskey' => 's3-access-key', 'secretkey' => 's3-secret-key-for-access-key', 'bucket' => 'your-s3-bucket', - 'region' => 'your-s3-region' + 'region' => 'your-s3-region', + 'regionendpoint' => 'your-s3-regionendpoint' } } ``` @@ -421,6 +425,7 @@ storage: secretkey: 'secret123' bucket: 'gitlab-registry-bucket-AKIAKIAKI' region: 'your-s3-region' + regionendpoint: 'your-s3-regionendpoint' cache: blobdescriptor: inmemory delete: diff --git a/doc/administration/custom_hooks.md b/doc/administration/custom_hooks.md index 28afaf84f5a..f318cb38ef0 100644 --- a/doc/administration/custom_hooks.md +++ b/doc/administration/custom_hooks.md @@ -1,6 +1,7 @@ -# Custom Git Hooks +# Custom server-side Git hooks -> **Note:** Custom Git hooks must be configured on the filesystem of the GitLab +NOTE: **Note:** +Custom Git hooks must be configured on the filesystem of the GitLab server. Only GitLab server administrators will be able to complete these tasks. Please explore [webhooks] and [CI] as an option if you do not have filesystem access. For a user configurable Git hook interface, see @@ -14,15 +15,14 @@ See [Git SCM Server-Side Hooks][hooks] for more information about each hook type As of gitlab-shell version 2.2.0 (which requires GitLab 7.5+), GitLab administrators can add custom git hooks to any GitLab project. -## Setup +## Create a custom Git hook for a repository -Normally, Git hooks are placed in the repository or project's `hooks` directory. -GitLab creates a symlink from each project's `hooks` directory to the -gitlab-shell `hooks` directory for ease of maintenance between gitlab-shell -upgrades. As such, custom hooks are implemented a little differently. Behavior -is exactly the same once the hook is created, though. - -Follow the steps below to set up a custom hook: +Server-side Git hooks are typically placed in the repository's `hooks` +subdirectory. In GitLab, hook directories are are symlinked to the gitlab-shell +`hooks` directory for ease of maintenance between gitlab-shell upgrades. +Custom hooks are implemented differently, but the behavior is exactly the same +once the hook is created. Follow the steps below to set up a custom hook for a +repository: 1. Pick a project that needs a custom Git hook. 1. On the GitLab server, navigate to the project's repository directory. @@ -42,33 +42,56 @@ Follow the steps below to set up a custom hook: That's it! Assuming the hook code is properly implemented the hook will fire as appropriate. +## Set a global Git hook for all repositories + +To create a Git hook that applies to all of your repositories in +your instance, set a global Git hook. Since all the repositories' `hooks` +directories are symlinked to gitlab-shell's `hooks` directory, adding any hook +to the gitlab-shell `hooks` directory will also apply it to all repositories. Follow +the steps below to properly set up a custom hook all for repositories: + +1. On the GitLab server, navigate to the configured custom hook directory. The + default is in the gitlab-shell directory. The gitlab-shell `hook` directory + for an installation from source the path is usually + `/home/git/gitlab-shell/hooks`. For Omnibus installs the path is usually + `/opt/gitlab/embedded/service/gitlab-shell/hooks`. + To look in a different directory for the global custom hooks, + set `custom_hooks_dir` in the gitlab-shell config. For + Omnibus installations, this can be set in `gitlab.rb`; and in source + installations, this can be set in `gitlab-shell/config.yml`. +1. Create a new directory in this location. Depending on your hook, it will be + either a `pre-receive.d`, `post-receive.d`, or `update.d` directory. +1. Inside this new directory, add your hook. Hooks can be + in any language. Ensure the 'shebang' at the top properly reflects the language + type. For example, if the script is in Ruby the shebang will probably be + `#!/usr/bin/env ruby`. +1. Make the hook file executable and make sure it's owned by Git. + +Now test the hook to see that it's functioning properly. + ## Chained hooks support > [Introduced][93] in GitLab Shell 4.1.0 and GitLab 8.15. -Hooks can be also placed in `hooks/<hook_name>.d` (global) or -`custom_hooks/<hook_name>.d` (per project) directories supporting chained +Hooks can be also global or be set per project directories and support a chained execution of the hooks. -NOTE: **Note:** `<hook_name>.d` would need to be either `pre-receive.d`, +NOTE: **Note:** +`<hook_name>.d` would need to be either `pre-receive.d`, `post-receive.d`, or `update.d` to work properly. Any other names will be ignored. -To look in a different directory for the global custom hooks (those in -`hooks/<hook_name.d>`), set `custom_hooks_dir` in gitlab-shell config. For -Omnibus installations, this can be set in `gitlab.rb`; and in source -installations, this can be set in `gitlab-shell/config.yml`. +NOTE: **Note:** +Files in `.d` directories need to be executable and not match the backup file +pattern (`*~`). The hooks are searched and executed in this order: 1. `gitlab-shell/hooks` directory as known to Gitaly -1. `<project>.git/hooks/<hook_name>` - executed by `git` itself, this is `gitlab-shell/hooks/<hook_name>` +1. `<project>.git/hooks/<hook_name>` - executed by `git` itself, this is symlinked to `gitlab-shell/hooks/<hook_name>` 1. `<project>.git/custom_hooks/<hook_name>` - per project hook (this is already existing behavior) 1. `<project>.git/custom_hooks/<hook_name>.d/*` - per project hooks 1. `<project>.git/hooks/<hook_name>.d/*` OR `<custom_hooks_dir>/<hook_name.d>/*` - global hooks: all executable files (minus editor backup files) -Files in `.d` directories need to be executable and not match the backup file -pattern (`*~`). - The hooks of the same type are executed in order and execution stops on the first script exiting with a non-zero value. diff --git a/doc/api/graphql/index.md b/doc/api/graphql/index.md index ec48bf4940b..cf02bbd9c92 100644 --- a/doc/api/graphql/index.md +++ b/doc/api/graphql/index.md @@ -29,7 +29,11 @@ curl --data "value=100" --header "PRIVATE-TOKEN: <your_access_token>" https://gi ## Available queries -A first iteration of a GraphQL API includes a query for: `project`. Within a project it is also possible to fetch a `mergeRequest` by IID. +A first iteration of a GraphQL API includes the following queries + +1. `project` : Within a project it is also possible to fetch a `mergeRequest` by IID. + +1. `group` : Only basic group information is currently supported. ## GraphiQL diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index ed4b6281acc..7992af15448 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -93,6 +93,14 @@ Parameters: "avatar_url": null, "web_url" : "https://gitlab.example.com/admin" }, + "assignees": [{ + "name": "Miss Monserrate Beier", + "username": "axel.block", + "id": 12, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/46f6f7dc858ada7be1853f7fb96e81da?s=80&d=identicon", + "web_url": "https://gitlab.example.com/axel.block" + }], "source_project_id": 2, "target_project_id": 3, "labels": [ @@ -227,6 +235,14 @@ Parameters: "avatar_url": null, "web_url" : "https://gitlab.example.com/admin" }, + "assignees": [{ + "name": "Miss Monserrate Beier", + "username": "axel.block", + "id": 12, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/46f6f7dc858ada7be1853f7fb96e81da?s=80&d=identicon", + "web_url": "https://gitlab.example.com/axel.block" + }], "source_project_id": 2, "target_project_id": 3, "labels": [ @@ -351,6 +367,14 @@ Parameters: "avatar_url": null, "web_url" : "https://gitlab.example.com/admin" }, + "assignees": [{ + "name": "Miss Monserrate Beier", + "username": "axel.block", + "id": 12, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/46f6f7dc858ada7be1853f7fb96e81da?s=80&d=identicon", + "web_url": "https://gitlab.example.com/axel.block" + }], "source_project_id": 2, "target_project_id": 3, "labels": [ @@ -445,6 +469,14 @@ Parameters: "avatar_url": null, "web_url" : "https://gitlab.example.com/admin" }, + "assignees": [{ + "name": "Miss Monserrate Beier", + "username": "axel.block", + "id": 12, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/46f6f7dc858ada7be1853f7fb96e81da?s=80&d=identicon", + "web_url": "https://gitlab.example.com/axel.block" + }], "source_project_id": 2, "target_project_id": 3, "labels": [ @@ -629,6 +661,14 @@ Parameters: "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40&d=identicon", "web_url" : "https://gitlab.example.com/root" }, + "assignees": [{ + "name": "Miss Monserrate Beier", + "username": "axel.block", + "id": 12, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/46f6f7dc858ada7be1853f7fb96e81da?s=80&d=identicon", + "web_url": "https://gitlab.example.com/axel.block" + }], "source_project_id": 4, "target_project_id": 4, "labels": [ ], @@ -718,6 +758,7 @@ POST /projects/:id/merge_requests | `target_branch` | string | yes | The target branch | | `title` | string | yes | Title of MR | | `assignee_id` | integer | no | Assignee user ID | +| `assignee_ids` | Array[integer] | no | The ID of the user(s) to assign the MR to. Set to `0` or provide an empty value to unassign all assignees. | | `description` | string | no | Description of MR | | `target_project_id` | integer | no | The target project (numeric id) | | `labels` | string | no | Labels for MR as a comma-separated list | @@ -843,6 +884,7 @@ PUT /projects/:id/merge_requests/:merge_request_iid | `target_branch` | string | no | The target branch | | `title` | string | no | Title of MR | | `assignee_id` | integer | no | The ID of the user to assign the merge request to. Set to `0` or provide an empty value to unassign all assignees. | +| `assignee_ids` | Array[integer] | no | The ID of the user(s) to assign the MR to. Set to `0` or provide an empty value to unassign all assignees. | | `milestone_id` | integer | no | The global ID of a milestone to assign the merge request to. Set to `0` or provide an empty value to unassign a milestone.| | `labels` | string | no | Comma-separated label names for a merge request. Set to an empty string to unassign all labels. | | `description` | string | no | Description of MR | @@ -885,6 +927,14 @@ Must include at least one non-required attribute from above. "avatar_url": null, "web_url" : "https://gitlab.example.com/admin" }, + "assignees": [{ + "name": "Miss Monserrate Beier", + "username": "axel.block", + "id": 12, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/46f6f7dc858ada7be1853f7fb96e81da?s=80&d=identicon", + "web_url": "https://gitlab.example.com/axel.block" + }], "source_project_id": 2, "target_project_id": 3, "labels": [ @@ -1030,6 +1080,14 @@ Parameters: "avatar_url": null, "web_url" : "https://gitlab.example.com/admin" }, + "assignees": [{ + "name": "Miss Monserrate Beier", + "username": "axel.block", + "id": 12, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/46f6f7dc858ada7be1853f7fb96e81da?s=80&d=identicon", + "web_url": "https://gitlab.example.com/axel.block" + }], "source_project_id": 2, "target_project_id": 3, "labels": [ @@ -1180,6 +1238,14 @@ Parameters: "avatar_url": null, "web_url" : "https://gitlab.example.com/admin" }, + "assignees": [{ + "name": "Miss Monserrate Beier", + "username": "axel.block", + "id": 12, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/46f6f7dc858ada7be1853f7fb96e81da?s=80&d=identicon", + "web_url": "https://gitlab.example.com/axel.block" + }], "source_project_id": 2, "target_project_id": 3, "labels": [ @@ -1436,6 +1502,14 @@ Example response: "avatar_url": null, "web_url" : "https://gitlab.example.com/admin" }, + "assignees": [{ + "name": "Miss Monserrate Beier", + "username": "axel.block", + "id": 12, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/46f6f7dc858ada7be1853f7fb96e81da?s=80&d=identicon", + "web_url": "https://gitlab.example.com/axel.block" + }], "source_project_id": 2, "target_project_id": 3, "labels": [ @@ -1557,6 +1631,14 @@ Example response: "avatar_url": null, "web_url" : "https://gitlab.example.com/admin" }, + "assignees": [{ + "name": "Miss Monserrate Beier", + "username": "axel.block", + "id": 12, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/46f6f7dc858ada7be1853f7fb96e81da?s=80&d=identicon", + "web_url": "https://gitlab.example.com/axel.block" + }], "source_project_id": 2, "target_project_id": 3, "labels": [ @@ -1698,6 +1780,14 @@ Example response: "avatar_url": "http://www.gravatar.com/avatar/733005fcd7e6df12d2d8580171ccb966?s=80&d=identicon", "web_url": "https://gitlab.example.com/barrett.krajcik" }, + "assignees": [{ + "name": "Miss Monserrate Beier", + "username": "axel.block", + "id": 12, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/46f6f7dc858ada7be1853f7fb96e81da?s=80&d=identicon", + "web_url": "https://gitlab.example.com/axel.block" + }], "source_project_id": 3, "target_project_id": 3, "labels": [], diff --git a/doc/gitlab-basics/README.md b/doc/gitlab-basics/README.md index 4e15f7cfd49..aa008d6f768 100644 --- a/doc/gitlab-basics/README.md +++ b/doc/gitlab-basics/README.md @@ -2,18 +2,34 @@ comments: false --- -# GitLab basics - -Step-by-step guides on the basics of working with Git and GitLab. - -- [Command line basics](command-line-commands.md) -- [Start using Git on the command line](start-using-git.md) -- [Create and add your SSH Keys](create-your-ssh-keys.md) -- [Create a project](create-project.md) -- [Create a group](../user/group/index.md#create-a-new-group) -- [Create a branch](create-branch.md) -- [Fork a project](fork-project.md) -- [Add a file](add-file.md) -- [Add an image](add-image.md) -- [Create an issue](../user/project/issues/create_new_issue.md) -- [Create a merge request](add-merge-request.md) +# GitLab basics guides + +This section provides resources to help you start with GitLab by focusing on basic functionality. + +This documentation is split into the following groups: + +- [GitLab-specific functionality](#gitlab-basics), for basic GitLab features. +- [General Git functionality](#git-basics), for working with Git in conjunction with GitLab. + +## GitLab basics + +The following are guides to basic GitLab functionality: + +- [Create and add your SSH Keys](create-your-ssh-keys.md), for enabling Git over SSH. +- [Create a project](create-project.md), to start using GitLab. +- [Create a group](../user/group/index.md#create-a-new-group), to combine and administer projects together. +- [Create a branch](create-branch.md), to make changes to files stored in a project's repository. +- [Fork a project](fork-project.md), to duplicate projects so they can be worked on in parallel. +- [Add a file](add-file.md), to add new files to a project's repository. +- [Add an image](add-image.md), to add new images to a project's repository. +- [Create an issue](../user/project/issues/create_new_issue.md), to start collaborating within a project. +- [Create a merge request](add-merge-request.md), to request changes made in a branch be merged into a project's repository. + +## Git basics + +If you're unfamiliar with the command line, these resources will help: + +- [Command line basics](command-line-commands.md), for those unfamiliar with the command line interface. +- [Start using Git on the command line](start-using-git.md), for some simple Git commands. + +More Git resources are available at GitLab's [Git documentation](../topics/git/index.md). diff --git a/doc/user/admin_area/index.md b/doc/user/admin_area/index.md index 5e67cb0ef16..f924ff8dfde 100644 --- a/doc/user/admin_area/index.md +++ b/doc/user/admin_area/index.md @@ -16,7 +16,7 @@ The Admin Area is made up of the following sections: | Section | Description | |:------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------| -| Overview | View your GitLab [Dashboard](#admin-dashboard), and maintain projects, users, groups, jobs, runners, and Gitaly servers. | +| Overview | View your GitLab [Dashboard](#admin-dashboard), and administer [projects](#administer-projects), users, groups, jobs, runners, and Gitaly servers. | | Monitoring | View GitLab system information, and information on background jobs, logs, [health checks](monitoring/health_check.md), request profiles, and audit logs. | | Messages | Send and manage [broadcast messages](broadcast_messages.md) for your users. | | System Hooks | Configure [system hooks](../../system_hooks/system_hooks.md) for many events. | @@ -46,4 +46,28 @@ The Dashboard is the default view of the Admin Area, and is made up of the follo | Groups | The total number of groups, up to 10 of the latest groups, and the option of creating a new group. | | Statistics | Totals of all elements of the GitLab instance. | | Features | All features available on the GitLab instance. Enabled features are marked with a green circle icon, and disabled features are marked with a power icon. | -| Components | The major components of GitLab and the version number of each. A link to the Gitaly Servers is also included. |
\ No newline at end of file +| Components | The major components of GitLab and the version number of each. A link to the Gitaly Servers is also included. | + +## Administer Projects + +You can administer all projects in the GitLab instance from the Admin Area's Projects page. + +To access the Projects page, go to **Admin Area > Overview > Projects**. + +Click the **All**, **Private**, **Internal**, or **Public** tab to list only projects of that +criteria. + +By default, all projects are listed, in reverse order of when they were last updated. For each +project, the name, namespace, description, and size is listed, also options to **Edit** or +**Delete** it. + +Sort projects by **Name**, **Last created**, **Oldest created**, **Last updated**, **Oldest +updated**, **Owner**, and choose to hide or show archived projects. + +In the **Filter by name** field, type the project name you want to find, and GitLab will filter +them as you type. + +Select from the **Namespace** dropdown to filter only projects in that namespace. + +You can combine the filter options. For example, click the **Public** tab, and enter `score` in +the **Filter by name...** input box to list only public projects with `score` in their name.
\ No newline at end of file diff --git a/doc/user/project/integrations/custom_issue_tracker.md b/doc/user/project/integrations/custom_issue_tracker.md index 6fc083170b6..23f1ce7a15a 100644 --- a/doc/user/project/integrations/custom_issue_tracker.md +++ b/doc/user/project/integrations/custom_issue_tracker.md @@ -7,9 +7,9 @@ in the table below. | Field | Description | | ----- | ----------- | -| `title` | A title for the issue tracker (to differentiate between instances, for example) | +| `title` | A title for the issue tracker (to differentiate between instances, for example). | | `description` | A name for the issue tracker (to differentiate between instances, for example) | -| `project_url` | Currently unused. Will be changed in a future release. | +| `project_url` | The URL to the project in the custom issue tracker. | | `issues_url` | The URL to the issue in the issue tracker project that is linked to this GitLab project. Note that the `issues_url` requires `:id` in the URL. This ID is used by GitLab as a placeholder to replace the issue number. For example, `https://customissuetracker.com/project-name/:id`. | | `new_issue_url` | Currently unused. Will be changed in a future release. | diff --git a/doc/user/project/merge_requests/img/multiple_assignees_for_merge_requests_sidebar.png b/doc/user/project/merge_requests/img/multiple_assignees_for_merge_requests_sidebar.png Binary files differnew file mode 100644 index 00000000000..9ae6e350798 --- /dev/null +++ b/doc/user/project/merge_requests/img/multiple_assignees_for_merge_requests_sidebar.png diff --git a/doc/user/project/merge_requests/index.md b/doc/user/project/merge_requests/index.md index ba7d05a7ad7..2765a32c845 100644 --- a/doc/user/project/merge_requests/index.md +++ b/doc/user/project/merge_requests/index.md @@ -169,6 +169,28 @@ can easily apply them to the codebase directly from the UI. Read through the documentation on [Suggest changes](../../discussions/index.md#suggest-changes) to learn more. +## Multiple assignees **[STARTER]** + +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/2004) +in [GitLab Starter 11.11](https://about.gitlab.com/pricing). + +Multiple people often review merge requests at the same time. GitLab allows you to have multiple assignees for merge requests to indicate everyone that is reviewing or accountable for it. + +![multiple assignees for merge requests sidebar](img/multiple_assignees_for_merge_requests_sidebar.png) + +To assign multiple assignees to a merge request: + +1. From a merge request, expand the right sidebar and locate the **Assignees** section. +1. Click on **Edit** and from the dropdown menu, select as many users as you want +to assign the merge request to. + +Similarly, assignees are removed by deselecting them from the same dropdown menu. + +It's also possible to manage multiple assignees: + +- When creating a merge request. +- Using [quick actions](../quick_actions.md#quick-actions-for-issues-and-merge-requests). + ## Resolve conflicts When a merge request has conflicts, GitLab may provide the option to resolve diff --git a/doc/user/project/quick_actions.md b/doc/user/project/quick_actions.md index 88f4de891a1..2040e2ee004 100644 --- a/doc/user/project/quick_actions.md +++ b/doc/user/project/quick_actions.md @@ -26,9 +26,10 @@ discussions, and descriptions: | `/award :emoji:` | Toggle emoji award | ✓ | ✓ | | `/assign me` | Assign yourself | ✓ | ✓ | | `/assign @user` | Assign one user | ✓ | ✓ | -| `/assign @user1 @user2` | Assign multiple users **[STARTER]** | ✓ | | -| `/unassign` | Remove assignee(s) | ✓ | ✓ | -| `/reassign @user1 @user2` | Change assignee | ✓ | ✓ | +| `/assign @user1 @user2` | Assign multiple users **[STARTER]** | ✓ | ✓ | +| `/unassign @user1 @user2` | Remove assignee(s) **[STARTER]** | ✓ | ✓ | +| `/reassign @user1 @user2` | Change assignee **[STARTER]** | ✓ | ✓ | +| `/unassign` | Remove current assignee | ✓ | ✓ | | `/milestone %milestone` | Set milestone | ✓ | ✓ | | `/remove_milestone` | Remove milestone | ✓ | ✓ | | `/label ~label1 ~label2` | Add label(s). Label names can also start without ~ but mixed syntax is not supported. | ✓ | ✓ | diff --git a/lib/gitlab/git/object_pool.rb b/lib/gitlab/git/object_pool.rb index 8eb3c28ab70..d0577d7a4ff 100644 --- a/lib/gitlab/git/object_pool.rb +++ b/lib/gitlab/git/object_pool.rb @@ -40,6 +40,10 @@ module Gitlab @repository ||= Gitlab::Git::Repository.new(storage, relative_path, GL_REPOSITORY, gl_project_path) end + def fetch + object_pool_service.fetch(source_repository) + end + private def object_pool_service diff --git a/lib/gitlab/gitaly_client/object_pool_service.rb b/lib/gitlab/gitaly_client/object_pool_service.rb index ce1fb4d68ae..d7fac26bc13 100644 --- a/lib/gitlab/gitaly_client/object_pool_service.rb +++ b/lib/gitlab/gitaly_client/object_pool_service.rb @@ -33,6 +33,15 @@ module Gitlab GitalyClient.call(storage, :object_pool_service, :link_repository_to_object_pool, request, timeout: GitalyClient.fast_timeout) end + + def fetch(repository) + request = Gitaly::FetchIntoObjectPoolRequest.new( + object_pool: object_pool, + origin: repository.gitaly_repository + ) + + GitalyClient.call(storage, :object_pool_service, :fetch_into_object_pool, request) + end end end end diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb index e00309e7946..582c3065189 100644 --- a/lib/gitlab/gon_helper.rb +++ b/lib/gitlab/gon_helper.rb @@ -15,7 +15,12 @@ module Gitlab gon.relative_url_root = Gitlab.config.gitlab.relative_url_root gon.shortcuts_path = Gitlab::Routing.url_helpers.help_page_path('shortcuts') gon.user_color_scheme = Gitlab::ColorSchemes.for_user(current_user).css_class - gon.sentry_dsn = Gitlab::CurrentSettings.clientside_sentry_dsn if Gitlab::CurrentSettings.clientside_sentry_enabled + + if Gitlab::CurrentSettings.clientside_sentry_enabled + gon.sentry_dsn = Gitlab::CurrentSettings.clientside_sentry_dsn + gon.sentry_environment = Gitlab.config.sentry.environment + end + gon.gitlab_url = Gitlab.config.gitlab.url gon.revision = Gitlab.revision gon.gitlab_logo = ActionController::Base.helpers.asset_path('gitlab_logo.png') diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 3d56efa9834..68454457b76 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -1296,6 +1296,9 @@ msgstr "" msgid "Badges|e.g. %{exampleUrl}" msgstr "" +msgid "Balsamiq file could not be loaded." +msgstr "" + msgid "BambooService|A continuous integration and build server" msgstr "" @@ -1614,6 +1617,9 @@ msgstr "" msgid "Cannot render the image. Maximum character count (%{charLimit}) has been exceeded." msgstr "" +msgid "Cannot show preview. For previews on sketch files, they must have the file format introduced by Sketch version 43 and above." +msgstr "" + msgid "Cannot skip two factor authentication setup" msgstr "" @@ -2714,6 +2720,9 @@ msgstr "" msgid "Copy secret to clipboard" msgstr "" +msgid "Copy source to clipboard" +msgstr "" + msgid "Copy to clipboard" msgstr "" @@ -3292,6 +3301,9 @@ msgstr "" msgid "Do you want to customize how Google Code email addresses and usernames are imported into GitLab?" msgstr "" +msgid "Dockerfile" +msgstr "" + msgid "Domain" msgstr "" @@ -3709,6 +3721,9 @@ msgstr "" msgid "Error loading branches." msgstr "" +msgid "Error loading file viewer." +msgstr "" + msgid "Error loading last commit." msgstr "" @@ -3727,6 +3742,9 @@ msgstr "" msgid "Error loading template." msgstr "" +msgid "Error loading viewer" +msgstr "" + msgid "Error occurred when toggling the notification subscription" msgstr "" @@ -3760,9 +3778,15 @@ msgstr "" msgid "Error uploading file" msgstr "" +msgid "Error uploading file: %{stripped}" +msgstr "" + msgid "Error while loading the merge request. Please try again." msgstr "" +msgid "Error while loading the project data. Please try again." +msgstr "" + msgid "Error while migrating %{upload_id}: %{error_message}" msgstr "" @@ -4012,6 +4036,9 @@ msgstr "" msgid "Failed to update issues, please try again." msgstr "" +msgid "Failed to update tag!" +msgstr "" + msgid "Failed to update." msgstr "" @@ -5195,6 +5222,9 @@ msgstr "" msgid "LFSStatus|Enabled" msgstr "" +msgid "LICENSE" +msgstr "" + msgid "Label" msgstr "" @@ -6658,6 +6688,9 @@ msgstr "" msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access." msgstr "" +msgid "Please select a file" +msgstr "" + msgid "Please select a group." msgstr "" @@ -7198,6 +7231,57 @@ msgstr "" msgid "ProjectSettings|When conflicts arise the user is given the option to rebase" msgstr "" +msgid "ProjectTemplates|.NET Core" +msgstr "" + +msgid "ProjectTemplates|Android" +msgstr "" + +msgid "ProjectTemplates|Go Micro" +msgstr "" + +msgid "ProjectTemplates|Netlify/GitBook" +msgstr "" + +msgid "ProjectTemplates|Netlify/Hexo" +msgstr "" + +msgid "ProjectTemplates|Netlify/Hugo" +msgstr "" + +msgid "ProjectTemplates|Netlify/Jekyll" +msgstr "" + +msgid "ProjectTemplates|Netlify/Plain HTML" +msgstr "" + +msgid "ProjectTemplates|NodeJS Express" +msgstr "" + +msgid "ProjectTemplates|Pages/GitBook" +msgstr "" + +msgid "ProjectTemplates|Pages/Hexo" +msgstr "" + +msgid "ProjectTemplates|Pages/Hugo" +msgstr "" + +msgid "ProjectTemplates|Pages/Jekyll" +msgstr "" + +msgid "ProjectTemplates|Pages/Plain HTML" +msgstr "" + +msgid "ProjectTemplates|Ruby on Rails" +msgstr "" + +msgid "ProjectTemplates|Spring" +msgstr "" + +msgid "ProjectTemplates|iOS (Swift)" +msgstr "" + msgid "Projects" msgstr "" @@ -7321,6 +7405,9 @@ msgstr "" msgid "Protected" msgstr "" +msgid "Protected Tag" +msgstr "" + msgid "Protip:" msgstr "" @@ -7844,6 +7931,9 @@ msgstr "" msgid "Save Changes" msgstr "" +msgid "Save anyway" +msgstr "" + msgid "Save application" msgstr "" @@ -8725,6 +8815,9 @@ msgstr "" msgid "Switch to GitLab Next" msgstr "" +msgid "Switch to the source to copy it to the clipboard" +msgstr "" + msgid "System Hooks" msgstr "" @@ -8872,6 +8965,9 @@ msgstr "" msgid "Test failed." msgstr "" +msgid "Test settings and save changes" +msgstr "" + msgid "TestHooks|Ensure one of your projects has merge requests." msgstr "" @@ -9147,6 +9243,9 @@ msgstr "" msgid "There is already a repository with that name on disk" msgstr "" +msgid "There was a problem communicating with your device." +msgstr "" + msgid "There was an error loading users activity calendar." msgstr "" @@ -9225,6 +9324,12 @@ msgstr "" msgid "This container registry has been scheduled for deletion." msgstr "" +msgid "This device has already been registered with us." +msgstr "" + +msgid "This device has not been registered with us." +msgstr "" + msgid "This diff is collapsed." msgstr "" @@ -9811,6 +9916,9 @@ msgstr "" msgid "Type" msgstr "" +msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details." +msgstr "" + msgid "Unable to connect to server: %{error}" msgstr "" @@ -10240,6 +10348,9 @@ msgstr "" msgid "VisibilityLevel|Unknown" msgstr "" +msgid "Wait for the source to load to copy it to the clipboard" +msgstr "" + msgid "Want to see the data? Please ask an administrator for access." msgstr "" diff --git a/package.json b/package.json index 7981ec850a2..6fd364251e8 100644 --- a/package.json +++ b/package.json @@ -90,7 +90,7 @@ "jszip-utils": "^0.0.2", "katex": "^0.10.0", "marked": "^0.3.12", - "mermaid": "^8.0.0-rc.8", + "mermaid": "^8.0.0", "monaco-editor": "^0.15.6", "monaco-editor-webpack-plugin": "^1.7.0", "mousetrap": "^1.4.6", diff --git a/spec/javascripts/vue_shared/components/sidebar/collapsed_calendar_icon_spec.js b/spec/frontend/vue_shared/components/sidebar/collapsed_calendar_icon_spec.js index 6bff1521695..691ebe43d6b 100644 --- a/spec/javascripts/vue_shared/components/sidebar/collapsed_calendar_icon_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/collapsed_calendar_icon_spec.js @@ -1,6 +1,6 @@ import Vue from 'vue'; import collapsedCalendarIcon from '~/vue_shared/components/sidebar/collapsed_calendar_icon.vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; +import mountComponent from 'helpers/vue_mount_component_helper'; describe('collapsedCalendarIcon', () => { let vm; @@ -26,7 +26,7 @@ describe('collapsedCalendarIcon', () => { }); it('should emit click event when container is clicked', () => { - const click = jasmine.createSpy(); + const click = jest.fn(); vm.$on('click', click); vm.$el.click(); diff --git a/spec/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js b/spec/frontend/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js index c507a97d37e..062ebfa01c9 100644 --- a/spec/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js @@ -1,6 +1,6 @@ import Vue from 'vue'; import collapsedGroupedDatePicker from '~/vue_shared/components/sidebar/collapsed_grouped_date_picker.vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; +import mountComponent from 'helpers/vue_mount_component_helper'; describe('collapsedGroupedDatePicker', () => { let vm; @@ -13,7 +13,7 @@ describe('collapsedGroupedDatePicker', () => { describe('toggleCollapse events', () => { beforeEach(done => { - spyOn(vm, 'toggleSidebar'); + jest.spyOn(vm, 'toggleSidebar').mockImplementation(() => {}); vm.minDate = new Date('07/17/2016'); Vue.nextTick(done); }); diff --git a/spec/javascripts/vue_shared/components/sidebar/date_picker_spec.js b/spec/frontend/vue_shared/components/sidebar/date_picker_spec.js index 805ba7b9947..5e2bca6efc9 100644 --- a/spec/javascripts/vue_shared/components/sidebar/date_picker_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/date_picker_spec.js @@ -1,6 +1,6 @@ import Vue from 'vue'; import sidebarDatePicker from '~/vue_shared/components/sidebar/date_picker.vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; +import mountComponent from 'helpers/vue_mount_component_helper'; describe('sidebarDatePicker', () => { let vm; @@ -13,7 +13,7 @@ describe('sidebarDatePicker', () => { }); it('should emit toggleCollapse when collapsed toggle sidebar is clicked', () => { - const toggleCollapse = jasmine.createSpy(); + const toggleCollapse = jest.fn(); vm.$on('toggleCollapse', toggleCollapse); vm.$el.querySelector('.issuable-sidebar-header .gutter-toggle').click(); @@ -90,7 +90,7 @@ describe('sidebarDatePicker', () => { }); it('should emit saveDate when remove button is clicked', () => { - const saveDate = jasmine.createSpy(); + const saveDate = jest.fn(); vm.$on('saveDate', saveDate); vm.$el.querySelector('.value-content .btn-blank').click(); @@ -110,7 +110,7 @@ describe('sidebarDatePicker', () => { }); it('should emit toggleCollapse when toggle sidebar is clicked', () => { - const toggleCollapse = jasmine.createSpy(); + const toggleCollapse = jest.fn(); vm.$on('toggleCollapse', toggleCollapse); vm.$el.querySelector('.title .gutter-toggle').click(); diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/base_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select/base_spec.js index c44b04009ca..6aee616c324 100644 --- a/spec/javascripts/vue_shared/components/sidebar/labels_select/base_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select/base_spec.js @@ -3,25 +3,35 @@ import Vue from 'vue'; import LabelsSelect from '~/labels_select'; import baseComponent from '~/vue_shared/components/sidebar/labels_select/base.vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; - -import { mockConfig, mockLabels } from './mock_data'; +import { mount } from '@vue/test-utils'; +import { + mockConfig, + mockLabels, +} from '../../../../../javascripts/vue_shared/components/sidebar/labels_select/mock_data'; const createComponent = (config = mockConfig) => { const Component = Vue.extend(baseComponent); - return mountComponent(Component, config); + return mount(Component, { + propsData: config, + sync: false, + }); }; describe('BaseComponent', () => { + let wrapper; let vm; - beforeEach(() => { - vm = createComponent(); + beforeEach(done => { + wrapper = createComponent(); + + ({ vm } = wrapper); + + Vue.nextTick(done); }); afterEach(() => { - vm.$destroy(); + wrapper.destroy(); }); describe('computed', () => { @@ -31,11 +41,9 @@ describe('BaseComponent', () => { }); it('returns correct string when showCreate prop is `false`', () => { - const mockConfigNonEditable = Object.assign({}, mockConfig, { showCreate: false }); - const vmNonEditable = createComponent(mockConfigNonEditable); + wrapper.setProps({ showCreate: false }); - expect(vmNonEditable.hiddenInputName).toBe('label_id[]'); - vmNonEditable.$destroy(); + expect(vm.hiddenInputName).toBe('label_id[]'); }); }); @@ -45,11 +53,9 @@ describe('BaseComponent', () => { }); it('return `Create group label` when `isProject` prop is false', () => { - const mockConfigGroup = Object.assign({}, mockConfig, { isProject: false }); - const vmGroup = createComponent(mockConfigGroup); + wrapper.setProps({ isProject: false }); - expect(vmGroup.createLabelTitle).toBe('Create group label'); - vmGroup.$destroy(); + expect(vm.createLabelTitle).toBe('Create group label'); }); }); @@ -59,11 +65,9 @@ describe('BaseComponent', () => { }); it('return `Manage group labels` when `isProject` prop is false', () => { - const mockConfigGroup = Object.assign({}, mockConfig, { isProject: false }); - const vmGroup = createComponent(mockConfigGroup); + wrapper.setProps({ isProject: false }); - expect(vmGroup.manageLabelsTitle).toBe('Manage group labels'); - vmGroup.$destroy(); + expect(vm.manageLabelsTitle).toBe('Manage group labels'); }); }); }); @@ -71,7 +75,7 @@ describe('BaseComponent', () => { describe('methods', () => { describe('handleClick', () => { it('emits onLabelClick event with label and list of labels as params', () => { - spyOn(vm, '$emit'); + jest.spyOn(vm, '$emit').mockImplementation(() => {}); vm.handleClick(mockLabels[0]); expect(vm.$emit).toHaveBeenCalledWith('onLabelClick', mockLabels[0]); @@ -80,7 +84,7 @@ describe('BaseComponent', () => { describe('handleCollapsedValueClick', () => { it('emits toggleCollapse event on component', () => { - spyOn(vm, '$emit'); + jest.spyOn(vm, '$emit').mockImplementation(() => {}); vm.handleCollapsedValueClick(); expect(vm.$emit).toHaveBeenCalledWith('toggleCollapse'); @@ -89,7 +93,7 @@ describe('BaseComponent', () => { describe('handleDropdownHidden', () => { it('emits onDropdownClose event on component', () => { - spyOn(vm, '$emit'); + jest.spyOn(vm, '$emit').mockImplementation(() => {}); vm.handleDropdownHidden(); expect(vm.$emit).toHaveBeenCalledWith('onDropdownClose'); diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_button_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_button_spec.js index 0689fc1cf1f..bb33dc6ea0f 100644 --- a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_button_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_button_spec.js @@ -2,9 +2,11 @@ import Vue from 'vue'; import dropdownButtonComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_button.vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; - -import { mockConfig, mockLabels } from './mock_data'; +import mountComponent from 'helpers/vue_mount_component_helper'; +import { + mockConfig, + mockLabels, +} from '../../../../../javascripts/vue_shared/components/sidebar/labels_select/mock_data'; const componentConfig = Object.assign({}, mockConfig, { fieldName: 'label_id[]', diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_create_label_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_create_label_spec.js index b8f32f96332..1c25d42682c 100644 --- a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_create_label_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_create_label_spec.js @@ -2,9 +2,8 @@ import Vue from 'vue'; import dropdownCreateLabelComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_create_label.vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; - -import { mockSuggestedColors } from './mock_data'; +import mountComponent from 'helpers/vue_mount_component_helper'; +import { mockSuggestedColors } from '../../../../../javascripts/vue_shared/components/sidebar/labels_select/mock_data'; const createComponent = headerTitle => { const Component = Vue.extend(dropdownCreateLabelComponent); diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_footer_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_footer_spec.js index 3711e9dac8c..989901a0012 100644 --- a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_footer_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_footer_spec.js @@ -2,9 +2,8 @@ import Vue from 'vue'; import dropdownFooterComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_footer.vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; - -import { mockConfig } from './mock_data'; +import mountComponent from 'helpers/vue_mount_component_helper'; +import { mockConfig } from '../../../../../javascripts/vue_shared/components/sidebar/labels_select/mock_data'; const createComponent = ( labelsWebUrl = mockConfig.labelsWebUrl, diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_header_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_header_spec.js index 115e21e4f9f..c36a82e1a35 100644 --- a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_header_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_header_spec.js @@ -2,7 +2,7 @@ import Vue from 'vue'; import dropdownHeaderComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_header.vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; +import mountComponent from 'helpers/vue_mount_component_helper'; const createComponent = () => { const Component = Vue.extend(dropdownHeaderComponent); diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_search_input_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_search_input_spec.js index c30e619e76b..2fffb2e495e 100644 --- a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_search_input_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_search_input_spec.js @@ -2,7 +2,7 @@ import Vue from 'vue'; import dropdownSearchInputComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_search_input.vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; +import mountComponent from 'helpers/vue_mount_component_helper'; const createComponent = () => { const Component = Vue.extend(dropdownSearchInputComponent); diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_title_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_title_spec.js index 6c84d2e167c..1616e657c81 100644 --- a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_title_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_title_spec.js @@ -2,7 +2,7 @@ import Vue from 'vue'; import dropdownTitleComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_title.vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; +import mountComponent from 'helpers/vue_mount_component_helper'; const createComponent = (canEdit = true) => { const Component = Vue.extend(dropdownTitleComponent); diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js index 4d3de5e474d..517f2c01c46 100644 --- a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js @@ -2,9 +2,8 @@ import Vue from 'vue'; import dropdownValueCollapsedComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; - -import { mockLabels } from './mock_data'; +import mountComponent from 'helpers/vue_mount_component_helper'; +import { mockLabels } from '../../../../../javascripts/vue_shared/components/sidebar/labels_select/mock_data'; const createComponent = (labels = mockLabels) => { const Component = Vue.extend(dropdownValueCollapsedComponent); @@ -72,7 +71,7 @@ describe('DropdownValueCollapsedComponent', () => { describe('methods', () => { describe('handleClick', () => { it('emits onValueClick event on component', () => { - spyOn(vm, '$emit'); + jest.spyOn(vm, '$emit').mockImplementation(() => {}); vm.handleClick(); expect(vm.$emit).toHaveBeenCalledWith('onValueClick'); diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js index 35a9c300953..ec143fec5d9 100644 --- a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js @@ -3,9 +3,11 @@ import $ from 'jquery'; import dropdownValueComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_value.vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; - -import { mockConfig, mockLabels } from './mock_data'; +import mountComponent from 'helpers/vue_mount_component_helper'; +import { + mockConfig, + mockLabels, +} from '../../../../../javascripts/vue_shared/components/sidebar/labels_select/mock_data'; const createComponent = ( labels = mockLabels, diff --git a/spec/javascripts/vue_shared/components/sidebar/toggle_sidebar_spec.js b/spec/frontend/vue_shared/components/sidebar/toggle_sidebar_spec.js index c911a129173..5cf25ca6f81 100644 --- a/spec/javascripts/vue_shared/components/sidebar/toggle_sidebar_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/toggle_sidebar_spec.js @@ -1,6 +1,6 @@ import Vue from 'vue'; import toggleSidebar from '~/vue_shared/components/sidebar/toggle_sidebar.vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; +import mountComponent from 'helpers/vue_mount_component_helper'; describe('toggleSidebar', () => { let vm; @@ -23,7 +23,7 @@ describe('toggleSidebar', () => { }); it('should emit toggle event when button clicked', () => { - const toggle = jasmine.createSpy(); + const toggle = jest.fn(); vm.$on('toggle', toggle); vm.$el.click(); diff --git a/spec/graphql/types/group_type_spec.rb b/spec/graphql/types/group_type_spec.rb new file mode 100644 index 00000000000..3dd5b602aa2 --- /dev/null +++ b/spec/graphql/types/group_type_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe GitlabSchema.types['Group'] do + it { expect(described_class).to expose_permissions_using(Types::PermissionTypes::Group) } + + it { expect(described_class.graphql_name).to eq('Group') } + + it { expect(described_class).to require_graphql_authorizations(:read_group) } +end diff --git a/spec/graphql/types/namespace_type.rb b/spec/graphql/types/namespace_type.rb new file mode 100644 index 00000000000..7cd6a79ae5d --- /dev/null +++ b/spec/graphql/types/namespace_type.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe GitlabSchema.types['Namespace'] do + it { expect(described_class.graphql_name).to eq('Namespace') } +end diff --git a/spec/graphql/types/query_type_spec.rb b/spec/graphql/types/query_type_spec.rb index 69e3ea8a4a9..b4626955816 100644 --- a/spec/graphql/types/query_type_spec.rb +++ b/spec/graphql/types/query_type_spec.rb @@ -5,7 +5,7 @@ describe GitlabSchema.types['Query'] do expect(described_class.graphql_name).to eq('Query') end - it { is_expected.to have_graphql_fields(:project, :echo, :metadata) } + it { is_expected.to have_graphql_fields(:project, :group, :echo, :metadata) } describe 'project field' do subject { described_class.fields['project'] } diff --git a/spec/javascripts/raven/index_spec.js b/spec/javascripts/raven/index_spec.js index a503a54029f..6b9fe923624 100644 --- a/spec/javascripts/raven/index_spec.js +++ b/spec/javascripts/raven/index_spec.js @@ -5,19 +5,19 @@ describe('RavenConfig options', () => { const sentryDsn = 'sentryDsn'; const currentUserId = 'currentUserId'; const gitlabUrl = 'gitlabUrl'; - const isProduction = 'isProduction'; + const environment = 'test'; const revision = 'revision'; let indexReturnValue; beforeEach(() => { window.gon = { sentry_dsn: sentryDsn, + sentry_environment: environment, current_user_id: currentUserId, gitlab_url: gitlabUrl, revision, }; - process.env.NODE_ENV = isProduction; process.env.HEAD_COMMIT_SHA = revision; spyOn(RavenConfig, 'init'); @@ -25,12 +25,12 @@ describe('RavenConfig options', () => { indexReturnValue = index(); }); - it('should init with .sentryDsn, .currentUserId, .whitelistUrls and .isProduction', () => { + it('should init with .sentryDsn, .currentUserId, .whitelistUrls and environment', () => { expect(RavenConfig.init).toHaveBeenCalledWith({ sentryDsn, currentUserId, - whitelistUrls: [gitlabUrl], - isProduction, + whitelistUrls: [gitlabUrl, 'webpack-internal://'], + environment, release: revision, tags: { revision, diff --git a/spec/javascripts/raven/raven_config_spec.js b/spec/javascripts/raven/raven_config_spec.js index 5cc59cc28d3..af634a0c196 100644 --- a/spec/javascripts/raven/raven_config_spec.js +++ b/spec/javascripts/raven/raven_config_spec.js @@ -69,8 +69,8 @@ describe('RavenConfig', () => { let ravenConfig; const options = { sentryDsn: '//sentryDsn', - whitelistUrls: ['//gitlabUrl'], - isProduction: true, + whitelistUrls: ['//gitlabUrl', 'webpack-internal://'], + environment: 'test', release: 'revision', tags: { revision: 'revision', @@ -95,7 +95,7 @@ describe('RavenConfig', () => { release: options.release, tags: options.tags, whitelistUrls: options.whitelistUrls, - environment: 'production', + environment: 'test', ignoreErrors: ravenConfig.IGNORE_ERRORS, ignoreUrls: ravenConfig.IGNORE_URLS, shouldSendCallback: jasmine.any(Function), @@ -106,8 +106,8 @@ describe('RavenConfig', () => { expect(raven.install).toHaveBeenCalled(); }); - it('should set .environment to development if isProduction is false', () => { - ravenConfig.options.isProduction = false; + it('should set environment from options', () => { + ravenConfig.options.environment = 'development'; RavenConfig.configure.call(ravenConfig); diff --git a/spec/lib/gitlab/git/object_pool_spec.rb b/spec/lib/gitlab/git/object_pool_spec.rb index 0d5069568e1..6511c2b61bf 100644 --- a/spec/lib/gitlab/git/object_pool_spec.rb +++ b/spec/lib/gitlab/git/object_pool_spec.rb @@ -3,8 +3,12 @@ require 'spec_helper' describe Gitlab::Git::ObjectPool do + include RepoHelpers + let(:pool_repository) { create(:pool_repository) } let(:source_repository) { pool_repository.source_project.repository } + let(:source_repository_path) { File.join(TestEnv.repos_path, source_repository.relative_path) } + let(:source_repository_rugged) { Rugged::Repository.new(source_repository_path) } subject { pool_repository.object_pool } @@ -76,4 +80,41 @@ describe Gitlab::Git::ObjectPool do end end end + + describe '#fetch' do + let(:commit_count) { source_repository.commit_count } + + context "when the object's pool repository exists" do + it 'does not raise an error' do + expect { subject.fetch }.not_to raise_error + end + end + + context "when the object's pool repository does not exist" do + before do + subject.delete + end + + it "re-creates the object pool's repository" do + subject.fetch + + expect(subject.repository.exists?).to be(true) + end + + it 'does not raise an error' do + expect { subject.fetch }.not_to raise_error + end + + it 'fetches objects from the source repository' do + new_commit_id = new_commit_edit_old_file(source_repository_rugged).oid + + expect(subject.repository.exists?).to be false + + subject.fetch + + expect(subject.repository.commit_count('refs/remotes/origin/master')).to eq(commit_count) + expect(subject.repository.commit(new_commit_id).id).to eq(new_commit_id) + end + end + end end diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index 5f8a2848944..0f6aac9b6de 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -3,6 +3,7 @@ require "spec_helper" describe Gitlab::Git::Repository, :seed_helper do include Gitlab::EncodingHelper + include RepoHelpers using RSpec::Parameterized::TableSyntax shared_examples 'wrapping gRPC errors' do |gitaly_client_class, gitaly_client_method| @@ -2209,83 +2210,6 @@ describe Gitlab::Git::Repository, :seed_helper do repository_rugged.references.create("refs/remotes/#{remote_name}/#{branch_name}", source_branch.dereferenced_target.sha) end - # Build the options hash that's passed to Rugged::Commit#create - def commit_options(repo, index, target, ref, message) - options = {} - options[:tree] = index.write_tree(repo) - options[:author] = { - email: "test@example.com", - name: "Test Author", - time: Time.gm(2014, "mar", 3, 20, 15, 1) - } - options[:committer] = { - email: "test@example.com", - name: "Test Author", - time: Time.gm(2014, "mar", 3, 20, 15, 1) - } - options[:message] ||= message - options[:parents] = repo.empty? ? [] : [target].compact - options[:update_ref] = ref - - options - end - - # Writes a new commit to the repo and returns a Rugged::Commit. Replaces the - # contents of CHANGELOG with a single new line of text. - def new_commit_edit_old_file(repo) - oid = repo.write("I replaced the changelog with this text", :blob) - index = repo.index - index.read_tree(repo.head.target.tree) - index.add(path: "CHANGELOG", oid: oid, mode: 0100644) - - options = commit_options( - repo, - index, - repo.head.target, - "HEAD", - "Edit CHANGELOG in its original location" - ) - - sha = Rugged::Commit.create(repo, options) - repo.lookup(sha) - end - - # Writes a new commit to the repo and returns a Rugged::Commit. Replaces the - # contents of the specified file_path with new text. - def new_commit_edit_new_file(repo, file_path, commit_message, text, branch = repo.head) - oid = repo.write(text, :blob) - index = repo.index - index.read_tree(branch.target.tree) - index.add(path: file_path, oid: oid, mode: 0100644) - options = commit_options(repo, index, branch.target, branch.canonical_name, commit_message) - sha = Rugged::Commit.create(repo, options) - repo.lookup(sha) - end - - # Writes a new commit to the repo and returns a Rugged::Commit. Replaces the - # contents of encoding/CHANGELOG with new text. - def new_commit_edit_new_file_on_branch(repo, file_path, branch_name, commit_message, text) - branch = repo.branches[branch_name] - new_commit_edit_new_file(repo, file_path, commit_message, text, branch) - end - - # Writes a new commit to the repo and returns a Rugged::Commit. Moves the - # CHANGELOG file to the encoding/ directory. - def new_commit_move_file(repo) - blob_oid = repo.head.target.tree.detect { |i| i[:name] == "CHANGELOG" }[:oid] - file_content = repo.lookup(blob_oid).content - oid = repo.write(file_content, :blob) - index = repo.index - index.read_tree(repo.head.target.tree) - index.add(path: "encoding/CHANGELOG", oid: oid, mode: 0100644) - index.remove("CHANGELOG") - - options = commit_options(repo, index, repo.head.target, "HEAD", "Move CHANGELOG to encoding/") - - sha = Rugged::Commit.create(repo, options) - repo.lookup(sha) - end - def refs(dir) IO.popen(%W[git -C #{dir} for-each-ref], &:read).split("\n").map do |line| line.split("\t").last diff --git a/spec/lib/gitlab/gitaly_client/object_pool_service_spec.rb b/spec/lib/gitlab/gitaly_client/object_pool_service_spec.rb index 149b7ec5bb0..0e0c3d329b5 100644 --- a/spec/lib/gitlab/gitaly_client/object_pool_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/object_pool_service_spec.rb @@ -43,4 +43,24 @@ describe Gitlab::GitalyClient::ObjectPoolService do end end end + + describe '#fetch' do + before do + subject.delete + end + + it 'restores the pool repository objects' do + subject.fetch(project.repository) + + expect(object_pool.repository.exists?).to be(true) + end + + context 'when called twice' do + it "doesn't raise an error" do + subject.delete + + expect { subject.fetch(project.repository) }.not_to raise_error + end + end + end end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index f61857ea5ff..fb7f43b25cf 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -2262,6 +2262,50 @@ describe MergeRequest do end end + describe "#environments" do + subject { merge_request.environments } + + let(:merge_request) { create(:merge_request, source_branch: 'feature', target_branch: 'master') } + let(:project) { merge_request.project } + + let(:pipeline) do + create(:ci_pipeline, + source: :merge_request_event, + merge_request: merge_request, project: project, + sha: merge_request.diff_head_sha, + merge_requests_as_head_pipeline: [merge_request]) + end + + let!(:job) { create(:ci_build, :start_review_app, pipeline: pipeline, project: project) } + + it 'returns environments' do + is_expected.to eq(pipeline.environments) + expect(subject.count).to be(1) + end + + context 'when pipeline is not associated with environments' do + let!(:job) { create(:ci_build, pipeline: pipeline, project: project) } + + it 'returns empty array' do + is_expected.to be_empty + end + end + + context 'when pipeline is not a pipeline for merge request' do + let(:pipeline) do + create(:ci_pipeline, + project: project, + ref: 'feature', + sha: merge_request.diff_head_sha, + merge_requests_as_head_pipeline: [merge_request]) + end + + it 'returns empty relation' do + is_expected.to be_empty + end + end + end + describe "#reload_diff" do it 'calls MergeRequests::ReloadDiffsService#execute with correct params' do user = create(:user) diff --git a/spec/requests/api/graphql/group_query_spec.rb b/spec/requests/api/graphql/group_query_spec.rb new file mode 100644 index 00000000000..8ff95cc9af2 --- /dev/null +++ b/spec/requests/api/graphql/group_query_spec.rb @@ -0,0 +1,118 @@ +# frozen_string_literal: true + +require 'spec_helper' + +# Based on spec/requests/api/groups_spec.rb +# Should follow closely in order to ensure all situations are covered +describe 'getting group information' do + include GraphqlHelpers + include UploadHelpers + + let(:user1) { create(:user, can_create_group: false) } + let(:user2) { create(:user) } + let(:admin) { create(:admin) } + let(:public_group) { create(:group, :public) } + let(:private_group) { create(:group, :private) } + + # similar to the API "GET /groups/:id" + describe "Query group(fullPath)" do + def group_query(group) + graphql_query_for('group', 'fullPath' => group.full_path) + end + + it_behaves_like 'a working graphql query' do + before do + post_graphql(group_query(public_group)) + end + end + + context 'when unauthenticated' do + it 'returns nil for a private group' do + post_graphql(group_query(private_group)) + + expect(graphql_data['group']).to be_nil + end + + it 'returns a public group' do + post_graphql(group_query(public_group)) + + expect(graphql_data['group']).not_to be_nil + end + end + + context "when authenticated as user" do + let!(:group1) { create(:group, avatar: File.open(uploaded_image_temp_path)) } + let!(:group2) { create(:group, :private) } + + before do + group1.add_owner(user1) + group2.add_owner(user2) + end + + it "returns one of user1's groups" do + project = create(:project, namespace: group2, path: 'Foo') + create(:project_group_link, project: project, group: group1) + + post_graphql(group_query(group1), current_user: user1) + + expect(response).to have_gitlab_http_status(200) + expect(graphql_data['group']['id']).to eq(group1.id.to_s) + expect(graphql_data['group']['name']).to eq(group1.name) + expect(graphql_data['group']['path']).to eq(group1.path) + expect(graphql_data['group']['description']).to eq(group1.description) + expect(graphql_data['group']['visibility']).to eq(Gitlab::VisibilityLevel.string_level(group1.visibility_level)) + expect(graphql_data['group']['avatarUrl']).to eq(group1.avatar_url(only_path: false)) + expect(graphql_data['group']['webUrl']).to eq(group1.web_url) + expect(graphql_data['group']['requestAccessEnabled']).to eq(group1.request_access_enabled) + expect(graphql_data['group']['fullName']).to eq(group1.full_name) + expect(graphql_data['group']['fullPath']).to eq(group1.full_path) + expect(graphql_data['group']['parentId']).to eq(group1.parent_id) + end + + it "does not return a non existing group" do + query = graphql_query_for('group', 'fullPath' => '1328') + + post_graphql(query, current_user: user1) + + expect(graphql_data['group']).to be_nil + end + + it "does not return a group not attached to user1" do + private_group.add_owner(user2) + + post_graphql(group_query(private_group), current_user: user1) + + expect(graphql_data['group']).to be_nil + end + + it 'avoids N+1 queries' do + post_graphql(group_query(group1), current_user: admin) + + control_count = ActiveRecord::QueryRecorder.new do + post_graphql(group_query(group1), current_user: admin) + end.count + + create(:project, namespace: group1) + + expect do + post_graphql(group_query(group1), current_user: admin) + end.not_to exceed_query_limit(control_count) + end + end + + context "when authenticated as admin" do + it "returns any existing group" do + post_graphql(group_query(private_group), current_user: admin) + + expect(graphql_data['group']['name']).to eq(private_group.name) + end + + it "does not return a non existing group" do + query = graphql_query_for('group', 'fullPath' => '1328') + post_graphql(query, current_user: admin) + + expect(graphql_data['group']).to be_nil + end + end + end +end diff --git a/spec/services/ci/stop_environments_service_spec.rb b/spec/services/ci/stop_environments_service_spec.rb index 31b8d540356..890fa5bc009 100644 --- a/spec/services/ci/stop_environments_service_spec.rb +++ b/spec/services/ci/stop_environments_service_spec.rb @@ -105,6 +105,82 @@ describe Ci::StopEnvironmentsService do end end + describe '#execute_for_merge_request' do + subject { service.execute_for_merge_request(merge_request) } + + let(:merge_request) { create(:merge_request, source_branch: 'feature', target_branch: 'master') } + let(:project) { merge_request.project } + let(:user) { create(:user) } + + let(:pipeline) do + create(:ci_pipeline, + source: :merge_request_event, + merge_request: merge_request, + project: project, + sha: merge_request.diff_head_sha, + merge_requests_as_head_pipeline: [merge_request]) + end + + let!(:review_job) { create(:ci_build, :start_review_app, pipeline: pipeline, project: project) } + let!(:stop_review_job) { create(:ci_build, :stop_review_app, :manual, pipeline: pipeline, project: project) } + + before do + review_job.deployment.success! + end + + it 'has active environment at first' do + expect(pipeline.environments.first).to be_available + end + + context 'when user is a developer' do + before do + project.add_developer(user) + end + + it 'stops the active environment' do + subject + + expect(pipeline.environments.first).to be_stopped + end + end + + context 'when user is a reporter' do + before do + project.add_reporter(user) + end + + it 'does not stop the active environment' do + subject + + expect(pipeline.environments.first).to be_available + end + end + + context 'when pipeline is not associated with environments' do + let!(:job) { create(:ci_build, pipeline: pipeline, project: project) } + + it 'does not raise exception' do + expect { subject }.not_to raise_exception + end + end + + context 'when pipeline is not a pipeline for merge request' do + let(:pipeline) do + create(:ci_pipeline, + project: project, + ref: 'feature', + sha: merge_request.diff_head_sha, + merge_requests_as_head_pipeline: [merge_request]) + end + + it 'does not stop the active environment' do + subject + + expect(pipeline.environments.first).to be_available + end + end + end + def expect_environment_stopped_on(branch) expect_any_instance_of(Environment) .to receive(:stop!) diff --git a/spec/services/merge_requests/close_service_spec.rb b/spec/services/merge_requests/close_service_spec.rb index aa7dfda4950..ffa612cf315 100644 --- a/spec/services/merge_requests/close_service_spec.rb +++ b/spec/services/merge_requests/close_service_spec.rb @@ -74,6 +74,14 @@ describe MergeRequests::CloseService do .to change { project.open_merge_requests_count }.from(1).to(0) end + it 'clean up environments for the merge request' do + expect_next_instance_of(Ci::StopEnvironmentsService) do |service| + expect(service).to receive(:execute_for_merge_request).with(merge_request) + end + + described_class.new(project, user).execute(merge_request) + end + context 'current user is not authorized to close merge request' do before do perform_enqueued_jobs do diff --git a/spec/services/merge_requests/post_merge_service_spec.rb b/spec/services/merge_requests/post_merge_service_spec.rb index 7b87913ab8b..ffc86f68469 100644 --- a/spec/services/merge_requests/post_merge_service_spec.rb +++ b/spec/services/merge_requests/post_merge_service_spec.rb @@ -62,5 +62,13 @@ describe MergeRequests::PostMergeService do expect(merge_request.reload).to be_merged end + + it 'clean up environments for the merge request' do + expect_next_instance_of(Ci::StopEnvironmentsService) do |service| + expect(service).to receive(:execute_for_merge_request).with(merge_request) + end + + described_class.new(project, user).execute(merge_request) + end end end diff --git a/spec/support/helpers/repo_helpers.rb b/spec/support/helpers/repo_helpers.rb index 4af90f4af79..44d95a029af 100644 --- a/spec/support/helpers/repo_helpers.rb +++ b/spec/support/helpers/repo_helpers.rb @@ -11,6 +11,8 @@ module RepoHelpers # blob.path # => 'files/js/commit.js.coffee' # blob.data # => 'class Commit...' # + # Build the options hash that's passed to Rugged::Commit#create + def sample_blob OpenStruct.new( oid: '5f53439ca4b009096571d3c8bc3d09d30e7431b3', @@ -129,4 +131,80 @@ eos file_content: content ).execute end + + def commit_options(repo, index, target, ref, message) + options = {} + options[:tree] = index.write_tree(repo) + options[:author] = { + email: "test@example.com", + name: "Test Author", + time: Time.gm(2014, "mar", 3, 20, 15, 1) + } + options[:committer] = { + email: "test@example.com", + name: "Test Author", + time: Time.gm(2014, "mar", 3, 20, 15, 1) + } + options[:message] ||= message + options[:parents] = repo.empty? ? [] : [target].compact + options[:update_ref] = ref + + options + end + + # Writes a new commit to the repo and returns a Rugged::Commit. Replaces the + # contents of CHANGELOG with a single new line of text. + def new_commit_edit_old_file(repo) + oid = repo.write("I replaced the changelog with this text", :blob) + index = repo.index + index.read_tree(repo.head.target.tree) + index.add(path: "CHANGELOG", oid: oid, mode: 0100644) + + options = commit_options( + repo, + index, + repo.head.target, + "HEAD", + "Edit CHANGELOG in its original location" + ) + + sha = Rugged::Commit.create(repo, options) + repo.lookup(sha) + end + + # Writes a new commit to the repo and returns a Rugged::Commit. Replaces the + # contents of the specified file_path with new text. + def new_commit_edit_new_file(repo, file_path, commit_message, text, branch = repo.head) + oid = repo.write(text, :blob) + index = repo.index + index.read_tree(branch.target.tree) + index.add(path: file_path, oid: oid, mode: 0100644) + options = commit_options(repo, index, branch.target, branch.canonical_name, commit_message) + sha = Rugged::Commit.create(repo, options) + repo.lookup(sha) + end + + # Writes a new commit to the repo and returns a Rugged::Commit. Replaces the + # contents of encoding/CHANGELOG with new text. + def new_commit_edit_new_file_on_branch(repo, file_path, branch_name, commit_message, text) + branch = repo.branches[branch_name] + new_commit_edit_new_file(repo, file_path, commit_message, text, branch) + end + + # Writes a new commit to the repo and returns a Rugged::Commit. Moves the + # CHANGELOG file to the encoding/ directory. + def new_commit_move_file(repo) + blob_oid = repo.head.target.tree.detect { |i| i[:name] == "CHANGELOG" }[:oid] + file_content = repo.lookup(blob_oid).content + oid = repo.write(file_content, :blob) + index = repo.index + index.read_tree(repo.head.target.tree) + index.add(path: "encoding/CHANGELOG", oid: oid, mode: 0100644) + index.remove("CHANGELOG") + + options = commit_options(repo, index, repo.head.target, "HEAD", "Move CHANGELOG to encoding/") + + sha = Rugged::Commit.create(repo, options) + repo.lookup(sha) + end end diff --git a/spec/support/shared_examples/application_setting_examples.rb b/spec/support/shared_examples/application_setting_examples.rb index e7ec24c5b7e..d8f7ba1185e 100644 --- a/spec/support/shared_examples/application_setting_examples.rb +++ b/spec/support/shared_examples/application_setting_examples.rb @@ -249,4 +249,41 @@ RSpec.shared_examples 'application settings examples' do expect(setting.password_authentication_enabled_for_web?).to be_falsey end + + describe 'sentry settings' do + context 'when the sentry settings are not set in gitlab.yml' do + it 'fallbacks to the settings in the database' do + setting.sentry_enabled = true + setting.sentry_dsn = 'https://b44a0828b72421a6d8e99efd68d44fa8@example.com/40' + setting.clientside_sentry_enabled = true + setting.clientside_sentry_dsn = 'https://b44a0828b72421a6d8e99efd68d44fa8@example.com/41' + + allow(Gitlab.config.sentry).to receive(:enabled).and_return(false) + allow(Gitlab.config.sentry).to receive(:dsn).and_return(nil) + + expect(setting.sentry_enabled).to eq true + expect(setting.sentry_dsn).to eq 'https://b44a0828b72421a6d8e99efd68d44fa8@example.com/40' + expect(setting.clientside_sentry_enabled).to eq true + expect(setting.clientside_sentry_dsn). to eq 'https://b44a0828b72421a6d8e99efd68d44fa8@example.com/41' + end + end + + context 'when the sentry settings are set in gitlab.yml' do + it 'does not fallback to the settings in the database' do + setting.sentry_enabled = false + setting.sentry_dsn = 'https://b44a0828b72421a6d8e99efd68d44fa8@example.com/40' + setting.clientside_sentry_enabled = false + setting.clientside_sentry_dsn = 'https://b44a0828b72421a6d8e99efd68d44fa8@example.com/41' + + allow(Gitlab.config.sentry).to receive(:enabled).and_return(true) + allow(Gitlab.config.sentry).to receive(:dsn).and_return('https://b44a0828b72421a6d8e99efd68d44fa8@example.com/42') + + expect(setting).not_to receive(:read_attribute) + expect(setting.sentry_enabled).to eq true + expect(setting.sentry_dsn).to eq 'https://b44a0828b72421a6d8e99efd68d44fa8@example.com/42' + expect(setting.clientside_sentry_enabled).to eq true + expect(setting.clientside_sentry_dsn). to eq 'https://b44a0828b72421a6d8e99efd68d44fa8@example.com/42' + end + end + end end diff --git a/yarn.lock b/yarn.lock index d97659ab5fa..7c9652ba131 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2741,17 +2741,17 @@ cyclist@~0.2.2: resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640" integrity sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA= -d3-array@1, d3-array@1.2.1, d3-array@^1.2.0, d3-array@^1.2.1: +d3-array@1, d3-array@1.2.1, d3-array@^1.1.1, d3-array@^1.2.0, d3-array@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-1.2.1.tgz#d1ca33de2f6ac31efadb8e050a021d7e2396d5dc" integrity sha512-CyINJQ0SOUHojDdFDH4JEM0552vCR1utGyLHegJHyYH0JyCpSeTPxi4OBqHMA2jJZq4NH782LtaJWBImqI/HBw== -d3-axis@1.0.8, d3-axis@^1.0.8: +d3-axis@1, d3-axis@1.0.8, d3-axis@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/d3-axis/-/d3-axis-1.0.8.tgz#31a705a0b535e65759de14173a31933137f18efa" integrity sha1-MacFoLU15ldZ3hQXOjGTMTfxjvo= -d3-brush@1.0.4, d3-brush@^1.0.4: +d3-brush@1, d3-brush@1.0.4, d3-brush@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/d3-brush/-/d3-brush-1.0.4.tgz#00c2f238019f24f6c0a194a26d41a1530ffe7bc4" integrity sha1-AMLyOAGfJPbAoZSibUGhUw/+e8Q= @@ -2762,7 +2762,7 @@ d3-brush@1.0.4, d3-brush@^1.0.4: d3-selection "1" d3-transition "1" -d3-chord@1.0.4: +d3-chord@1, d3-chord@1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/d3-chord/-/d3-chord-1.0.4.tgz#7dec4f0ba886f713fe111c45f763414f6f74ca2c" integrity sha1-fexPC6iG9xP+ERxF92NBT290yiw= @@ -2780,6 +2780,13 @@ d3-color@1, d3-color@1.0.3: resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-1.0.3.tgz#bc7643fca8e53a8347e2fbdaffa236796b58509b" integrity sha1-vHZD/KjlOoNH4vva/6I2eWtYUJs= +d3-contour@1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/d3-contour/-/d3-contour-1.3.2.tgz#652aacd500d2264cb3423cee10db69f6f59bead3" + integrity sha512-hoPp4K/rJCu0ladiH6zmJUEz6+u3lgR+GSm/QdM2BBvDraU39Vr7YdDCicJcxP1z8i9B/2dJLgDC1NcvlF8WCg== + dependencies: + d3-array "^1.1.1" + d3-dispatch@1, d3-dispatch@1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-1.0.3.tgz#46e1491eaa9b58c358fce5be4e8bed626e7871f8" @@ -2807,7 +2814,14 @@ d3-ease@1, d3-ease@1.0.3, d3-ease@^1.0.3: resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-1.0.3.tgz#68bfbc349338a380c44d8acc4fbc3304aa2d8c0e" integrity sha1-aL+8NJM4o4DETYrMT7wzBKotjA4= -d3-force@1.1.0: +d3-fetch@1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/d3-fetch/-/d3-fetch-1.1.2.tgz#957c8fbc6d4480599ba191b1b2518bf86b3e1be2" + integrity sha512-S2loaQCV/ZeyTyIF2oP8D1K9Z4QizUzW7cWeAOAS4U88qOt3Ucf6GsmgthuYSdyB2HyEm4CeGvkQxWsmInsIVA== + dependencies: + d3-dsv "1" + +d3-force@1, d3-force@1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/d3-force/-/d3-force-1.1.0.tgz#cebf3c694f1078fcc3d4daf8e567b2fbd70d4ea3" integrity sha512-2HVQz3/VCQs0QeRNZTYb7GxoUCeb6bOzMp/cGcLa87awY9ZsPvXOGeZm0iaGBjXic6I1ysKwMn+g+5jSAdzwcg== @@ -2822,14 +2836,14 @@ d3-format@1, d3-format@1.2.2: resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.2.2.tgz#1a39c479c8a57fe5051b2e67a3bee27061a74e7a" integrity sha512-zH9CfF/3C8zUI47nsiKfD0+AGDEuM8LwBIP7pBVpyR4l/sKkZqITmMtxRp04rwBrlshIZ17XeFAaovN3++wzkw== -d3-geo@1.9.1: +d3-geo@1, d3-geo@1.9.1: version "1.9.1" resolved "https://registry.yarnpkg.com/d3-geo/-/d3-geo-1.9.1.tgz#157e3b0f917379d0f73bebfff3be537f49fa7356" integrity sha512-l9wL/cEQkyZQYXw3xbmLsH3eQ5ij+icNfo4r0GrLa5rOCZR/e/3am45IQ0FvQ5uMsv+77zBRunLc9ufTWSQYFA== dependencies: d3-array "1" -d3-hierarchy@1.1.5: +d3-hierarchy@1, d3-hierarchy@1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/d3-hierarchy/-/d3-hierarchy-1.1.5.tgz#a1c845c42f84a206bcf1c01c01098ea4ddaa7a26" integrity sha1-ochFxC+Eoga88cAcAQmOpN2qeiY= @@ -2846,7 +2860,7 @@ d3-path@1, d3-path@1.0.5: resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-1.0.5.tgz#241eb1849bd9e9e8021c0d0a799f8a0e8e441764" integrity sha1-JB6xhJvZ6egCHA0KeZ+KDo5EF2Q= -d3-polygon@1.0.3: +d3-polygon@1, d3-polygon@1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/d3-polygon/-/d3-polygon-1.0.3.tgz#16888e9026460933f2b179652ad378224d382c62" integrity sha1-FoiOkCZGCTPysXllKtN4Ik04LGI= @@ -2861,7 +2875,7 @@ d3-queue@3.0.7: resolved "https://registry.yarnpkg.com/d3-queue/-/d3-queue-3.0.7.tgz#c93a2e54b417c0959129d7d73f6cf7d4292e7618" integrity sha1-yTouVLQXwJWRKdfXP2z31Ckudhg= -d3-random@1.1.0: +d3-random@1, d3-random@1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/d3-random/-/d3-random-1.1.0.tgz#6642e506c6fa3a648595d2b2469788a8d12529d3" integrity sha1-ZkLlBsb6OmSFldKyRpeIqNElKdM= @@ -2876,6 +2890,14 @@ d3-request@1.0.6: d3-dsv "1" xmlhttprequest "1" +d3-scale-chromatic@1: + version "1.3.3" + resolved "https://registry.yarnpkg.com/d3-scale-chromatic/-/d3-scale-chromatic-1.3.3.tgz#dad4366f0edcb288f490128979c3c793583ed3c0" + integrity sha512-BWTipif1CimXcYfT02LKjAyItX5gKiwxuPRgr4xM58JwlLocWbjPLI7aMEjkcoOQXMkYsmNsvv3d2yl/OKuHHw== + dependencies: + d3-color "1" + d3-interpolate "1" + d3-scale@1.0.7, d3-scale@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-1.0.7.tgz#fa90324b3ea8a776422bd0472afab0b252a0945d" @@ -2889,12 +2911,24 @@ d3-scale@1.0.7, d3-scale@^1.0.7: d3-time "1" d3-time-format "2" +d3-scale@2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-2.2.2.tgz#4e880e0b2745acaaddd3ede26a9e908a9e17b81f" + integrity sha512-LbeEvGgIb8UMcAa0EATLNX0lelKWGYDQiPdHj+gLblGVhGLyNbaCn3EvrJf0A3Y/uOOU5aD6MTh5ZFCdEwGiCw== + dependencies: + d3-array "^1.2.0" + d3-collection "1" + d3-format "1" + d3-interpolate "1" + d3-time "1" + d3-time-format "2" + d3-selection@1, d3-selection@1.3.0, d3-selection@^1.1.0, d3-selection@^1.2.0: version "1.3.0" resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-1.3.0.tgz#d53772382d3dc4f7507bfb28bcd2d6aed2a0ad6d" integrity sha512-qgpUOg9tl5CirdqESUAu0t9MU/t3O9klYfGfyKsXEmhyxyzLpzpeh08gaxBUTQw1uXIOkr/30Ut2YRjSSxlmHA== -d3-shape@1.2.0, d3-shape@^1.2.0: +d3-shape@1, d3-shape@1.2.0, d3-shape@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-1.2.0.tgz#45d01538f064bafd05ea3d6d2cb748fd8c41f777" integrity sha1-RdAVOPBkuv0F6j1tLLdI/YxB93c= @@ -2930,12 +2964,12 @@ d3-transition@1, d3-transition@1.1.1, d3-transition@^1.1.1: d3-selection "^1.1.0" d3-timer "1" -d3-voronoi@1.1.2: +d3-voronoi@1, d3-voronoi@1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/d3-voronoi/-/d3-voronoi-1.1.2.tgz#1687667e8f13a2d158c80c1480c5a29cb0d8973c" integrity sha1-Fodmfo8TotFYyAwUgMWinLDYlzw= -d3-zoom@1.7.1: +d3-zoom@1, d3-zoom@1.7.1: version "1.7.1" resolved "https://registry.yarnpkg.com/d3-zoom/-/d3-zoom-1.7.1.tgz#02f43b3c3e2db54f364582d7e4a236ccc5506b63" integrity sha512-sZHQ55DGq5BZBFGnRshUT8tm2sfhPHFnOlmPbbwTkAoPeVdRTkB4Xsf9GCY0TSHrTD8PeJPZGmP/TpGicwJDJQ== @@ -2982,6 +3016,43 @@ d3@^4.13.0: d3-voronoi "1.1.2" d3-zoom "1.7.1" +d3@^5.7.0: + version "5.9.2" + resolved "https://registry.yarnpkg.com/d3/-/d3-5.9.2.tgz#64e8a7e9c3d96d9e6e4999d2c8a2c829767e67f5" + integrity sha512-ydrPot6Lm3nTWH+gJ/Cxf3FcwuvesYQ5uk+j/kXEH/xbuYWYWTMAHTJQkyeuG8Y5WM5RSEYB41EctUrXQQytRQ== + dependencies: + d3-array "1" + d3-axis "1" + d3-brush "1" + d3-chord "1" + d3-collection "1" + d3-color "1" + d3-contour "1" + d3-dispatch "1" + d3-drag "1" + d3-dsv "1" + d3-ease "1" + d3-fetch "1" + d3-force "1" + d3-format "1" + d3-geo "1" + d3-hierarchy "1" + d3-interpolate "1" + d3-path "1" + d3-polygon "1" + d3-quadtree "1" + d3-random "1" + d3-scale "2" + d3-scale-chromatic "1" + d3-selection "1" + d3-shape "1" + d3-time "1" + d3-time-format "2" + d3-timer "1" + d3-transition "1" + d3-voronoi "1" + d3-zoom "1" + dagre-d3-renderer@^0.5.8: version "0.5.8" resolved "https://registry.yarnpkg.com/dagre-d3-renderer/-/dagre-d3-renderer-0.5.8.tgz#aa071bb71d3c4d67426925906f3f6ddead49c1a3" @@ -4943,10 +5014,10 @@ hash.js@^1.0.0, hash.js@^1.0.3: inherits "^2.0.3" minimalistic-assert "^1.0.0" -he@^1.1.0, he@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" - integrity sha1-k0EP0hsAlzUVH4howvJx80J+I/0= +he@^1.1.0, he@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" + integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== highlight.js@^9.13.1, highlight.js@~9.13.0: version "9.13.1" @@ -7013,19 +7084,19 @@ merge@^1.2.0: resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.1.tgz#38bebf80c3220a8a487b6fcfb3941bb11720c145" integrity sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ== -mermaid@^8.0.0-rc.8: - version "8.0.0-rc.8" - resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-8.0.0-rc.8.tgz#74ed54d0d46e9ee71c4db2730b2d83d516a21e72" - integrity sha512-GbF9jHWfqE7YGx9vQySmBxy2Ahlclxmpk4tJ9ntNyafENl96s96ggUK/NQS5ydYoFab6MavTm4YMTIPKqWVvPQ== +mermaid@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-8.0.0.tgz#8f6c75017e788a8c3997e20c5e5046c2b88d1a8f" + integrity sha512-vUQRykev0A6RtxIVqQT3a9TDxcSbdZbQF5JDyKgidnYuJy8BE8jp6LM+HKDSQuroKm6buu4NlpMO+qhxIP/cTg== dependencies: - d3 "^4.13.0" + d3 "^5.7.0" dagre-d3-renderer "^0.5.8" dagre-layout "^0.8.8" graphlibrary "^2.2.0" - he "^1.1.1" - lodash "^4.17.5" - moment "^2.21.0" - scope-css "^1.0.5" + he "^1.2.0" + lodash "^4.17.11" + moment "^2.23.0" + scope-css "^1.2.1" methods@~1.1.2: version "1.1.2" @@ -7177,10 +7248,10 @@ mkdirp@0.5.x, mkdirp@0.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp dependencies: minimist "0.0.8" -moment@2.x, moment@^2.10.2, moment@^2.21.0: - version "2.23.0" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.23.0.tgz#759ea491ac97d54bac5ad776996e2a58cc1bc225" - integrity sha512-3IE39bHVqFbWWaPOMHZF98Q9c3LDKGTmypMiTM2QygGXXElkFWIH7GxfmlwmY2vwa+wmNsoYZmG2iusf1ZjJoA== +moment@2.x, moment@^2.10.2, moment@^2.23.0: + version "2.24.0" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b" + integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg== monaco-editor-webpack-plugin@^1.7.0: version "1.7.0" @@ -9272,7 +9343,7 @@ schema-utils@^1.0.0: ajv-errors "^1.0.0" ajv-keywords "^3.1.0" -scope-css@^1.0.5: +scope-css@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/scope-css/-/scope-css-1.2.1.tgz#c35768bc900cad030a3e0d663a818c0f6a57f40e" integrity sha512-UjLRmyEYaDNiOS673xlVkZFlVCtckJR/dKgr434VMm7Lb+AOOqXKdAcY7PpGlJYErjXXJzKN7HWo4uRPiZZG0Q== |