Commit 45b4df3e authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent 7421e6f9
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
* Render modal to confirm rollback/redeploy. * Render modal to confirm rollback/redeploy.
*/ */
import _ from 'underscore'; import { escape as esc } from 'lodash';
import { GlModal } from '@gitlab/ui'; import { GlModal } from '@gitlab/ui';
import { s__, sprintf } from '~/locale'; import { s__, sprintf } from '~/locale';
...@@ -30,7 +30,7 @@ export default { ...@@ -30,7 +30,7 @@ export default {
: s__('Environments|Rollback environment %{name}?'); : s__('Environments|Rollback environment %{name}?');
return sprintf(title, { return sprintf(title, {
name: _.escape(this.environment.name), name: esc(this.environment.name),
}); });
}, },
...@@ -50,10 +50,10 @@ export default { ...@@ -50,10 +50,10 @@ export default {
}, },
modalText() { modalText() {
const linkStart = `<a class="commit-sha mr-0" href="${_.escape(this.commitUrl)}">`; const linkStart = `<a class="commit-sha mr-0" href="${esc(this.commitUrl)}">`;
const commitId = _.escape(this.commitShortSha); const commitId = esc(this.commitShortSha);
const linkEnd = '</a>'; const linkEnd = '</a>';
const name = _.escape(this.name); const name = esc(this.name);
const body = this.environment.isLastDeployment const body = this.environment.isLastDeployment
? s__( ? 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?', '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?',
......
<script> <script>
/* eslint-disable @gitlab/vue-require-i18n-strings */ /* eslint-disable @gitlab/vue-require-i18n-strings */
import _ from 'underscore'; import { isEmpty } from 'lodash';
import { GlTooltipDirective } from '@gitlab/ui'; import { GlTooltipDirective } from '@gitlab/ui';
import { __, sprintf } from '~/locale'; import { __, sprintf } from '~/locale';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
...@@ -79,7 +79,7 @@ export default { ...@@ -79,7 +79,7 @@ export default {
* @returns {Boolean} * @returns {Boolean}
*/ */
hasLastDeploymentKey() { 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 true;
} }
return false; return false;
...@@ -390,8 +390,8 @@ export default { ...@@ -390,8 +390,8 @@ export default {
deploymentHasUser() { deploymentHasUser() {
return ( return (
this.model && this.model &&
!_.isEmpty(this.model.last_deployment) && !isEmpty(this.model.last_deployment) &&
!_.isEmpty(this.model.last_deployment.user) !isEmpty(this.model.last_deployment.user)
); );
}, },
...@@ -404,8 +404,8 @@ export default { ...@@ -404,8 +404,8 @@ export default {
deploymentUser() { deploymentUser() {
if ( if (
this.model && this.model &&
!_.isEmpty(this.model.last_deployment) && !isEmpty(this.model.last_deployment) &&
!_.isEmpty(this.model.last_deployment.user) !isEmpty(this.model.last_deployment.user)
) { ) {
return this.model.last_deployment.user; return this.model.last_deployment.user;
} }
...@@ -431,8 +431,8 @@ export default { ...@@ -431,8 +431,8 @@ export default {
shouldRenderBuildName() { shouldRenderBuildName() {
return ( return (
!this.isFolder && !this.isFolder &&
!_.isEmpty(this.model.last_deployment) && !isEmpty(this.model.last_deployment) &&
!_.isEmpty(this.model.last_deployment.deployable) !isEmpty(this.model.last_deployment.deployable)
); );
}, },
...@@ -473,7 +473,7 @@ export default { ...@@ -473,7 +473,7 @@ export default {
shouldRenderDeploymentID() { shouldRenderDeploymentID() {
return ( return (
!this.isFolder && !this.isFolder &&
!_.isEmpty(this.model.last_deployment) && !isEmpty(this.model.last_deployment) &&
this.model.last_deployment.iid !== undefined this.model.last_deployment.iid !== undefined
); );
}, },
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
* Render environments table. * Render environments table.
*/ */
import { GlLoadingIcon } from '@gitlab/ui'; 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 environmentTableMixin from 'ee_else_ce/environments/mixins/environments_table_mixin';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
...@@ -102,13 +102,13 @@ export default { ...@@ -102,13 +102,13 @@ export default {
* 4. Reverse (last deployment descending, name ascending), * 4. Reverse (last deployment descending, name ascending),
* 5. Put folders first. * 5. Put folders first.
*/ */
return _.chain(environments) return flow(
.sortBy(env => (env.isFolder ? env.folderName : env.name)) sortBy(env => (env.isFolder ? env.folderName : env.name)),
.reverse() reverse,
.sortBy(env => (env.last_deployment ? env.last_deployment.created_at : '0000')) sortBy(env => (env.last_deployment ? env.last_deployment.created_at : '0000')),
.reverse() reverse,
.sortBy(env => (env.isFolder ? -1 : 1)) sortBy(env => (env.isFolder ? -1 : 1)),
.value(); )(environments);
}, },
}, },
}; };
......
/** /**
* Common code between environmets app and folder view * Common code between environmets app and folder view
*/ */
import _ from 'underscore'; import { isEqual, isFunction, omitBy } from 'lodash';
import Visibility from 'visibilityjs'; import Visibility from 'visibilityjs';
import EnvironmentsStore from 'ee_else_ce/environments/stores/environments_store'; import EnvironmentsStore from 'ee_else_ce/environments/stores/environments_store';
import Poll from '../../lib/utils/poll'; import Poll from '../../lib/utils/poll';
...@@ -54,7 +54,7 @@ export default { ...@@ -54,7 +54,7 @@ export default {
const response = this.filterNilValues(resp.config.params); const response = this.filterNilValues(resp.config.params);
const request = this.filterNilValues(this.requestData); const request = this.filterNilValues(this.requestData);
if (_.isEqual(response, request)) { if (isEqual(response, request)) {
this.store.storeAvailableCount(resp.data.available_count); this.store.storeAvailableCount(resp.data.available_count);
this.store.storeStoppedCount(resp.data.stopped_count); this.store.storeStoppedCount(resp.data.stopped_count);
this.store.storeEnvironments(resp.data.environments); this.store.storeEnvironments(resp.data.environments);
...@@ -64,7 +64,7 @@ export default { ...@@ -64,7 +64,7 @@ export default {
}, },
filterNilValues(obj) { filterNilValues(obj) {
return _.omit(obj, value => _.isUndefined(value) || _.isNull(value)); return omitBy(obj, value => value === undefined || value === null);
}, },
/** /**
...@@ -109,7 +109,7 @@ export default { ...@@ -109,7 +109,7 @@ export default {
.then(() => this.fetchEnvironments()) .then(() => this.fetchEnvironments())
.catch(err => { .catch(err => {
this.isLoading = false; this.isLoading = false;
Flash(_.isFunction(errorMessage) ? errorMessage(err.response.data) : errorMessage); Flash(isFunction(errorMessage) ? errorMessage(err.response.data) : errorMessage);
}); });
} }
}, },
......
...@@ -44,7 +44,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -44,7 +44,7 @@ class Projects::IssuesController < Projects::ApplicationController
before_action do before_action do
push_frontend_feature_flag(:vue_issuable_sidebar, project.group) 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 end
before_action only: :show do before_action only: :show do
......
...@@ -6,20 +6,20 @@ module Types ...@@ -6,20 +6,20 @@ module Types
authorize :read_statistics 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' 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' 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' 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' 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' 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' 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' description: 'Wiki size of the project'
end end
end end
...@@ -6,11 +6,11 @@ module Types ...@@ -6,11 +6,11 @@ module Types
authorize :read_statistics authorize :read_statistics
field :storage_size, GraphQL::INT_TYPE, null: false, description: 'The total storage in bytes' field :storage_size, GraphQL::FLOAT_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 :repository_size, GraphQL::FLOAT_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 :lfs_objects_size, GraphQL::FLOAT_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 :build_artifacts_size, GraphQL::FLOAT_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 :packages_size, GraphQL::FLOAT_TYPE, null: false, description: 'The packages size in bytes'
field :wiki_size, GraphQL::INT_TYPE, null: false, description: 'The wiki size in bytes' field :wiki_size, GraphQL::FLOAT_TYPE, null: false, description: 'The wiki size in bytes'
end end
end end
# 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')
...@@ -16,6 +16,13 @@ class ContainerRepository < ApplicationRecord ...@@ -16,6 +16,13 @@ class ContainerRepository < ApplicationRecord
where(project_id: Project.for_group_and_its_subgroups(group).with_container_registry.select(:id)) where(project_id: Project.for_group_and_its_subgroups(group).with_container_registry.select(:id))
end end
def self.exists_by_path?(path)
where(
project: path.repository_project,
name: path.repository_name
).exists?
end
# rubocop: disable CodeReuse/ServiceClass # rubocop: disable CodeReuse/ServiceClass
def registry def registry
@registry ||= begin @registry ||= begin
......
...@@ -20,7 +20,6 @@ ...@@ -20,7 +20,6 @@
issue: { assignee_id: finder.assignee.try(:id), issue: { assignee_id: finder.assignee.try(:id),
milestone_id: finder.milestones.first.try(:id) }), milestone_id: finder.milestones.first.try(:id) }),
class: "btn btn-success", class: "btn btn-success",
title: _("New issue"),
id: "new_issue_link" id: "new_issue_link"
- if show_export_button - if show_export_button
......
...@@ -48,7 +48,7 @@ ...@@ -48,7 +48,7 @@
- if can_create_issue - if can_create_issue
- if can_update_issue || can_report_spam - if can_update_issue || can_report_spam
%li.divider %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 = render 'shared/issuable/close_reopen_button', issuable: @issue, can_update: can_update_issue, can_reopen: can_reopen_issue
......
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
= _("To widen your search, change or remove filters above") = _("To widen your search, change or remove filters above")
- if show_new_issue_link?(@project) - if show_new_issue_link?(@project)
.text-center .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 - elsif is_opened_state && opened_issues_count == 0 && closed_issues_count > 0
%h4.text-center %h4.text-center
= _("There are no open issues") = _("There are no open issues")
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
= _("To keep this project going, create a new issue") = _("To keep this project going, create a new issue")
- if show_new_issue_link?(@project) - if show_new_issue_link?(@project)
.text-center .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 - elsif is_closed_state && opened_issues_count > 0 && closed_issues_count == 0
%h4.text-center %h4.text-center
= _("There are no closed issues") = _("There are no closed issues")
...@@ -42,7 +42,7 @@ ...@@ -42,7 +42,7 @@
- if project_select_button - if project_select_button
= render 'shared/new_project_item_select', path: 'issues/new', label: _('New issue'), type: :issues, with_feature_enabled: 'issues' = render 'shared/new_project_item_select', path: 'issues/new', label: _('New issue'), type: :issues, with_feature_enabled: 'issues'
- else - 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 - if show_import_button
= render 'projects/issues/import_csv/button', type: :text = render 'projects/issues/import_csv/button', type: :text
......
---
title: Add `discussion_locked` to Webhook
merge_request: 28018
author:
type: fixed
---
title: Remove feature flag 'export_fast_serialize' and 'export_fast_serialize_with_raw_json'
merge_request: 28037
author:
type: performance
---
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
---
title: Remove new issue tooltip
merge_request: 28261
author: Victor Wu
type: other
...@@ -6695,37 +6695,37 @@ type ProjectStatistics { ...@@ -6695,37 +6695,37 @@ type ProjectStatistics {
""" """
Build artifacts size of the project Build artifacts size of the project
""" """
buildArtifactsSize: Int! buildArtifactsSize: Float!
""" """
Commit count of the project Commit count of the project
""" """
commitCount: Int! commitCount: Float!
""" """
Large File Storage (LFS) object size of the project Large File Storage (LFS) object size of the project
""" """
lfsObjectsSize: Int! lfsObjectsSize: Float!
""" """
Packages size of the project Packages size of the project
""" """
packagesSize: Int! packagesSize: Float!
""" """
Repository size of the project Repository size of the project
""" """
repositorySize: Int! repositorySize: Float!
""" """
Storage size of the project Storage size of the project
""" """
storageSize: Int! storageSize: Float!
""" """
Wiki size of the project Wiki size of the project
""" """
wikiSize: Int wikiSize: Float
} }
type Query { type Query {
...@@ -7059,32 +7059,32 @@ type RootStorageStatistics { ...@@ -7059,32 +7059,32 @@ type RootStorageStatistics {
""" """
The CI artifacts size in bytes The CI artifacts size in bytes
""" """
buildArtifactsSize: Int! buildArtifactsSize: Float!
""" """
The LFS objects size in bytes The LFS objects size in bytes
""" """
lfsObjectsSize: Int! lfsObjectsSize: Float!
""" """
The packages size in bytes The packages size in bytes
""" """
packagesSize: Int! packagesSize: Float!
""" """
The Git repository size in bytes The Git repository size in bytes
""" """
repositorySize: Int! repositorySize: Float!
""" """
The total storage in bytes The total storage in bytes
""" """
storageSize: Int! storageSize: Float!
""" """
The wiki size in bytes The wiki size in bytes
""" """
wikiSize: Int! wikiSize: Float!
} }
""" """
......
...@@ -20104,7 +20104,7 @@ ...@@ -20104,7 +20104,7 @@
"name": null, "name": null,
"ofType": { "ofType": {
"kind": "SCALAR", "kind": "SCALAR",
"name": "Int", "name": "Float",
"ofType": null "ofType": null
} }
}, },
...@@ -20122,7 +20122,7 @@ ...@@ -20122,7 +20122,7 @@
"name": null, "name": null,
"ofType": { "ofType": {
"kind": "SCALAR", "kind": "SCALAR",
"name": "Int", "name": "Float",
"ofType": null "ofType": null
} }
}, },
...@@ -20140,7 +20140,7 @@ ...@@ -20140,7 +20140,7 @@
"name": null, "name": null,
"ofType": { "ofType": {
"kind": "SCALAR", "kind": "SCALAR",
"name": "Int", "name": "Float",
"ofType": null "ofType": null
} }
}, },
...@@ -20158,7 +20158,7 @@ ...@@ -20158,7 +20158,7 @@
"name": null, "name": null,
"ofType": { "ofType": {
"kind": "SCALAR", "kind": "SCALAR",
"name": "Int", "name": "Float",
"ofType": null "ofType": null
} }
}, },
...@@ -20176,7 +20176,7 @@ ...@@ -20176,7 +20176,7 @@
"name": null, "name": null,
"ofType": { "ofType": {
"kind": "SCALAR", "kind": "SCALAR",
"name": "Int", "name": "Float",
"ofType": null "ofType": null
} }
}, },
...@@ -20194,7 +20194,7 @@ ...@@ -20194,7 +20194,7 @@
"name": null, "name": null,
"ofType": { "ofType": {
"kind": "SCALAR", "kind": "SCALAR",
"name": "Int", "name": "Float",
"ofType": null "ofType": null
} }
}, },
...@@ -20209,7 +20209,7 @@ ...@@ -20209,7 +20209,7 @@
], ],
"type": { "type": {
"kind": "SCALAR", "kind": "SCALAR",
"name": "Int", "name": "Float",
"ofType": null "ofType": null
}, },
"isDeprecated": false, "isDeprecated": false,
...@@ -21203,7 +21203,7 @@ ...@@ -21203,7 +21203,7 @@
"name": null, "name": null,
"ofType": { "ofType": {
"kind": "SCALAR", "kind": "SCALAR",
"name": "Int", "name": "Float",
"ofType": null "ofType": null
} }
}, },
...@@ -21221,7 +21221,7 @@ ...@@ -21221,7 +21221,7 @@
"name": null, "name": null,
"ofType": { "ofType": {
"kind": "SCALAR", "kind": "SCALAR",
"name": "Int", "name": "Float",
"ofType": null "ofType": null
} }
}, },
...@@ -21239,7 +21239,7 @@ ...@@ -21239,7 +21239,7 @@
"name": null, "name": null,
"ofType": { "ofType": {
"kind": "SCALAR", "kind": "SCALAR",
"name": "Int", "name": "Float",
"ofType": null "ofType": null
} }
}, },
...@@ -21257,7 +21257,7 @@ ...@@ -21257,7 +21257,7 @@
"name": null, "name": null,
"ofType": { "ofType": {
"kind": "SCALAR", "kind": "SCALAR",
"name": "Int", "name": "Float",
"ofType": null "ofType": null
} }
}, },
...@@ -21275,7 +21275,7 @@ ...@@ -21275,7 +21275,7 @@
"name": null, "name": null,
"ofType": { "ofType": {
"kind": "SCALAR", "kind": "SCALAR",
"name": "Int", "name": "Float",
"ofType": null "ofType": null
} }
}, },
...@@ -21293,7 +21293,7 @@ ...@@ -21293,7 +21293,7 @@
"name": null, "name": null,
"ofType": { "ofType": {
"kind": "SCALAR", "kind": "SCALAR",
"name": "Int", "name": "Float",
"ofType": null "ofType": null
} }
}, },
......
...@@ -979,13 +979,13 @@ Information about pagination in a connection. ...@@ -979,13 +979,13 @@ Information about pagination in a connection.
| Name | Type | Description | | Name | Type | Description |
| --- | ---- | ---------- | | --- | ---- | ---------- |
| `buildArtifactsSize` | Int! | Build artifacts size of the project | | `buildArtifactsSize` | Float! | Build artifacts size of the project |
| `commitCount` | Int! | Commit count of the project | | `commitCount` | Float! | Commit count of the project |
| `lfsObjectsSize` | Int! | Large File Storage (LFS) object size of the project | | `lfsObjectsSize` | Float! | Large File Storage (LFS) object size of the project |
| `packagesSize` | Int! | Packages size of the project | | `packagesSize` | Float! | Packages size of the project |
| `repositorySize` | Int! | Repository size of the project | | `repositorySize` | Float! | Repository size of the project |
| `storageSize` | Int! | Storage size of the project | | `storageSize` | Float! | Storage size of the project |
| `wikiSize` | Int | Wiki size of the project | | `wikiSize` | Float | Wiki size of the project |
## RemoveAwardEmojiPayload ## RemoveAwardEmojiPayload
...@@ -1047,12 +1047,12 @@ Counts of requirements by their state. ...@@ -1047,12 +1047,12 @@ Counts of requirements by their state.
| Name | Type | Description | | Name | Type | Description |
| --- | ---- | ---------- | | --- | ---- | ---------- |
| `buildArtifactsSize` | Int! | The CI artifacts size in bytes | | `buildArtifactsSize` | Float! | The CI artifacts size in bytes |
| `lfsObjectsSize` | Int! | The LFS objects size in bytes | | `lfsObjectsSize` | Float! | The LFS objects size in bytes |
| `packagesSize` | Int! | The packages size in bytes | | `packagesSize` | Float! | The packages size in bytes |
| `repositorySize` | Int! | The Git repository size in bytes | | `repositorySize` | Float! | The Git repository size in bytes |
| `storageSize` | Int! | The total storage in bytes | | `storageSize` | Float! | The total storage in bytes |
| `wikiSize` | Int! | The wiki size in bytes | | `wikiSize` | Float! | The wiki size in bytes |
## SentryDetailedError ## SentryDetailedError
......
...@@ -252,7 +252,7 @@ X-Gitlab-Event: Tag Push Hook ...@@ -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. Triggered when a new issue is created or an existing issue was updated/closed/reopened.
...@@ -267,10 +267,12 @@ X-Gitlab-Event: Issue Hook ...@@ -267,10 +267,12 @@ X-Gitlab-Event: Issue Hook
```json ```json
{ {
"object_kind": "issue", "object_kind": "issue",
"event_type": "issue",
"user": { "user": {
"name": "Administrator", "name": "Administrator",
"username": "root", "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": { "project": {
"id": 1, "id": 1,
...@@ -284,17 +286,12 @@ X-Gitlab-Event: Issue Hook ...@@ -284,17 +286,12 @@ X-Gitlab-Event: Issue Hook
"visibility_level":20, "visibility_level":20,
"path_with_namespace":"gitlabhq/gitlab-test", "path_with_namespace":"gitlabhq/gitlab-test",
"default_branch":"master", "default_branch":"master",
"ci_config_path": null,
"homepage":"http://example.com/gitlabhq/gitlab-test", "homepage":"http://example.com/gitlabhq/gitlab-test",
"url":"http://example.com/gitlabhq/gitlab-test.git", "url":"http://example.com/gitlabhq/gitlab-test.git",
"ssh_url":"git@example.com:gitlabhq/gitlab-test.git", "ssh_url":"git@example.com:gitlabhq/gitlab-test.git",
"http_url":"http://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": { "object_attributes": {
"id": 301, "id": 301,
"title": "New API: create/update/delete file", "title": "New API: create/update/delete file",
...@@ -304,14 +301,45 @@ X-Gitlab-Event: Issue Hook ...@@ -304,14 +301,45 @@ X-Gitlab-Event: Issue Hook
"project_id": 14, "project_id": 14,
"created_at": "2013-12-03T17:15:43Z", "created_at": "2013-12-03T17:15:43Z",
"updated_at": "2013-12-03T17:15:43Z", "updated_at": "2013-12-03T17:15:43Z",
"position": 0, "updated_by_id": 1,
"branch_name": null, "last_edited_at": null,
"last_edited_by_id": null,
"relative_position": 0,
"description": "Create new API for manipulations with repository", "description": "Create new API for manipulations with repository",
"milestone_id": null, "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, "iid": 23,
"url": "http://example.com/diaspora/issues/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": [{ "assignees": [{
"name": "User1", "name": "User1",
......
...@@ -182,12 +182,12 @@ To help you track the status of your issues, you can assign a status to each iss ...@@ -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 #### Enable issue health status
This feature comes with the `:save_issuable_health_status` feature flag disabled by default. However, in some cases 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 on the feature while configuration is 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: migrated, ask a GitLab administrator with Rails console access to run the following command:
```ruby ```ruby
Feature.enable(:save_issuable_health_status) Feature.disable(:save_issuable_health_status)
``` ```
## Other Issue actions ## Other Issue actions
......
...@@ -121,6 +121,7 @@ module API ...@@ -121,6 +121,7 @@ module API
mount ::API::BroadcastMessages mount ::API::BroadcastMessages
mount ::API::Commits mount ::API::Commits
mount ::API::CommitStatuses mount ::API::CommitStatuses
mount ::API::ContainerRegistryEvent
mount ::API::DeployKeys mount ::API::DeployKeys
mount ::API::DeployTokens mount ::API::DeployTokens
mount ::API::Deployments mount ::API::Deployments
......
# 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
...@@ -17,6 +17,7 @@ module Gitlab ...@@ -17,6 +17,7 @@ module Gitlab
confidential confidential
created_at created_at
description description
discussion_locked
due_date due_date
id id
iid iid
......
...@@ -142,12 +142,7 @@ module Gitlab ...@@ -142,12 +142,7 @@ module Gitlab
# returned by database when no `ORDER` is specified # returned by database when no `ORDER` is specified
batch = batch.reorder(batch.klass.primary_key) 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)) 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
end end
data data
......
...@@ -6,13 +6,9 @@ module Gitlab ...@@ -6,13 +6,9 @@ module Gitlab
include Gitlab::ImportExport::CommandLineUtil include Gitlab::ImportExport::CommandLineUtil
def serialize(exportable, relations_tree) def serialize(exportable, relations_tree)
if Feature.enabled?(:export_fast_serialize, default_enabled: true)
Gitlab::ImportExport::FastHashSerializer Gitlab::ImportExport::FastHashSerializer
.new(exportable, relations_tree) .new(exportable, relations_tree)
.execute .execute
else
exportable.as_json(relations_tree)
end
end end
def save(tree, dir_path, filename) def save(tree, dir_path, filename)
......
...@@ -18,6 +18,7 @@ describe Gitlab::HookData::IssueBuilder do ...@@ -18,6 +18,7 @@ describe Gitlab::HookData::IssueBuilder do
confidential confidential
created_at created_at
description description
discussion_locked
due_date due_date
id id
iid iid
......
...@@ -23,50 +23,6 @@ describe Gitlab::ImportExport::Group::TreeSaver do ...@@ -23,50 +23,6 @@ describe Gitlab::ImportExport::Group::TreeSaver do
expect(group_tree_saver.save).to be true expect(group_tree_saver.save).to be true
end 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 # It is mostly duplicated in
# `spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb` # `spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb`
# except: # except:
......
...@@ -8,13 +8,8 @@ describe Gitlab::ImportExport::LegacyRelationTreeSaver do ...@@ -8,13 +8,8 @@ describe Gitlab::ImportExport::LegacyRelationTreeSaver do
let(:tree) { {} } let(:tree) { {} }
describe '#serialize' do 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 it 'uses FastHashSerializer' do
expect(Gitlab::ImportExport::FastHashSerializer) expect(Gitlab::ImportExport::FastHashSerializer)
.to receive(:new) .to receive(:new)
...@@ -26,17 +21,4 @@ describe Gitlab::ImportExport::LegacyRelationTreeSaver do ...@@ -26,17 +21,4 @@ describe Gitlab::ImportExport::LegacyRelationTreeSaver do
relation_tree_saver.serialize(exportable, tree) relation_tree_saver.serialize(exportable, tree)
end end
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
end
end
end end
...@@ -25,51 +25,6 @@ describe Gitlab::ImportExport::Project::LegacyTreeSaver do ...@@ -25,51 +25,6 @@ describe Gitlab::ImportExport::Project::LegacyTreeSaver do
expect(project_tree_saver.save).to be true expect(project_tree_saver.save).to be true
end 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 # It is mostly duplicated in
# `spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb` # `spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb`
# except: # except:
......
# 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
...@@ -29,6 +29,18 @@ describe ContainerRepository do ...@@ -29,6 +29,18 @@ describe ContainerRepository do
end end
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 describe '#tag' do
it 'has a test tag' do it 'has a test tag' do
expect(repository.tag('test')).not_to be_nil expect(repository.tag('test')).not_to be_nil
......
# 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
...@@ -6,7 +6,7 @@ describe 'rendering namespace statistics' do ...@@ -6,7 +6,7 @@ describe 'rendering namespace statistics' do
include GraphqlHelpers include GraphqlHelpers
let(:namespace) { user.namespace } 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(:user) { create(:user) }
let(:query) do let(:query) do
...@@ -26,7 +26,7 @@ describe 'rendering namespace statistics' do ...@@ -26,7 +26,7 @@ describe 'rendering namespace statistics' do
post_graphql(query, current_user: user) post_graphql(query, current_user: user)
expect(graphql_data['namespace']['rootStorageStatistics']).not_to be_blank 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
end end
......
...@@ -6,7 +6,7 @@ describe 'rendering project statistics' do ...@@ -6,7 +6,7 @@ describe 'rendering project statistics' do
include GraphqlHelpers include GraphqlHelpers
let(:project) { create(:project) } 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(:user) { create(:user) }
let(:query) do let(:query) do
...@@ -28,7 +28,7 @@ describe 'rendering project statistics' do ...@@ -28,7 +28,7 @@ describe 'rendering project statistics' do
it "includes the packages size if the user can read the statistics" do it "includes the packages size if the user can read the statistics" do
post_graphql(query, current_user: user) 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 end
context 'when the project is public' do context 'when the project is public' do
......
...@@ -3,9 +3,9 @@ ...@@ -3,9 +3,9 @@
require 'spec_helper' require 'spec_helper'
describe API::ProjectTemplates do describe API::ProjectTemplates do
let(:public_project) { create(:project, :public) } let_it_be(:public_project) { create(:project, :public) }
let(:private_project) { create(:project, :private) } let_it_be(:private_project) { create(:project, :private) }
let(:developer) { create(:user) } let_it_be(:developer) { create(:user) }
before do before do
private_project.add_developer(developer) private_project.add_developer(developer)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment