diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-03-30 09:07:58 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-03-30 09:07:58 +0000 |
commit | 45b4df3e57c949c88107840c44ccbfaf2eabdf26 (patch) | |
tree | f73c1533a75b03d2ceb1361644e0d8ab97568a8f | |
parent | 7421e6f9f2b5889b05738af7eba568af6ae3fcbc (diff) | |
download | gitlab-ce-45b4df3e57c949c88107840c44ccbfaf2eabdf26.tar.gz |
Add latest changes from gitlab-org/gitlab@master
36 files changed, 433 insertions, 234 deletions
diff --git a/app/assets/javascripts/environments/components/confirm_rollback_modal.vue b/app/assets/javascripts/environments/components/confirm_rollback_modal.vue index 70b5c6b0094..5c03c008faf 100644 --- a/app/assets/javascripts/environments/components/confirm_rollback_modal.vue +++ b/app/assets/javascripts/environments/components/confirm_rollback_modal.vue @@ -3,7 +3,7 @@ * Render modal to confirm rollback/redeploy. */ -import _ from 'underscore'; +import { escape as esc } from 'lodash'; import { GlModal } from '@gitlab/ui'; import { s__, sprintf } from '~/locale'; @@ -30,7 +30,7 @@ export default { : s__('Environments|Rollback environment %{name}?'); return sprintf(title, { - name: _.escape(this.environment.name), + name: esc(this.environment.name), }); }, @@ -50,10 +50,10 @@ export default { }, modalText() { - const linkStart = `<a class="commit-sha mr-0" href="${_.escape(this.commitUrl)}">`; - const commitId = _.escape(this.commitShortSha); + const linkStart = `<a class="commit-sha mr-0" href="${esc(this.commitUrl)}">`; + const commitId = esc(this.commitShortSha); const linkEnd = '</a>'; - const name = _.escape(this.name); + const name = esc(this.name); const body = this.environment.isLastDeployment ? s__( 'Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?', diff --git a/app/assets/javascripts/environments/components/environment_item.vue b/app/assets/javascripts/environments/components/environment_item.vue index ec5b1092c14..305d860a692 100644 --- a/app/assets/javascripts/environments/components/environment_item.vue +++ b/app/assets/javascripts/environments/components/environment_item.vue @@ -1,6 +1,6 @@ <script> /* eslint-disable @gitlab/vue-require-i18n-strings */ -import _ from 'underscore'; +import { isEmpty } from 'lodash'; import { GlTooltipDirective } from '@gitlab/ui'; import { __, sprintf } from '~/locale'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; @@ -79,7 +79,7 @@ export default { * @returns {Boolean} */ hasLastDeploymentKey() { - if (this.model && this.model.last_deployment && !_.isEmpty(this.model.last_deployment)) { + if (this.model && this.model.last_deployment && !isEmpty(this.model.last_deployment)) { return true; } return false; @@ -390,8 +390,8 @@ export default { deploymentHasUser() { return ( this.model && - !_.isEmpty(this.model.last_deployment) && - !_.isEmpty(this.model.last_deployment.user) + !isEmpty(this.model.last_deployment) && + !isEmpty(this.model.last_deployment.user) ); }, @@ -404,8 +404,8 @@ export default { deploymentUser() { if ( this.model && - !_.isEmpty(this.model.last_deployment) && - !_.isEmpty(this.model.last_deployment.user) + !isEmpty(this.model.last_deployment) && + !isEmpty(this.model.last_deployment.user) ) { return this.model.last_deployment.user; } @@ -431,8 +431,8 @@ export default { shouldRenderBuildName() { return ( !this.isFolder && - !_.isEmpty(this.model.last_deployment) && - !_.isEmpty(this.model.last_deployment.deployable) + !isEmpty(this.model.last_deployment) && + !isEmpty(this.model.last_deployment.deployable) ); }, @@ -473,7 +473,7 @@ export default { shouldRenderDeploymentID() { return ( !this.isFolder && - !_.isEmpty(this.model.last_deployment) && + !isEmpty(this.model.last_deployment) && this.model.last_deployment.iid !== undefined ); }, diff --git a/app/assets/javascripts/environments/components/environments_table.vue b/app/assets/javascripts/environments/components/environments_table.vue index 3f316643784..01a00e03814 100644 --- a/app/assets/javascripts/environments/components/environments_table.vue +++ b/app/assets/javascripts/environments/components/environments_table.vue @@ -3,7 +3,7 @@ * Render environments table. */ import { GlLoadingIcon } from '@gitlab/ui'; -import _ from 'underscore'; +import { flow, reverse, sortBy } from 'lodash/fp'; import environmentTableMixin from 'ee_else_ce/environments/mixins/environments_table_mixin'; import { s__ } from '~/locale'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; @@ -102,13 +102,13 @@ export default { * 4. Reverse (last deployment descending, name ascending), * 5. Put folders first. */ - return _.chain(environments) - .sortBy(env => (env.isFolder ? env.folderName : env.name)) - .reverse() - .sortBy(env => (env.last_deployment ? env.last_deployment.created_at : '0000')) - .reverse() - .sortBy(env => (env.isFolder ? -1 : 1)) - .value(); + return flow( + sortBy(env => (env.isFolder ? env.folderName : env.name)), + reverse, + sortBy(env => (env.last_deployment ? env.last_deployment.created_at : '0000')), + reverse, + sortBy(env => (env.isFolder ? -1 : 1)), + )(environments); }, }, }; diff --git a/app/assets/javascripts/environments/mixins/environments_mixin.js b/app/assets/javascripts/environments/mixins/environments_mixin.js index 4fadecdd3e9..73dc8c02485 100644 --- a/app/assets/javascripts/environments/mixins/environments_mixin.js +++ b/app/assets/javascripts/environments/mixins/environments_mixin.js @@ -1,7 +1,7 @@ /** * Common code between environmets app and folder view */ -import _ from 'underscore'; +import { isEqual, isFunction, omitBy } from 'lodash'; import Visibility from 'visibilityjs'; import EnvironmentsStore from 'ee_else_ce/environments/stores/environments_store'; import Poll from '../../lib/utils/poll'; @@ -54,7 +54,7 @@ export default { const response = this.filterNilValues(resp.config.params); const request = this.filterNilValues(this.requestData); - if (_.isEqual(response, request)) { + if (isEqual(response, request)) { this.store.storeAvailableCount(resp.data.available_count); this.store.storeStoppedCount(resp.data.stopped_count); this.store.storeEnvironments(resp.data.environments); @@ -64,7 +64,7 @@ export default { }, filterNilValues(obj) { - return _.omit(obj, value => _.isUndefined(value) || _.isNull(value)); + return omitBy(obj, value => value === undefined || value === null); }, /** @@ -109,7 +109,7 @@ export default { .then(() => this.fetchEnvironments()) .catch(err => { this.isLoading = false; - Flash(_.isFunction(errorMessage) ? errorMessage(err.response.data) : errorMessage); + Flash(isFunction(errorMessage) ? errorMessage(err.response.data) : errorMessage); }); } }, diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index d813d7b8225..a50d516f75e 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -44,7 +44,7 @@ class Projects::IssuesController < Projects::ApplicationController before_action do push_frontend_feature_flag(:vue_issuable_sidebar, project.group) - push_frontend_feature_flag(:save_issuable_health_status, project.group) + push_frontend_feature_flag(:save_issuable_health_status, project.group, default_enabled: true) end before_action only: :show do diff --git a/app/graphql/types/project_statistics_type.rb b/app/graphql/types/project_statistics_type.rb index c46410df6c0..e1546d31e89 100644 --- a/app/graphql/types/project_statistics_type.rb +++ b/app/graphql/types/project_statistics_type.rb @@ -6,20 +6,20 @@ module Types authorize :read_statistics - field :commit_count, GraphQL::INT_TYPE, null: false, + field :commit_count, GraphQL::FLOAT_TYPE, null: false, description: 'Commit count of the project' - field :storage_size, GraphQL::INT_TYPE, null: false, + field :storage_size, GraphQL::FLOAT_TYPE, null: false, description: 'Storage size of the project' - field :repository_size, GraphQL::INT_TYPE, null: false, + field :repository_size, GraphQL::FLOAT_TYPE, null: false, description: 'Repository size of the project' - field :lfs_objects_size, GraphQL::INT_TYPE, null: false, + field :lfs_objects_size, GraphQL::FLOAT_TYPE, null: false, description: 'Large File Storage (LFS) object size of the project' - field :build_artifacts_size, GraphQL::INT_TYPE, null: false, + field :build_artifacts_size, GraphQL::FLOAT_TYPE, null: false, description: 'Build artifacts size of the project' - field :packages_size, GraphQL::INT_TYPE, null: false, + field :packages_size, GraphQL::FLOAT_TYPE, null: false, description: 'Packages size of the project' - field :wiki_size, GraphQL::INT_TYPE, null: true, + field :wiki_size, GraphQL::FLOAT_TYPE, null: true, description: 'Wiki size of the project' end end diff --git a/app/graphql/types/root_storage_statistics_type.rb b/app/graphql/types/root_storage_statistics_type.rb index 3c471df072d..e2d85aebc48 100644 --- a/app/graphql/types/root_storage_statistics_type.rb +++ b/app/graphql/types/root_storage_statistics_type.rb @@ -6,11 +6,11 @@ module Types authorize :read_statistics - field :storage_size, GraphQL::INT_TYPE, null: false, description: 'The total storage in bytes' - field :repository_size, GraphQL::INT_TYPE, null: false, description: 'The Git repository size in bytes' - field :lfs_objects_size, GraphQL::INT_TYPE, null: false, description: 'The LFS objects size in bytes' - field :build_artifacts_size, GraphQL::INT_TYPE, null: false, description: 'The CI artifacts size in bytes' - field :packages_size, GraphQL::INT_TYPE, null: false, description: 'The packages size in bytes' - field :wiki_size, GraphQL::INT_TYPE, null: false, description: 'The wiki size in bytes' + field :storage_size, GraphQL::FLOAT_TYPE, null: false, description: 'The total storage in bytes' + field :repository_size, GraphQL::FLOAT_TYPE, null: false, description: 'The Git repository size in bytes' + field :lfs_objects_size, GraphQL::FLOAT_TYPE, null: false, description: 'The LFS objects size in bytes' + field :build_artifacts_size, GraphQL::FLOAT_TYPE, null: false, description: 'The CI artifacts size in bytes' + field :packages_size, GraphQL::FLOAT_TYPE, null: false, description: 'The packages size in bytes' + field :wiki_size, GraphQL::FLOAT_TYPE, null: false, description: 'The wiki size in bytes' end end diff --git a/app/models/container_registry/event.rb b/app/models/container_registry/event.rb new file mode 100644 index 00000000000..109fda675a2 --- /dev/null +++ b/app/models/container_registry/event.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +module ContainerRegistry + class Event + ALLOWED_ACTIONS = %w(push delete).freeze + PUSH_ACTION = 'push' + EVENT_TRACKING_CATEGORY = 'container_registry:notification' + + attr_reader :event + + def initialize(event) + @event = event + end + + def supported? + action.in?(ALLOWED_ACTIONS) + end + + def handle! + # no op + end + + def track! + tracked_target = target_tag? ? :tag : :repository + tracking_action = "#{action}_#{tracked_target}" + + if target_repository? && action_push? && !container_repository_exists? + tracking_action = "create_repository" + end + + ::Gitlab::Tracking.event(EVENT_TRACKING_CATEGORY, tracking_action) + end + + private + + def target_tag? + # There is no clear indication in the event structure when we delete a top-level manifest + # except existance of "tag" key + event['target'].has_key?('tag') + end + + def target_repository? + !target_tag? && event['target'].has_key?('repository') + end + + def action + event['action'] + end + + def action_push? + PUSH_ACTION == action + end + + def container_repository_exists? + return unless container_registry_path + + ContainerRepository.exists_by_path?(container_registry_path) + end + + def container_registry_path + path = event.dig('target', 'repository') + return unless path + + ContainerRegistry::Path.new(path) + end + end +end + +::ContainerRegistry::Event.prepend_if_ee('EE::ContainerRegistry::Event') diff --git a/app/models/container_repository.rb b/app/models/container_repository.rb index fcbfda8fbc2..b74c044b687 100644 --- a/app/models/container_repository.rb +++ b/app/models/container_repository.rb @@ -16,6 +16,13 @@ class ContainerRepository < ApplicationRecord where(project_id: Project.for_group_and_its_subgroups(group).with_container_registry.select(:id)) end + def self.exists_by_path?(path) + where( + project: path.repository_project, + name: path.repository_name + ).exists? + end + # rubocop: disable CodeReuse/ServiceClass def registry @registry ||= begin diff --git a/app/views/projects/issues/_nav_btns.html.haml b/app/views/projects/issues/_nav_btns.html.haml index 329efa0cdbf..d81089bee68 100644 --- a/app/views/projects/issues/_nav_btns.html.haml +++ b/app/views/projects/issues/_nav_btns.html.haml @@ -20,7 +20,6 @@ issue: { assignee_id: finder.assignee.try(:id), milestone_id: finder.milestones.first.try(:id) }), class: "btn btn-success", - title: _("New issue"), id: "new_issue_link" - if show_export_button diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index f7702499970..1f499dbd0a2 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -48,7 +48,7 @@ - if can_create_issue - if can_update_issue || can_report_spam %li.divider - %li= link_to 'New issue', new_project_issue_path(@project), title: 'New issue', id: 'new_issue_link' + %li= link_to 'New issue', new_project_issue_path(@project), id: 'new_issue_link' = render 'shared/issuable/close_reopen_button', issuable: @issue, can_update: can_update_issue, can_reopen: can_reopen_issue diff --git a/app/views/shared/empty_states/_issues.html.haml b/app/views/shared/empty_states/_issues.html.haml index 325e01bb5c8..eb5637acca0 100644 --- a/app/views/shared/empty_states/_issues.html.haml +++ b/app/views/shared/empty_states/_issues.html.haml @@ -20,7 +20,7 @@ = _("To widen your search, change or remove filters above") - if show_new_issue_link?(@project) .text-center - = link_to _("New issue"), new_project_issue_path(@project), class: "btn btn-success", title: _("New issue"), id: "new_issue_body_link" + = link_to _("New issue"), new_project_issue_path(@project), class: "btn btn-success", id: "new_issue_body_link" - elsif is_opened_state && opened_issues_count == 0 && closed_issues_count > 0 %h4.text-center = _("There are no open issues") @@ -28,7 +28,7 @@ = _("To keep this project going, create a new issue") - if show_new_issue_link?(@project) .text-center - = link_to _("New issue"), new_project_issue_path(@project), class: "btn btn-success", title: _("New issue"), id: "new_issue_body_link" + = link_to _("New issue"), new_project_issue_path(@project), class: "btn btn-success", id: "new_issue_body_link" - elsif is_closed_state && opened_issues_count > 0 && closed_issues_count == 0 %h4.text-center = _("There are no closed issues") @@ -42,7 +42,7 @@ - if project_select_button = render 'shared/new_project_item_select', path: 'issues/new', label: _('New issue'), type: :issues, with_feature_enabled: 'issues' - else - = link_to _('New issue'), button_path, class: 'btn btn-success', title: _('New issue'), id: 'new_issue_link' + = link_to _('New issue'), button_path, class: 'btn btn-success', id: 'new_issue_link' - if show_import_button = render 'projects/issues/import_csv/button', type: :text diff --git a/changelogs/unreleased/212452-webhooks-do-not-show-when-discussion-lock-changes.yml b/changelogs/unreleased/212452-webhooks-do-not-show-when-discussion-lock-changes.yml new file mode 100644 index 00000000000..ca379d86e5b --- /dev/null +++ b/changelogs/unreleased/212452-webhooks-do-not-show-when-discussion-lock-changes.yml @@ -0,0 +1,5 @@ +--- +title: Add `discussion_locked` to Webhook +merge_request: 28018 +author: +type: fixed diff --git a/changelogs/unreleased/212463-remove-export-fast-serialize-feature-flag.yml b/changelogs/unreleased/212463-remove-export-fast-serialize-feature-flag.yml new file mode 100644 index 00000000000..5acb10d399c --- /dev/null +++ b/changelogs/unreleased/212463-remove-export-fast-serialize-feature-flag.yml @@ -0,0 +1,5 @@ +--- +title: Remove feature flag 'export_fast_serialize' and 'export_fast_serialize_with_raw_json' +merge_request: 28037 +author: +type: performance diff --git a/changelogs/unreleased/23131-upgrade-statistics-endpoints.yml b/changelogs/unreleased/23131-upgrade-statistics-endpoints.yml new file mode 100644 index 00000000000..4db191c161e --- /dev/null +++ b/changelogs/unreleased/23131-upgrade-statistics-endpoints.yml @@ -0,0 +1,6 @@ +--- +title: > + #42671: Project and group storage statistics now support values up to 8 PiB (up from 4GiB) +merge_request: 23131 +author: Matthias van de Meent +type: fixed diff --git a/changelogs/unreleased/remove-new-issue-tooltip.yml b/changelogs/unreleased/remove-new-issue-tooltip.yml new file mode 100644 index 00000000000..1839db89474 --- /dev/null +++ b/changelogs/unreleased/remove-new-issue-tooltip.yml @@ -0,0 +1,5 @@ +--- +title: Remove new issue tooltip +merge_request: 28261 +author: Victor Wu +type: other diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql index 2f1b902825a..12da6728d03 100644 --- a/doc/api/graphql/reference/gitlab_schema.graphql +++ b/doc/api/graphql/reference/gitlab_schema.graphql @@ -6695,37 +6695,37 @@ type ProjectStatistics { """ Build artifacts size of the project """ - buildArtifactsSize: Int! + buildArtifactsSize: Float! """ Commit count of the project """ - commitCount: Int! + commitCount: Float! """ Large File Storage (LFS) object size of the project """ - lfsObjectsSize: Int! + lfsObjectsSize: Float! """ Packages size of the project """ - packagesSize: Int! + packagesSize: Float! """ Repository size of the project """ - repositorySize: Int! + repositorySize: Float! """ Storage size of the project """ - storageSize: Int! + storageSize: Float! """ Wiki size of the project """ - wikiSize: Int + wikiSize: Float } type Query { @@ -7059,32 +7059,32 @@ type RootStorageStatistics { """ The CI artifacts size in bytes """ - buildArtifactsSize: Int! + buildArtifactsSize: Float! """ The LFS objects size in bytes """ - lfsObjectsSize: Int! + lfsObjectsSize: Float! """ The packages size in bytes """ - packagesSize: Int! + packagesSize: Float! """ The Git repository size in bytes """ - repositorySize: Int! + repositorySize: Float! """ The total storage in bytes """ - storageSize: Int! + storageSize: Float! """ The wiki size in bytes """ - wikiSize: Int! + wikiSize: Float! } """ diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json index aebd9131f3d..13e506fcb6c 100644 --- a/doc/api/graphql/reference/gitlab_schema.json +++ b/doc/api/graphql/reference/gitlab_schema.json @@ -20104,7 +20104,7 @@ "name": null, "ofType": { "kind": "SCALAR", - "name": "Int", + "name": "Float", "ofType": null } }, @@ -20122,7 +20122,7 @@ "name": null, "ofType": { "kind": "SCALAR", - "name": "Int", + "name": "Float", "ofType": null } }, @@ -20140,7 +20140,7 @@ "name": null, "ofType": { "kind": "SCALAR", - "name": "Int", + "name": "Float", "ofType": null } }, @@ -20158,7 +20158,7 @@ "name": null, "ofType": { "kind": "SCALAR", - "name": "Int", + "name": "Float", "ofType": null } }, @@ -20176,7 +20176,7 @@ "name": null, "ofType": { "kind": "SCALAR", - "name": "Int", + "name": "Float", "ofType": null } }, @@ -20194,7 +20194,7 @@ "name": null, "ofType": { "kind": "SCALAR", - "name": "Int", + "name": "Float", "ofType": null } }, @@ -20209,7 +20209,7 @@ ], "type": { "kind": "SCALAR", - "name": "Int", + "name": "Float", "ofType": null }, "isDeprecated": false, @@ -21203,7 +21203,7 @@ "name": null, "ofType": { "kind": "SCALAR", - "name": "Int", + "name": "Float", "ofType": null } }, @@ -21221,7 +21221,7 @@ "name": null, "ofType": { "kind": "SCALAR", - "name": "Int", + "name": "Float", "ofType": null } }, @@ -21239,7 +21239,7 @@ "name": null, "ofType": { "kind": "SCALAR", - "name": "Int", + "name": "Float", "ofType": null } }, @@ -21257,7 +21257,7 @@ "name": null, "ofType": { "kind": "SCALAR", - "name": "Int", + "name": "Float", "ofType": null } }, @@ -21275,7 +21275,7 @@ "name": null, "ofType": { "kind": "SCALAR", - "name": "Int", + "name": "Float", "ofType": null } }, @@ -21293,7 +21293,7 @@ "name": null, "ofType": { "kind": "SCALAR", - "name": "Int", + "name": "Float", "ofType": null } }, diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index dc3638dc7bc..6b7c4925238 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -979,13 +979,13 @@ Information about pagination in a connection. | Name | Type | Description | | --- | ---- | ---------- | -| `buildArtifactsSize` | Int! | Build artifacts size of the project | -| `commitCount` | Int! | Commit count of the project | -| `lfsObjectsSize` | Int! | Large File Storage (LFS) object size of the project | -| `packagesSize` | Int! | Packages size of the project | -| `repositorySize` | Int! | Repository size of the project | -| `storageSize` | Int! | Storage size of the project | -| `wikiSize` | Int | Wiki size of the project | +| `buildArtifactsSize` | Float! | Build artifacts size of the project | +| `commitCount` | Float! | Commit count of the project | +| `lfsObjectsSize` | Float! | Large File Storage (LFS) object size of the project | +| `packagesSize` | Float! | Packages size of the project | +| `repositorySize` | Float! | Repository size of the project | +| `storageSize` | Float! | Storage size of the project | +| `wikiSize` | Float | Wiki size of the project | ## RemoveAwardEmojiPayload @@ -1047,12 +1047,12 @@ Counts of requirements by their state. | Name | Type | Description | | --- | ---- | ---------- | -| `buildArtifactsSize` | Int! | The CI artifacts size in bytes | -| `lfsObjectsSize` | Int! | The LFS objects size in bytes | -| `packagesSize` | Int! | The packages size in bytes | -| `repositorySize` | Int! | The Git repository size in bytes | -| `storageSize` | Int! | The total storage in bytes | -| `wikiSize` | Int! | The wiki size in bytes | +| `buildArtifactsSize` | Float! | The CI artifacts size in bytes | +| `lfsObjectsSize` | Float! | The LFS objects size in bytes | +| `packagesSize` | Float! | The packages size in bytes | +| `repositorySize` | Float! | The Git repository size in bytes | +| `storageSize` | Float! | The total storage in bytes | +| `wikiSize` | Float! | The wiki size in bytes | ## SentryDetailedError diff --git a/doc/user/project/integrations/webhooks.md b/doc/user/project/integrations/webhooks.md index 9423929d72f..6dd2fd3b61b 100644 --- a/doc/user/project/integrations/webhooks.md +++ b/doc/user/project/integrations/webhooks.md @@ -252,7 +252,7 @@ X-Gitlab-Event: Tag Push Hook } ``` -### Issues events +### Issue events Triggered when a new issue is created or an existing issue was updated/closed/reopened. @@ -267,10 +267,12 @@ X-Gitlab-Event: Issue Hook ```json { "object_kind": "issue", + "event_type": "issue", "user": { "name": "Administrator", "username": "root", - "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon" + "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon", + "email": "admin@example.com" }, "project": { "id": 1, @@ -284,17 +286,12 @@ X-Gitlab-Event: Issue Hook "visibility_level":20, "path_with_namespace":"gitlabhq/gitlab-test", "default_branch":"master", + "ci_config_path": null, "homepage":"http://example.com/gitlabhq/gitlab-test", "url":"http://example.com/gitlabhq/gitlab-test.git", "ssh_url":"git@example.com:gitlabhq/gitlab-test.git", "http_url":"http://example.com/gitlabhq/gitlab-test.git" }, - "repository": { - "name": "Gitlab Test", - "url": "http://example.com/gitlabhq/gitlab-test.git", - "description": "Aut reprehenderit ut est.", - "homepage": "http://example.com/gitlabhq/gitlab-test" - }, "object_attributes": { "id": 301, "title": "New API: create/update/delete file", @@ -304,14 +301,45 @@ X-Gitlab-Event: Issue Hook "project_id": 14, "created_at": "2013-12-03T17:15:43Z", "updated_at": "2013-12-03T17:15:43Z", - "position": 0, - "branch_name": null, + "updated_by_id": 1, + "last_edited_at": null, + "last_edited_by_id": null, + "relative_position": 0, "description": "Create new API for manipulations with repository", "milestone_id": null, - "state": "opened", + "state_id": 1, + "confidential": false, + "discussion_locked": true, + "due_date": null, + "moved_to_id": null, + "duplicated_to_id": null, + "time_estimate": 0, + "total_time_spent": 0, + "human_total_time_spent": null, + "human_time_estimate": null, + "weight": null, "iid": 23, "url": "http://example.com/diaspora/issues/23", - "action": "open" + "state": "opened", + "action": "open", + "labels": [{ + "id": 206, + "title": "API", + "color": "#ffffff", + "project_id": 14, + "created_at": "2013-12-03T17:15:43Z", + "updated_at": "2013-12-03T17:15:43Z", + "template": false, + "description": "API related issues", + "type": "ProjectLabel", + "group_id": 41 + }] + }, + "repository": { + "name": "Gitlab Test", + "url": "http://example.com/gitlabhq/gitlab-test.git", + "description": "Aut reprehenderit ut est.", + "homepage": "http://example.com/gitlabhq/gitlab-test" }, "assignees": [{ "name": "User1", diff --git a/doc/user/project/issues/index.md b/doc/user/project/issues/index.md index 7bca62792ac..f8608c4b711 100644 --- a/doc/user/project/issues/index.md +++ b/doc/user/project/issues/index.md @@ -182,12 +182,12 @@ To help you track the status of your issues, you can assign a status to each iss #### Enable issue health status -This feature comes with the `:save_issuable_health_status` feature flag disabled by default. However, in some cases -this feature is incompatible with old configuration. To turn on the feature while configuration is +This feature comes with the `:save_issuable_health_status` feature flag enabled by default. However, in some cases +this feature is incompatible with old configuration. To turn off the feature while configuration is migrated, ask a GitLab administrator with Rails console access to run the following command: ```ruby -Feature.enable(:save_issuable_health_status) +Feature.disable(:save_issuable_health_status) ``` ## Other Issue actions diff --git a/lib/api/api.rb b/lib/api/api.rb index 02b3fe7e03e..bc333880bbd 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -121,6 +121,7 @@ module API mount ::API::BroadcastMessages mount ::API::Commits mount ::API::CommitStatuses + mount ::API::ContainerRegistryEvent mount ::API::DeployKeys mount ::API::DeployTokens mount ::API::Deployments diff --git a/lib/api/container_registry_event.rb b/lib/api/container_registry_event.rb new file mode 100644 index 00000000000..6d93cc65336 --- /dev/null +++ b/lib/api/container_registry_event.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +module API + class ContainerRegistryEvent < Grape::API + DOCKER_DISTRIBUTION_EVENTS_V1_JSON = 'application/vnd.docker.distribution.events.v1+json' + + before { authenticate_registry_notification! } + + resource :container_registry_event do + helpers do + def authenticate_registry_notification! + secret_token = Gitlab.config.registry.notification_secret + + unauthorized! unless Devise.secure_compare(secret_token, headers['Authorization']) + end + end + + # Docker Registry sends data in a body of the request as JSON string, + # by setting 'content_type' we make Grape to parse it automatically + content_type :json, DOCKER_DISTRIBUTION_EVENTS_V1_JSON + format :json + + params do + requires :events, type: Array + end + + # This endpoint is used by Docker Registry to push a set of event + # that took place recently. + post 'events' do + params['events'].each do |raw_event| + event = ::ContainerRegistry::Event.new(raw_event) + + if event.supported? + event.handle! + event.track! + end + end + + status :ok + end + end + end +end diff --git a/lib/gitlab/hook_data/issue_builder.rb b/lib/gitlab/hook_data/issue_builder.rb index 9d9db6cf94f..f38012c9804 100644 --- a/lib/gitlab/hook_data/issue_builder.rb +++ b/lib/gitlab/hook_data/issue_builder.rb @@ -17,6 +17,7 @@ module Gitlab confidential created_at description + discussion_locked due_date id iid diff --git a/lib/gitlab/import_export/fast_hash_serializer.rb b/lib/gitlab/import_export/fast_hash_serializer.rb index c6ecf13ded8..c5fb00ed741 100644 --- a/lib/gitlab/import_export/fast_hash_serializer.rb +++ b/lib/gitlab/import_export/fast_hash_serializer.rb @@ -142,12 +142,7 @@ module Gitlab # returned by database when no `ORDER` is specified batch = batch.reorder(batch.klass.primary_key) - if Feature.enabled?(:export_fast_serialize_with_raw_json, default_enabled: true) - data.append(JSONBatchRelation.new(batch, options, preloads[key]).tap(&:raw_json)) - else - batch = batch.preload(preloads[key]) if preloads&.key?(key) - data += batch.as_json(options) - end + data.append(JSONBatchRelation.new(batch, options, preloads[key]).tap(&:raw_json)) end data diff --git a/lib/gitlab/import_export/legacy_relation_tree_saver.rb b/lib/gitlab/import_export/legacy_relation_tree_saver.rb index fe3e64358e5..cf75a2c7fa8 100644 --- a/lib/gitlab/import_export/legacy_relation_tree_saver.rb +++ b/lib/gitlab/import_export/legacy_relation_tree_saver.rb @@ -6,13 +6,9 @@ module Gitlab include Gitlab::ImportExport::CommandLineUtil def serialize(exportable, relations_tree) - if Feature.enabled?(:export_fast_serialize, default_enabled: true) - Gitlab::ImportExport::FastHashSerializer - .new(exportable, relations_tree) - .execute - else - exportable.as_json(relations_tree) - end + Gitlab::ImportExport::FastHashSerializer + .new(exportable, relations_tree) + .execute end def save(tree, dir_path, filename) diff --git a/spec/lib/gitlab/hook_data/issue_builder_spec.rb b/spec/lib/gitlab/hook_data/issue_builder_spec.rb index 168603d6fcc..1f681faa8f7 100644 --- a/spec/lib/gitlab/hook_data/issue_builder_spec.rb +++ b/spec/lib/gitlab/hook_data/issue_builder_spec.rb @@ -18,6 +18,7 @@ describe Gitlab::HookData::IssueBuilder do confidential created_at description + discussion_locked due_date id iid diff --git a/spec/lib/gitlab/import_export/group/tree_saver_spec.rb b/spec/lib/gitlab/import_export/group/tree_saver_spec.rb index 44fd49f0ac3..eb790662d8c 100644 --- a/spec/lib/gitlab/import_export/group/tree_saver_spec.rb +++ b/spec/lib/gitlab/import_export/group/tree_saver_spec.rb @@ -23,50 +23,6 @@ describe Gitlab::ImportExport::Group::TreeSaver do expect(group_tree_saver.save).to be true end - context ':export_fast_serialize feature flag checks' do - before do - expect(Gitlab::ImportExport::Reader).to receive(:new).with(shared: shared, config: group_config).and_return(reader) - expect(reader).to receive(:group_tree).and_return(group_tree) - end - - let(:reader) { instance_double('Gitlab::ImportExport::Reader') } - let(:group_config) { Gitlab::ImportExport::Config.new(config: Gitlab::ImportExport.group_config_file).to_h } - let(:group_tree) do - { - include: [{ milestones: { include: [] } }], - preload: { milestones: nil } - } - end - - context 'when :export_fast_serialize feature is enabled' do - let(:serializer) { instance_double(Gitlab::ImportExport::FastHashSerializer) } - - before do - stub_feature_flags(export_fast_serialize: true) - - expect(Gitlab::ImportExport::FastHashSerializer).to receive(:new).with(group, group_tree).and_return(serializer) - end - - it 'uses FastHashSerializer' do - expect(serializer).to receive(:execute) - - group_tree_saver.save - end - end - - context 'when :export_fast_serialize feature is disabled' do - before do - stub_feature_flags(export_fast_serialize: false) - end - - it 'is serialized via built-in `as_json`' do - expect(group).to receive(:as_json).with(group_tree).and_call_original - - group_tree_saver.save - end - end - end - # It is mostly duplicated in # `spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb` # except: diff --git a/spec/lib/gitlab/import_export/legacy_relation_tree_saver_spec.rb b/spec/lib/gitlab/import_export/legacy_relation_tree_saver_spec.rb index db77bd338e1..958865f52a0 100644 --- a/spec/lib/gitlab/import_export/legacy_relation_tree_saver_spec.rb +++ b/spec/lib/gitlab/import_export/legacy_relation_tree_saver_spec.rb @@ -8,35 +8,17 @@ describe Gitlab::ImportExport::LegacyRelationTreeSaver do let(:tree) { {} } describe '#serialize' do - context 'when :export_fast_serialize feature is enabled' do - let(:serializer) { instance_double(Gitlab::ImportExport::FastHashSerializer) } + let(:serializer) { instance_double(Gitlab::ImportExport::FastHashSerializer) } - before do - stub_feature_flags(export_fast_serialize: true) - end + it 'uses FastHashSerializer' do + expect(Gitlab::ImportExport::FastHashSerializer) + .to receive(:new) + .with(exportable, tree) + .and_return(serializer) - it 'uses FastHashSerializer' do - expect(Gitlab::ImportExport::FastHashSerializer) - .to receive(:new) - .with(exportable, tree) - .and_return(serializer) + expect(serializer).to receive(:execute) - expect(serializer).to receive(:execute) - - relation_tree_saver.serialize(exportable, tree) - end - end - - context 'when :export_fast_serialize feature is disabled' do - before do - stub_feature_flags(export_fast_serialize: false) - end - - it 'is serialized via built-in `as_json`' do - expect(exportable).to receive(:as_json).with(tree) - - relation_tree_saver.serialize(exportable, tree) - end + relation_tree_saver.serialize(exportable, tree) end end end diff --git a/spec/lib/gitlab/import_export/project/legacy_tree_saver_spec.rb b/spec/lib/gitlab/import_export/project/legacy_tree_saver_spec.rb index d4406dbc60b..e51f6888bbb 100644 --- a/spec/lib/gitlab/import_export/project/legacy_tree_saver_spec.rb +++ b/spec/lib/gitlab/import_export/project/legacy_tree_saver_spec.rb @@ -25,51 +25,6 @@ describe Gitlab::ImportExport::Project::LegacyTreeSaver do expect(project_tree_saver.save).to be true end - context ':export_fast_serialize feature flag checks' do - before do - expect(Gitlab::ImportExport::Reader).to receive(:new).with(shared: shared).and_return(reader) - expect(reader).to receive(:project_tree).and_return(project_tree) - end - - let(:serializer) { instance_double('Gitlab::ImportExport::FastHashSerializer') } - let(:reader) { instance_double('Gitlab::ImportExport::Reader') } - let(:project_tree) do - { - include: [{ issues: { include: [] } }], - preload: { issues: nil } - } - end - - context 'when :export_fast_serialize feature is enabled' do - before do - stub_feature_flags(export_fast_serialize: true) - end - - it 'uses FastHashSerializer' do - expect(Gitlab::ImportExport::FastHashSerializer) - .to receive(:new) - .with(project, project_tree) - .and_return(serializer) - - expect(serializer).to receive(:execute) - - project_tree_saver.save - end - end - - context 'when :export_fast_serialize feature is disabled' do - before do - stub_feature_flags(export_fast_serialize: false) - end - - it 'is serialized via built-in `as_json`' do - expect(project).to receive(:as_json).with(project_tree) - - project_tree_saver.save - end - end - end - # It is mostly duplicated in # `spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb` # except: diff --git a/spec/models/container_registry/event_spec.rb b/spec/models/container_registry/event_spec.rb new file mode 100644 index 00000000000..54ff218f2a8 --- /dev/null +++ b/spec/models/container_registry/event_spec.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ContainerRegistry::Event do + using RSpec::Parameterized::TableSyntax + + let_it_be(:group) { create(:group, name: 'group') } + let_it_be(:project) { create(:project, name: 'test', namespace: group) } + + describe '#supported?' do + let(:raw_event) { { 'action' => action } } + + subject { described_class.new(raw_event).supported? } + + where(:action, :supported) do + 'delete' | true + 'push' | true + 'mount' | false + 'pull' | false + end + + with_them do + it { is_expected.to eq supported } + end + end + + describe '#handle!' do + let(:raw_event) { { 'action' => 'push', 'target' => { 'mediaType' => ContainerRegistry::Client::DOCKER_DISTRIBUTION_MANIFEST_V2_TYPE } } } + + subject { described_class.new(raw_event).handle! } + + it { is_expected.to eq nil } + end + + describe '#track!' do + let_it_be(:container_repository) { create(:container_repository, name: 'container', project: project) } + + let(:raw_event) { { 'action' => action, 'target' => target } } + + subject { described_class.new(raw_event).track! } + + context 'with a respository target' do + let(:target) do + { + 'mediaType' => ContainerRegistry::Client::DOCKER_DISTRIBUTION_MANIFEST_V2_TYPE, + 'repository' => repository_path + } + end + + where(:repository_path, :action, :tracking_action) do + 'group/test/container' | 'push' | 'push_repository' + 'group/test/container' | 'delete' | 'delete_repository' + 'foo/bar' | 'push' | 'create_repository' + 'foo/bar' | 'delete' | 'delete_repository' + end + + with_them do + it 'creates a tracking event' do + expect(::Gitlab::Tracking).to receive(:event).with('container_registry:notification', tracking_action) + + subject + end + end + end + + context 'with a tag target' do + let(:target) do + { + 'mediaType' => ContainerRegistry::Client::DOCKER_DISTRIBUTION_MANIFEST_V2_TYPE, + 'repository' => repository_path, + 'tag' => 'latest' + } + end + + where(:repository_path, :action, :tracking_action) do + 'group/test/container' | 'push' | 'push_tag' + 'group/test/container' | 'delete' | 'delete_tag' + 'foo/bar' | 'push' | 'push_tag' + 'foo/bar' | 'delete' | 'delete_tag' + end + + with_them do + it 'creates a tracking event' do + expect(::Gitlab::Tracking).to receive(:event).with('container_registry:notification', tracking_action) + + subject + end + end + end + end +end diff --git a/spec/models/container_repository_spec.rb b/spec/models/container_repository_spec.rb index 5ed812652c5..5bcd9dfd396 100644 --- a/spec/models/container_repository_spec.rb +++ b/spec/models/container_repository_spec.rb @@ -29,6 +29,18 @@ describe ContainerRepository do end end + describe '.exists_by_path?' do + it 'returns true for known container repository paths' do + path = ContainerRegistry::Path.new("#{project.full_path}/#{repository.name}") + expect(described_class.exists_by_path?(path)).to be_truthy + end + + it 'returns false for unknown container repository paths' do + path = ContainerRegistry::Path.new('you/dont/know/me') + expect(described_class.exists_by_path?(path)).to be_falsey + end + end + describe '#tag' do it 'has a test tag' do expect(repository.tag('test')).not_to be_nil diff --git a/spec/requests/api/container_registry_event_spec.rb b/spec/requests/api/container_registry_event_spec.rb new file mode 100644 index 00000000000..9c144f80fd4 --- /dev/null +++ b/spec/requests/api/container_registry_event_spec.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe API::ContainerRegistryEvent do + let(:secret_token) { 'secret_token' } + let(:events) { [{ action: 'push' }] } + let(:registry_headers) { { 'Content-Type' => ::API::ContainerRegistryEvent::DOCKER_DISTRIBUTION_EVENTS_V1_JSON } } + + describe 'POST /container_registry_event/events' do + before do + allow(Gitlab.config.registry).to receive(:notification_secret) { secret_token } + end + + subject do + post api('/container_registry_event/events'), + params: { events: events }.to_json, + headers: registry_headers.merge('Authorization' => secret_token) + end + + it 'returns 200 status and events are passed to event handler' do + event = spy(:event) + allow(::ContainerRegistry::Event).to receive(:new).and_return(event) + expect(event).to receive(:supported?).and_return(true) + + subject + + expect(event).to have_received(:handle!).once + expect(event).to have_received(:track!).once + expect(response.status).to eq 200 + end + + it 'returns 401 error status when token is invalid' do + post api('/container_registry_event/events'), + params: { events: events }.to_json, + headers: registry_headers.merge('Authorization' => 'invalid_token') + + expect(response.status).to eq 401 + end + end +end diff --git a/spec/requests/api/graphql/namespace/root_storage_statistics_spec.rb b/spec/requests/api/graphql/namespace/root_storage_statistics_spec.rb index ac76d991bd4..f7e28043930 100644 --- a/spec/requests/api/graphql/namespace/root_storage_statistics_spec.rb +++ b/spec/requests/api/graphql/namespace/root_storage_statistics_spec.rb @@ -6,7 +6,7 @@ describe 'rendering namespace statistics' do include GraphqlHelpers let(:namespace) { user.namespace } - let!(:statistics) { create(:namespace_root_storage_statistics, namespace: namespace, packages_size: 5.megabytes) } + let!(:statistics) { create(:namespace_root_storage_statistics, namespace: namespace, packages_size: 5.gigabytes) } let(:user) { create(:user) } let(:query) do @@ -26,7 +26,7 @@ describe 'rendering namespace statistics' do post_graphql(query, current_user: user) expect(graphql_data['namespace']['rootStorageStatistics']).not_to be_blank - expect(graphql_data['namespace']['rootStorageStatistics']['packagesSize']).to eq(5.megabytes) + expect(graphql_data['namespace']['rootStorageStatistics']['packagesSize']).to eq(5.gigabytes) end end diff --git a/spec/requests/api/graphql/project/project_statistics_spec.rb b/spec/requests/api/graphql/project/project_statistics_spec.rb index ddee8537454..05dd5d36c26 100644 --- a/spec/requests/api/graphql/project/project_statistics_spec.rb +++ b/spec/requests/api/graphql/project/project_statistics_spec.rb @@ -6,7 +6,7 @@ describe 'rendering project statistics' do include GraphqlHelpers let(:project) { create(:project) } - let!(:project_statistics) { create(:project_statistics, project: project, packages_size: 5.megabytes) } + let!(:project_statistics) { create(:project_statistics, project: project, packages_size: 5.gigabytes) } let(:user) { create(:user) } let(:query) do @@ -28,7 +28,7 @@ describe 'rendering project statistics' do it "includes the packages size if the user can read the statistics" do post_graphql(query, current_user: user) - expect(graphql_data['project']['statistics']['packagesSize']).to eq(5.megabytes) + expect(graphql_data['project']['statistics']['packagesSize']).to eq(5.gigabytes) end context 'when the project is public' do diff --git a/spec/requests/api/project_templates_spec.rb b/spec/requests/api/project_templates_spec.rb index 50b950fb5c1..5dabce20043 100644 --- a/spec/requests/api/project_templates_spec.rb +++ b/spec/requests/api/project_templates_spec.rb @@ -3,9 +3,9 @@ require 'spec_helper' describe API::ProjectTemplates do - let(:public_project) { create(:project, :public) } - let(:private_project) { create(:project, :private) } - let(:developer) { create(:user) } + let_it_be(:public_project) { create(:project, :public) } + let_it_be(:private_project) { create(:project, :private) } + let_it_be(:developer) { create(:user) } before do private_project.add_developer(developer) |