Commit 60e3a879 authored by Pavel Shutsin's avatar Pavel Shutsin

Rename DevOpsAdoptionSegment

Refactors API and other code to use
`EnabledNamespace` term instead of segment

Changelog: other
MR: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/62834
EE: true
parent ec2587c6
......@@ -83,9 +83,9 @@ Fields related to design management.
Returns [`DesignManagement!`](#designmanagement).
### `Query.devopsAdoptionSegments`
### `Query.devopsAdoptionEnabledNamespaces`
Get configured DevOps adoption segments on the instance. **BETA** This endpoint is subject to change without notice.
Get configured DevOps adoption namespaces. **BETA** This endpoint is subject to change without notice.
Returns [`DevopsAdoptionSegmentConnection`](#devopsadoptionsegmentconnection).
......@@ -97,7 +97,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="querydevopsadoptionsegmentsdisplaynamespaceid"></a>`displayNamespaceId` | [`NamespaceID`](#namespaceid) | Filter by display namespace. |
| <a id="querydevopsadoptionenablednamespacesdisplaynamespaceid"></a>`displayNamespaceId` | [`NamespaceID`](#namespaceid) | Filter by display namespace. |
### `Query.echo`
......@@ -758,27 +758,27 @@ Input type: `BoardListUpdateLimitMetricsInput`
| <a id="mutationboardlistupdatelimitmetricserrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationboardlistupdatelimitmetricslist"></a>`list` | [`BoardList`](#boardlist) | The updated list. |
### `Mutation.bulkFindOrCreateDevopsAdoptionSegments`
### `Mutation.bulkEnableDevopsAdoptionNamespaces`
**BETA** This endpoint is subject to change without notice.
Input type: `BulkFindOrCreateDevopsAdoptionSegmentsInput`
Input type: `BulkEnableDevopsAdoptionNamespacesInput`
#### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationbulkfindorcreatedevopsadoptionsegmentsclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationbulkfindorcreatedevopsadoptionsegmentsdisplaynamespaceid"></a>`displayNamespaceId` | [`NamespaceID`](#namespaceid) | Display namespace ID. |
| <a id="mutationbulkfindorcreatedevopsadoptionsegmentsnamespaceids"></a>`namespaceIds` | [`[NamespaceID!]!`](#namespaceid) | List of Namespace IDs for the segments. |
| <a id="mutationbulkenabledevopsadoptionnamespacesclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationbulkenabledevopsadoptionnamespacesdisplaynamespaceid"></a>`displayNamespaceId` | [`NamespaceID`](#namespaceid) | Display namespace ID. |
| <a id="mutationbulkenabledevopsadoptionnamespacesnamespaceids"></a>`namespaceIds` | [`[NamespaceID!]!`](#namespaceid) | List of Namespace IDs. |
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationbulkfindorcreatedevopsadoptionsegmentsclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationbulkfindorcreatedevopsadoptionsegmentserrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationbulkfindorcreatedevopsadoptionsegmentssegments"></a>`segments` | [`[DevopsAdoptionSegment!]`](#devopsadoptionsegment) | Created segments after mutation. |
| <a id="mutationbulkenabledevopsadoptionnamespacesclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationbulkenabledevopsadoptionnamespacesenablednamespaces"></a>`enabledNamespaces` | [`[DevopsAdoptionSegment!]`](#devopsadoptionsegment) | Enabled namespaces after mutation. |
| <a id="mutationbulkenabledevopsadoptionnamespaceserrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
### `Mutation.ciCdSettingsUpdate`
......@@ -1094,28 +1094,6 @@ Input type: `CreateCustomEmojiInput`
| <a id="mutationcreatecustomemojicustomemoji"></a>`customEmoji` | [`CustomEmoji`](#customemoji) | The new custom emoji. |
| <a id="mutationcreatecustomemojierrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
### `Mutation.createDevopsAdoptionSegment`
**BETA** This endpoint is subject to change without notice.
Input type: `CreateDevopsAdoptionSegmentInput`
#### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationcreatedevopsadoptionsegmentclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationcreatedevopsadoptionsegmentdisplaynamespaceid"></a>`displayNamespaceId` | [`NamespaceID`](#namespaceid) | Display namespace ID. |
| <a id="mutationcreatedevopsadoptionsegmentnamespaceid"></a>`namespaceId` | [`NamespaceID!`](#namespaceid) | Namespace ID to set for the segment. |
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationcreatedevopsadoptionsegmentclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationcreatedevopsadoptionsegmenterrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationcreatedevopsadoptionsegmentsegment"></a>`segment` | [`DevopsAdoptionSegment`](#devopsadoptionsegment) | The segment after mutation. |
### `Mutation.createDiffNote`
Input type: `CreateDiffNoteInput`
......@@ -1678,26 +1656,6 @@ Input type: `DeleteAnnotationInput`
| <a id="mutationdeleteannotationclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationdeleteannotationerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
### `Mutation.deleteDevopsAdoptionSegment`
**BETA** This endpoint is subject to change without notice.
Input type: `DeleteDevopsAdoptionSegmentInput`
#### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationdeletedevopsadoptionsegmentclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationdeletedevopsadoptionsegmentid"></a>`id` | [`[AnalyticsDevopsAdoptionSegmentID!]!`](#analyticsdevopsadoptionsegmentid) | One or many IDs of the segments to delete. |
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationdeletedevopsadoptionsegmentclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationdeletedevopsadoptionsegmenterrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
### `Mutation.designManagementDelete`
Input type: `DesignManagementDeleteInput`
......@@ -1914,6 +1872,26 @@ Input type: `DestroySnippetInput`
| <a id="mutationdestroysnippeterrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationdestroysnippetsnippet"></a>`snippet` | [`Snippet`](#snippet) | The snippet after mutation. |
### `Mutation.disableDevopsAdoptionNamespace`
**BETA** This endpoint is subject to change without notice.
Input type: `DisableDevopsAdoptionNamespaceInput`
#### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationdisabledevopsadoptionnamespaceclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationdisabledevopsadoptionnamespaceid"></a>`id` | [`[AnalyticsDevopsAdoptionEnabledNamespaceID!]!`](#analyticsdevopsadoptionenablednamespaceid) | One or many IDs of the enabled namespaces to disable. |
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationdisabledevopsadoptionnamespaceclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationdisabledevopsadoptionnamespaceerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
### `Mutation.discussionToggleResolve`
Toggles the resolved state of a discussion.
......@@ -1961,6 +1939,28 @@ Input type: `DismissVulnerabilityInput`
| <a id="mutationdismissvulnerabilityerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationdismissvulnerabilityvulnerability"></a>`vulnerability` | [`Vulnerability`](#vulnerability) | The vulnerability after dismissal. |
### `Mutation.enableDevopsAdoptionNamespace`
**BETA** This endpoint is subject to change without notice.
Input type: `EnableDevopsAdoptionNamespaceInput`
#### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationenabledevopsadoptionnamespaceclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationenabledevopsadoptionnamespacedisplaynamespaceid"></a>`displayNamespaceId` | [`NamespaceID`](#namespaceid) | Display namespace ID. |
| <a id="mutationenabledevopsadoptionnamespacenamespaceid"></a>`namespaceId` | [`NamespaceID!`](#namespaceid) | Namespace ID. |
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationenabledevopsadoptionnamespaceclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationenabledevopsadoptionnamespaceenablednamespace"></a>`enabledNamespace` | [`DevopsAdoptionSegment`](#devopsadoptionsegment) | Enabled namespace after mutation. |
| <a id="mutationenabledevopsadoptionnamespaceerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
### `Mutation.environmentsCanaryIngressUpdate`
Input type: `EnvironmentsCanaryIngressUpdateInput`
......@@ -8271,15 +8271,15 @@ four standard [pagination arguments](#connection-pagination-arguments):
### `DevopsAdoptionSegment`
Segment.
Enabled namespace for DevopsAdoption.
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="devopsadoptionsegmentdisplaynamespace"></a>`displayNamespace` | [`Namespace`](#namespace) | Namespace where data should be displayed. |
| <a id="devopsadoptionsegmentid"></a>`id` | [`ID!`](#id) | ID of the segment. |
| <a id="devopsadoptionsegmentlatestsnapshot"></a>`latestSnapshot` | [`DevopsAdoptionSnapshot`](#devopsadoptionsnapshot) | The latest adoption metrics for the segment. |
| <a id="devopsadoptionsegmentid"></a>`id` | [`ID!`](#id) | ID of the enabled namespace. |
| <a id="devopsadoptionsegmentlatestsnapshot"></a>`latestSnapshot` | [`DevopsAdoptionSnapshot`](#devopsadoptionsnapshot) | The latest adoption metrics for the enabled namespace. |
| <a id="devopsadoptionsegmentnamespace"></a>`namespace` | [`Namespace`](#namespace) | Namespace which should be calculated. |
### `DevopsAdoptionSnapshot`
......@@ -14997,11 +14997,11 @@ A `AlertManagementHttpIntegrationID` is a global ID. It is encoded as a string.
An example `AlertManagementHttpIntegrationID` is: `"gid://gitlab/AlertManagement::HttpIntegration/1"`.
### `AnalyticsDevopsAdoptionSegmentID`
### `AnalyticsDevopsAdoptionEnabledNamespaceID`
A `AnalyticsDevopsAdoptionSegmentID` is a global ID. It is encoded as a string.
A `AnalyticsDevopsAdoptionEnabledNamespaceID` is a global ID. It is encoded as a string.
An example `AnalyticsDevopsAdoptionSegmentID` is: `"gid://gitlab/Analytics::DevopsAdoption::Segment/1"`.
An example `AnalyticsDevopsAdoptionEnabledNamespaceID` is: `"gid://gitlab/Analytics::DevopsAdoption::EnabledNamespace/1"`.
### `AwardableID`
......
......@@ -13,7 +13,7 @@ module EE
end
def show_adoption?
feature_already_in_use = ::Analytics::DevopsAdoption::Segment.any?
feature_already_in_use = ::Analytics::DevopsAdoption::EnabledNamespace.any?
::License.feature_available?(:devops_adoption) &&
(feature_already_in_use || ::Feature.enabled?(:devops_adoption_feature, default_enabled: :yaml))
......
......@@ -2,7 +2,7 @@
module Analytics
module DevopsAdoption
class SegmentsFinder
class EnabledNamespacesFinder
attr_reader :params, :current_user
def initialize(current_user, params:)
......@@ -11,7 +11,7 @@ module Analytics
end
def execute
scope = ::Analytics::DevopsAdoption::Segment.ordered_by_name
scope = ::Analytics::DevopsAdoption::EnabledNamespace.ordered_by_name
by_display_namespace(scope)
end
......
......@@ -66,9 +66,9 @@ module EE
mount_mutation ::Mutations::DastSiteTokens::Create
mount_mutation ::Mutations::Namespaces::IncreaseStorageTemporarily
mount_mutation ::Mutations::QualityManagement::TestCases::Create
mount_mutation ::Mutations::Analytics::DevopsAdoption::Segments::Create
mount_mutation ::Mutations::Analytics::DevopsAdoption::Segments::BulkFindOrCreate
mount_mutation ::Mutations::Analytics::DevopsAdoption::Segments::Delete
mount_mutation ::Mutations::Analytics::DevopsAdoption::EnabledNamespaces::Enable
mount_mutation ::Mutations::Analytics::DevopsAdoption::EnabledNamespaces::BulkEnable
mount_mutation ::Mutations::Analytics::DevopsAdoption::EnabledNamespaces::Disable
mount_mutation ::Mutations::IncidentManagement::OncallSchedule::Create
mount_mutation ::Mutations::IncidentManagement::OncallSchedule::Update
mount_mutation ::Mutations::IncidentManagement::OncallSchedule::Destroy
......
......@@ -57,10 +57,10 @@ module EE
resolver: ::Resolvers::InstanceSecurityDashboardResolver,
description: 'Fields related to Instance Security Dashboard.'
field :devops_adoption_segments, ::Types::Admin::Analytics::DevopsAdoption::SegmentType.connection_type,
field :devops_adoption_enabled_namespaces, ::Types::Analytics::DevopsAdoption::EnabledNamespaceType.connection_type,
null: true,
description: 'Get configured DevOps adoption segments on the instance. **BETA** This endpoint is subject to change without notice.',
resolver: ::Resolvers::Admin::Analytics::DevopsAdoption::SegmentsResolver
description: 'Get configured DevOps adoption namespaces. **BETA** This endpoint is subject to change without notice.',
resolver: ::Resolvers::Analytics::DevopsAdoption::EnabledNamespacesResolver
field :current_license, ::Types::Admin::CloudLicenses::CurrentLicenseType,
null: true,
......
......@@ -3,40 +3,40 @@
module Mutations
module Analytics
module DevopsAdoption
module Segments
class BulkFindOrCreate < BaseMutation
module EnabledNamespaces
class BulkEnable < BaseMutation
include Mixins::CommonMethods
graphql_name 'BulkFindOrCreateDevopsAdoptionSegments'
graphql_name 'BulkEnableDevopsAdoptionNamespaces'
description '**BETA** This endpoint is subject to change without notice.'
argument :namespace_ids, [::Types::GlobalIDType[::Namespace]],
required: true,
description: 'List of Namespace IDs for the segments.'
description: 'List of Namespace IDs.'
argument :display_namespace_id, ::Types::GlobalIDType[::Namespace],
required: false,
description: 'Display namespace ID.'
field :segments,
[::Types::Admin::Analytics::DevopsAdoption::SegmentType],
field :enabled_namespaces,
[::Types::Analytics::DevopsAdoption::EnabledNamespaceType],
null: true,
description: 'Created segments after mutation.'
description: 'Enabled namespaces after mutation.'
def resolve(namespace_ids:, display_namespace_id: nil, **)
namespaces = GlobalID::Locator.locate_many(namespace_ids)
display_namespace = Gitlab::Graphql::Lazy.force(GitlabSchema.find_by_gid(display_namespace_id))
with_authorization_handler do
service = ::Analytics::DevopsAdoption::Segments::BulkFindOrCreateService
service = ::Analytics::DevopsAdoption::EnabledNamespaces::BulkFindOrCreateService
.new(current_user: current_user, params: { namespaces: namespaces, display_namespace: display_namespace })
segments = service.execute.payload.fetch(:segments)
enabled_namespaces = service.execute.payload.fetch(:enabled_namespaces)
{
segments: segments.select(&:persisted?),
errors: segments.sum { |segment| errors_on_object(segment) }
enabled_namespaces: enabled_namespaces.select(&:persisted?),
errors: enabled_namespaces.sum { |ns| errors_on_object(ns) }
}
end
end
......
......@@ -3,28 +3,28 @@
module Mutations
module Analytics
module DevopsAdoption
module Segments
class Delete < BaseMutation
module EnabledNamespaces
class Disable < BaseMutation
include Mixins::CommonMethods
graphql_name 'DeleteDevopsAdoptionSegment'
graphql_name 'DisableDevopsAdoptionNamespace'
description '**BETA** This endpoint is subject to change without notice.'
argument :id, [::Types::GlobalIDType[::Analytics::DevopsAdoption::Segment]],
argument :id, [::Types::GlobalIDType[::Analytics::DevopsAdoption::EnabledNamespace]],
required: true,
description: 'One or many IDs of the segments to delete.'
description: 'One or many IDs of the enabled namespaces to disable.'
def resolve(id:, **)
segments = GlobalID::Locator.locate_many(id)
enabled_namespaces = GlobalID::Locator.locate_many(id)
with_authorization_handler do
service = ::Analytics::DevopsAdoption::Segments::BulkDeleteService
.new(segments: segments, current_user: current_user)
service = ::Analytics::DevopsAdoption::EnabledNamespaces::BulkDeleteService
.new(enabled_namespaces: enabled_namespaces, current_user: current_user)
response = service.execute
errors = response.payload[:segments].sum { |segment| errors_on_object(segment) }
errors = response.payload[:enabled_namespaces].sum { |ns| errors_on_object(ns) }
{ errors: errors }
end
......
......@@ -3,38 +3,38 @@
module Mutations
module Analytics
module DevopsAdoption
module Segments
class Create < BaseMutation
module EnabledNamespaces
class Enable < BaseMutation
include Mixins::CommonMethods
graphql_name 'CreateDevopsAdoptionSegment'
graphql_name 'EnableDevopsAdoptionNamespace'
description '**BETA** This endpoint is subject to change without notice.'
argument :namespace_id, ::Types::GlobalIDType[::Namespace],
required: true,
description: 'Namespace ID to set for the segment.'
description: 'Namespace ID.'
argument :display_namespace_id, ::Types::GlobalIDType[::Namespace],
required: false,
description: 'Display namespace ID.'
field :segment,
Types::Admin::Analytics::DevopsAdoption::SegmentType,
field :enabled_namespace,
Types::Analytics::DevopsAdoption::EnabledNamespaceType,
null: true,
description: 'The segment after mutation.'
description: 'Enabled namespace after mutation.'
def resolve(namespace_id:, display_namespace_id: nil, **)
namespace = Gitlab::Graphql::Lazy.force(GitlabSchema.find_by_gid(namespace_id))
display_namespace = Gitlab::Graphql::Lazy.force(GitlabSchema.find_by_gid(display_namespace_id))
with_authorization_handler do
service = ::Analytics::DevopsAdoption::Segments::CreateService
service = ::Analytics::DevopsAdoption::EnabledNamespaces::CreateService
.new(current_user: current_user, params: { namespace: namespace, display_namespace: display_namespace })
response = service.execute
resolve_segment(response)
resolve_enabled_namespace(response)
end
end
end
......
......@@ -3,23 +3,23 @@
module Mutations
module Analytics
module DevopsAdoption
module Segments
module EnabledNamespaces
module Mixins
module CommonMethods
private
def resolve_segment(response)
segment = response.payload.fetch(:segment)
def resolve_enabled_namespace(response)
enabled_namespace = response.payload.fetch(:enabled_namespace)
{
segment: response.success? ? response.payload.fetch(:segment) : nil,
errors: errors_on_object(segment)
enabled_namespace: response.success? ? enabled_namespace : nil,
errors: errors_on_object(enabled_namespace)
}
end
def with_authorization_handler
yield
rescue ::Analytics::DevopsAdoption::Segments::AuthorizationError => e
rescue ::Analytics::DevopsAdoption::EnabledNamespaces::AuthorizationError => e
handle_unauthorized!(e)
end
......
# frozen_string_literal: true
module Resolvers
module Admin
module Analytics
module DevopsAdoption
class SegmentsResolver < BaseResolver
include Gitlab::Graphql::Authorize::AuthorizeResource
include Gitlab::Allowable
type Types::Admin::Analytics::DevopsAdoption::SegmentType, null: true
argument :display_namespace_id, ::Types::GlobalIDType[::Namespace],
required: false,
description: 'Filter by display namespace.'
def resolve(display_namespace_id: nil, **)
display_namespace_id = GlobalID.parse(display_namespace_id)
display_namespace = Gitlab::Graphql::Lazy.force(GitlabSchema.find_by_gid(display_namespace_id))
authorize!(display_namespace)
::Analytics::DevopsAdoption::SegmentsFinder.new(current_user, params: {
display_namespace: display_namespace
}).execute
end
private
def authorize!(display_namespace)
display_namespace ? authorize_with_namespace!(display_namespace) : authorize_global!
end
def authorize_global!
unless can?(current_user, :view_instance_devops_adoption)
raise_resource_not_available_error!
end
end
def authorize_with_namespace!(display_namespace)
unless can?(current_user, :view_group_devops_adoption, display_namespace)
raise_resource_not_available_error!
end
end
end
end
end
end
end
# frozen_string_literal: true
module Resolvers
module Analytics
module DevopsAdoption
class EnabledNamespacesResolver < BaseResolver
include Gitlab::Graphql::Authorize::AuthorizeResource
include Gitlab::Allowable
type Types::Analytics::DevopsAdoption::EnabledNamespaceType, null: true
argument :display_namespace_id, ::Types::GlobalIDType[::Namespace],
required: false,
description: 'Filter by display namespace.'
def resolve(display_namespace_id: nil, **)
display_namespace_id = GlobalID.parse(display_namespace_id)
display_namespace = Gitlab::Graphql::Lazy.force(GitlabSchema.find_by_gid(display_namespace_id))
authorize!(display_namespace)
::Analytics::DevopsAdoption::EnabledNamespacesFinder.new(current_user, params: {
display_namespace: display_namespace
}).execute
end
private
def authorize!(display_namespace)
display_namespace ? authorize_with_namespace!(display_namespace) : authorize_global!
end
def authorize_global!
unless can?(current_user, :view_instance_devops_adoption)
raise_resource_not_available_error!
end
end
def authorize_with_namespace!(display_namespace)
unless can?(current_user, :view_group_devops_adoption, display_namespace)
raise_resource_not_available_error!
end
end
end
end
end
end
# frozen_string_literal: true
# rubocop:disable Graphql/AuthorizeTypes
module Types
module Admin
module Analytics
module DevopsAdoption
class SegmentType < BaseObject
graphql_name 'DevopsAdoptionSegment'
description 'Segment'
field :id, GraphQL::ID_TYPE, null: false,
description: "ID of the segment."
field :namespace, Types::NamespaceType, null: true,
description: 'Namespace which should be calculated.'
field :display_namespace, Types::NamespaceType, null: true,
description: 'Namespace where data should be displayed.'
field :latest_snapshot, SnapshotType, null: true,
description: 'The latest adoption metrics for the segment.'
def latest_snapshot
BatchLoader::GraphQL.for(object.namespace_id).batch(key: :devops_adoption_latest_snapshots) do |ids, loader, args|
snapshots = ::Analytics::DevopsAdoption::Snapshot
.latest_snapshot_for_namespace_ids(ids)
.index_by(&:namespace_id)
ids.each do |id|
loader.call(id, snapshots[id])
end
end
end
end
end
end
end
end
# frozen_string_literal: true
# rubocop:disable Graphql/AuthorizeTypes
module Types
module Admin
module Analytics
module DevopsAdoption
class SnapshotType < BaseObject
graphql_name 'DevopsAdoptionSnapshot'
description 'Snapshot'
field :issue_opened, GraphQL::BOOLEAN_TYPE, null: false,
description: 'At least one issue was opened.'
field :merge_request_opened, GraphQL::BOOLEAN_TYPE, null: false,
description: 'At least one merge request was opened.'
field :merge_request_approved, GraphQL::BOOLEAN_TYPE, null: false,
description: 'At least one merge request was approved.'
field :runner_configured, GraphQL::BOOLEAN_TYPE, null: false,
description: 'At least one runner was used.'
field :pipeline_succeeded, GraphQL::BOOLEAN_TYPE, null: false,
description: 'At least one pipeline succeeded.'
field :deploy_succeeded, GraphQL::BOOLEAN_TYPE, null: false,
description: 'At least one deployment succeeded.'
field :security_scan_succeeded, GraphQL::BOOLEAN_TYPE, null: false,
description: 'At least one security scan succeeded.'
field :code_owners_used_count, GraphQL::INT_TYPE, null: true,
description: 'Total number of projects with existing CODEOWNERS file.'
field :total_projects_count, GraphQL::INT_TYPE, null: true,
description: 'Total number of projects.'
field :recorded_at, Types::TimeType, null: false,
description: 'The time the snapshot was recorded.'
field :start_time, Types::TimeType, null: false,
description: 'The start time for the snapshot where the data points were collected.'
field :end_time, Types::TimeType, null: false,
description: 'The end time for the snapshot where the data points were collected.'
end
end
end
end
end
# frozen_string_literal: true
# rubocop:disable Graphql/AuthorizeTypes
module Types
module Analytics
module DevopsAdoption
class EnabledNamespaceType < BaseObject
graphql_name 'DevopsAdoptionSegment'
description 'Enabled namespace for DevopsAdoption'
field :id, GraphQL::ID_TYPE, null: false,
description: "ID of the enabled namespace."
field :namespace, Types::NamespaceType, null: true,
description: 'Namespace which should be calculated.'
field :display_namespace, Types::NamespaceType, null: true,
description: 'Namespace where data should be displayed.'
field :latest_snapshot, SnapshotType, null: true,
description: 'The latest adoption metrics for the enabled namespace.'
def latest_snapshot
BatchLoader::GraphQL.for(object.namespace_id).batch(key: :devops_adoption_latest_snapshots) do |ids, loader, args|
snapshots = ::Analytics::DevopsAdoption::Snapshot
.latest_snapshot_for_namespace_ids(ids)
.index_by(&:namespace_id)
ids.each do |id|
loader.call(id, snapshots[id])
end
end
end
end
end
end
end
# frozen_string_literal: true
# rubocop:disable Graphql/AuthorizeTypes
module Types
module Analytics
module DevopsAdoption
class SnapshotType < BaseObject
graphql_name 'DevopsAdoptionSnapshot'
description 'Snapshot'
field :issue_opened, GraphQL::BOOLEAN_TYPE, null: false,
description: 'At least one issue was opened.'
field :merge_request_opened, GraphQL::BOOLEAN_TYPE, null: false,
description: 'At least one merge request was opened.'
field :merge_request_approved, GraphQL::BOOLEAN_TYPE, null: false,
description: 'At least one merge request was approved.'
field :runner_configured, GraphQL::BOOLEAN_TYPE, null: false,
description: 'At least one runner was used.'
field :pipeline_succeeded, GraphQL::BOOLEAN_TYPE, null: false,
description: 'At least one pipeline succeeded.'
field :deploy_succeeded, GraphQL::BOOLEAN_TYPE, null: false,
description: 'At least one deployment succeeded.'
field :security_scan_succeeded, GraphQL::BOOLEAN_TYPE, null: false,
description: 'At least one security scan succeeded.'
field :code_owners_used_count, GraphQL::INT_TYPE, null: true,
description: 'Total number of projects with existing CODEOWNERS file.'
field :total_projects_count, GraphQL::INT_TYPE, null: true,
description: 'Total number of projects.'
field :recorded_at, Types::TimeType, null: false,
description: 'The time the snapshot was recorded.'
field :start_time, Types::TimeType, null: false,
description: 'The start time for the snapshot where the data points were collected.'
field :end_time, Types::TimeType, null: false,
description: 'The end time for the snapshot where the data points were collected.'
end
end
end
end
# frozen_string_literal: true
class Analytics::DevopsAdoption::Segment < ApplicationRecord
class Analytics::DevopsAdoption::EnabledNamespace < ApplicationRecord
self.table_name = 'analytics_devops_adoption_segments'
include IgnorableColumns
belongs_to :namespace
......
......@@ -5,7 +5,7 @@ class Analytics::DevopsAdoption::Snapshot < ApplicationRecord
belongs_to :namespace
has_many :segments, foreign_key: :namespace_id, primary_key: :namespace_id
has_many :enabled_namespaces, foreign_key: :namespace_id, primary_key: :namespace_id
validates :namespace, presence: true
validates :recorded_at, presence: true
......
......@@ -36,7 +36,7 @@ module EE
rule { ~anonymous & operations_dashboard_available }.enable :read_operations_dashboard
rule { admin & instance_devops_adoption_available }.policy do
enable :manage_devops_adoption_segments
enable :manage_devops_adoption_namespaces
enable :view_instance_devops_adoption
end
......
......@@ -186,12 +186,12 @@ module EE
end
rule { reporter & group_devops_adoption_enabled & group_devops_adoption_available }.policy do
enable :manage_devops_adoption_segments
enable :manage_devops_adoption_namespaces
enable :view_group_devops_adoption
end
rule { admin & group_devops_adoption_enabled }.policy do
enable :manage_devops_adoption_segments
enable :manage_devops_adoption_namespaces
end
rule { owner & ~has_parent & prevent_group_forking_available }.policy do
......
......@@ -2,7 +2,7 @@
module Analytics
module DevopsAdoption
module Segments
module EnabledNamespaces
class AuthorizationError < StandardError
attr_reader :service
......
......@@ -2,10 +2,10 @@
module Analytics
module DevopsAdoption
module Segments
module EnabledNamespaces
class BulkDeleteService
def initialize(segments:, current_user:)
@segments = segments
def initialize(enabled_namespaces:, current_user:)
@enabled_namespaces = enabled_namespaces
@current_user = current_user
end
......@@ -32,15 +32,15 @@ module Analytics
private
attr_reader :segments, :current_user
attr_reader :enabled_namespaces, :current_user
def response_payload
{ segments: segments }
{ enabled_namespaces: enabled_namespaces }
end
def deletion_services
@deletion_services ||= segments.map do |segment|
DeleteService.new(current_user: current_user, segment: segment)
@deletion_services ||= enabled_namespaces.map do |ns|
DeleteService.new(current_user: current_user, enabled_namespace: ns)
end
end
end
......
......@@ -2,7 +2,7 @@
module Analytics
module DevopsAdoption
module Segments
module EnabledNamespaces
class BulkFindOrCreateService
def initialize(params: {}, current_user:)
@params = params
......@@ -12,11 +12,11 @@ module Analytics
def execute
authorize!
segments = services.map do |service|
service.execute.payload[:segment]
enabled_namespaces = services.map do |service|
service.execute.payload[:enabled_namespace]
end
ServiceResponse.success(payload: { segments: segments })
ServiceResponse.success(payload: { enabled_namespaces: enabled_namespaces })
end
def authorize!
......
......@@ -2,16 +2,16 @@
module Analytics
module DevopsAdoption
module Segments
module EnabledNamespaces
module CommonMethods
include Gitlab::Allowable
def authorize!
unless can?(current_user, :manage_devops_adoption_segments, namespace)
unless can?(current_user, :manage_devops_adoption_namespaces, namespace)
raise AuthorizationError.new(self, 'Forbidden')
end
unless can?(current_user, :manage_devops_adoption_segments, display_namespace || :global)
unless can?(current_user, :manage_devops_adoption_namespaces, display_namespace || :global)
raise AuthorizationError.new(self, 'Forbidden')
end
end
......
......@@ -2,12 +2,12 @@
module Analytics
module DevopsAdoption
module Segments
module EnabledNamespaces
class CreateService
include CommonMethods
def initialize(segment: Analytics::DevopsAdoption::Segment.new, params: {}, current_user:)
@segment = segment
def initialize(enabled_namespace: Analytics::DevopsAdoption::EnabledNamespace.new, params: {}, current_user:)
@enabled_namespace = enabled_namespace
@params = params
@current_user = current_user
end
......@@ -15,10 +15,10 @@ module Analytics
def execute
authorize!
segment.assign_attributes(namespace: namespace, display_namespace: display_namespace)
enabled_namespace.assign_attributes(namespace: namespace, display_namespace: display_namespace)
if segment.save
Analytics::DevopsAdoption::CreateSnapshotWorker.perform_async(segment.id)
if enabled_namespace.save
Analytics::DevopsAdoption::CreateSnapshotWorker.perform_async(enabled_namespace.id)
ServiceResponse.success(payload: response_payload)
else
......@@ -28,10 +28,10 @@ module Analytics
private
attr_reader :segment
attr_reader :enabled_namespace
def response_payload
{ segment: segment }
{ enabled_namespace: enabled_namespace }
end
end
end
......
......@@ -2,12 +2,12 @@
module Analytics
module DevopsAdoption
module Segments
module EnabledNamespaces
class DeleteService
include CommonMethods
def initialize(segment:, current_user:)
@segment = segment
def initialize(enabled_namespace:, current_user:)
@enabled_namespace = enabled_namespace
@current_user = current_user
end
......@@ -15,22 +15,22 @@ module Analytics
authorize!
begin
segment.destroy!
enabled_namespace.destroy!
ServiceResponse.success(payload: response_payload)
rescue ActiveRecord::RecordNotDestroyed
ServiceResponse.error(message: 'DevOps Adoption Segment deletion error', payload: response_payload)
ServiceResponse.error(message: 'DevOps Adoption EnabledNamespace deletion error', payload: response_payload)
end
end
private
attr_reader :segment
attr_reader :enabled_namespace
delegate :namespace, :display_namespace, to: :segment
delegate :namespace, :display_namespace, to: :enabled_namespace
def response_payload
{ segment: segment }
{ enabled_namespace: enabled_namespace }
end
end
end
......
......@@ -2,7 +2,7 @@
module Analytics
module DevopsAdoption
module Segments
module EnabledNamespaces
class FindOrCreateService
include CommonMethods
......@@ -17,10 +17,10 @@ module Analytics
def execute
authorize!
segment = Analytics::DevopsAdoption::Segment.find_by(namespace_id: namespace.id, display_namespace_id: display_namespace&.id)
enabled_namespace = Analytics::DevopsAdoption::EnabledNamespace.find_by(namespace_id: namespace.id, display_namespace_id: display_namespace&.id)
if segment
ServiceResponse.success(payload: { segment: segment })
if enabled_namespace
ServiceResponse.success(payload: { enabled_namespace: enabled_namespace })
else
create_service.execute
end
......
......@@ -4,10 +4,10 @@ module Analytics
module DevopsAdoption
module Snapshots
class CalculateAndSaveService
attr_reader :segment, :range_end
attr_reader :enabled_namespace, :range_end
def initialize(segment:, range_end:)
@segment = segment
def initialize(enabled_namespace:, range_end:)
@enabled_namespace = enabled_namespace
@range_end = range_end
end
......@@ -20,11 +20,11 @@ module Analytics
end
def snapshot
@snapshot ||= segment.snapshots.for_month(range_end).first
@snapshot ||= enabled_namespace.snapshots.for_month(range_end).first
end
def calculated_data
@calculated_data ||= SnapshotCalculator.new(segment: segment, range_end: range_end, snapshot: snapshot).calculate
@calculated_data ||= SnapshotCalculator.new(enabled_namespace: enabled_namespace, range_end: range_end, snapshot: snapshot).calculate
end
end
end
......
......@@ -2,7 +2,7 @@
module Analytics
module DevopsAdoption
# Schedules update of snapshots for all segments
# Schedules update of snapshots for all enabled_namespaces
class CreateAllSnapshotsWorker
include ApplicationWorker
......@@ -18,8 +18,8 @@ module Analytics
# rubocop: disable CodeReuse/ActiveRecord
def perform
::Analytics::DevopsAdoption::Segment.all.pluck(:id).each.with_index do |segment_id, i|
CreateSnapshotWorker.perform_in(i * WORKERS_GAP, segment_id)
::Analytics::DevopsAdoption::EnabledNamespace.all.pluck(:id).each.with_index do |enabled_namespace_id, i|
CreateSnapshotWorker.perform_in(i * WORKERS_GAP, enabled_namespace_id)
end
end
# rubocop: enable CodeReuse/ActiveRecord
......
......@@ -2,7 +2,7 @@
module Analytics
module DevopsAdoption
# Updates all pending snapshots for given segment (from previous month)
# Updates all pending snapshots for given enabled_namespace (from previous month)
# and creates or update snapshot for current month
class CreateSnapshotWorker
include ApplicationWorker
......@@ -13,21 +13,19 @@ module Analytics
idempotent!
tags :exclude_from_kubernetes
# range_end was deprecated and must be removed in 14.0
#
def perform(segment_id, _deprecated_range_end = nil)
segment = Segment.find(segment_id)
def perform(enabled_namespace_id)
enabled_namespace = EnabledNamespace.find(enabled_namespace_id)
pending_ranges(segment).each do |range_end|
Snapshots::CalculateAndSaveService.new(segment: segment, range_end: range_end).execute
pending_ranges(enabled_namespace).each do |range_end|
Snapshots::CalculateAndSaveService.new(enabled_namespace: enabled_namespace, range_end: range_end).execute
end
end
private
# rubocop: disable CodeReuse/ActiveRecord
def pending_ranges(segment)
end_times = segment.snapshots.not_finalized.pluck(:end_time)
def pending_ranges(enabled_namespace)
end_times = enabled_namespace.snapshots.not_finalized.pluck(:end_time)
now = Time.zone.now
......
......@@ -8,14 +8,14 @@ Gitlab::Seeder.quiet do
groups = groups.sample(2)
ActiveRecord::Base.transaction do
segments = [
Analytics::DevopsAdoption::Segment.create(namespace: groups.first, display_namespace: groups.first),
Analytics::DevopsAdoption::Segment.create(namespace: groups.last, display_namespace: groups.last)
enabled_namespaces = [
Analytics::DevopsAdoption::EnabledNamespace.create(namespace: groups.first, display_namespace: groups.first),
Analytics::DevopsAdoption::EnabledNamespace.create(namespace: groups.last, display_namespace: groups.last)
]
if segments.any?(&:invalid?)
puts "Error creating segments"
puts "#{segments.map(&:errors)}"
if enabled_namespaces.any?(&:invalid?)
puts "Error creating enabled_namespaces"
puts "#{enabled_namespaces.map(&:errors)}"
next
end
......@@ -25,9 +25,9 @@ Gitlab::Seeder.quiet do
4.downto(0).each do |index|
end_time = index.months.ago.at_end_of_month
segments.each do |segment|
enabled_namespaces.each do |enabled_namespace|
calculated_data = {
namespace: segment.namespace,
namespace: enabled_namespace.namespace,
issue_opened: booleans.sample,
merge_request_opened: booleans.sample,
merge_request_approved: booleans.sample,
......
......@@ -3,7 +3,7 @@
module Analytics
module DevopsAdoption
class SnapshotCalculator
attr_reader :segment, :range_end, :range_start, :snapshot
attr_reader :enabled_namespace, :range_end, :range_start, :snapshot
BOOLEAN_METRICS = [
:issue_opened,
......@@ -22,15 +22,15 @@ module Analytics
ADOPTION_METRICS = BOOLEAN_METRICS + NUMERIC_METRICS
def initialize(segment:, range_end:, snapshot: nil)
@segment = segment
def initialize(enabled_namespace:, range_end:, snapshot: nil)
@enabled_namespace = enabled_namespace
@range_end = range_end
@range_start = Snapshot.new(end_time: range_end).start_time
@snapshot = snapshot
end
def calculate
params = { recorded_at: Time.zone.now, end_time: range_end, namespace: segment.namespace }
params = { recorded_at: Time.zone.now, end_time: range_end, namespace: enabled_namespace.namespace }
BOOLEAN_METRICS.each do |metric|
params[metric] = snapshot&.public_send(metric) || send(metric) # rubocop:disable GitlabSecurity/PublicSend
......@@ -46,7 +46,7 @@ module Analytics
private
def snapshot_groups
@snapshot_groups ||= segment.namespace.self_and_descendants
@snapshot_groups ||= enabled_namespace.namespace.self_and_descendants
end
# rubocop: disable CodeReuse/ActiveRecord
......@@ -105,7 +105,7 @@ module Analytics
end
def code_owners_used_count
return unless Feature.enabled?(:analytics_devops_adoption_codeowners, segment.namespace, default_enabled: :yaml)
return unless Feature.enabled?(:analytics_devops_adoption_codeowners, enabled_namespace.namespace, default_enabled: :yaml)
snapshot_projects.count do |project|
!Gitlab::CodeOwners::Loader.new(project, project.default_branch || 'HEAD').empty_code_owners?
......
......@@ -13,8 +13,8 @@ RSpec.describe Admin::DevOpsReportController do
stub_licensed_features(devops_adoption: true)
end
it 'is true if there are any segments' do
create(:devops_adoption_segment)
it 'is true if there are any enabled_namespaces' do
create(:devops_adoption_enabled_namespace)
expect(controller.show_adoption?).to be true
end
......
# frozen_string_literal: true
FactoryBot.define do
factory :devops_adoption_segment, class: 'Analytics::DevopsAdoption::Segment' do
factory :devops_adoption_enabled_namespace, class: 'Analytics::DevopsAdoption::EnabledNamespace' do
association :namespace, factory: :group
association :display_namespace, factory: :group
end
......
......@@ -2,19 +2,19 @@
require 'spec_helper'
RSpec.describe Analytics::DevopsAdoption::SegmentsFinder do
RSpec.describe Analytics::DevopsAdoption::EnabledNamespacesFinder do
let_it_be(:admin_user) { create(:user, :admin) }
subject(:finder_segments) { described_class.new(admin_user, params: params).execute }
subject(:finder) { described_class.new(admin_user, params: params).execute }
let(:params) { {} }
describe '#execute' do
let_it_be(:root_group_1) { create(:group, name: 'bbb') }
let_it_be(:segment_1) { create(:devops_adoption_segment, namespace: root_group_1, display_namespace: nil) }
let_it_be(:segment_2) { create(:devops_adoption_segment, namespace: root_group_1, display_namespace: root_group_1) }
let_it_be(:segment_3) { create(:devops_adoption_segment) }
let_it_be(:enabled_namespace_1) { create(:devops_adoption_enabled_namespace, namespace: root_group_1, display_namespace: nil) }
let_it_be(:enabled_namespace_2) { create(:devops_adoption_enabled_namespace, namespace: root_group_1, display_namespace: root_group_1) }
let_it_be(:enabled_namespace_3) { create(:devops_adoption_enabled_namespace) }
before do
stub_licensed_features(instance_level_devops_adoption: true)
......@@ -24,14 +24,14 @@ RSpec.describe Analytics::DevopsAdoption::SegmentsFinder do
context 'with display_namespace provided' do
let(:params) { super().merge(display_namespace: root_group_1) }
it 'returns segments with given display namespace' do
expect(finder_segments).to eq([segment_2])
it 'returns enabled_namespaces with given display namespace' do
expect(finder).to eq([enabled_namespace_2])
end
end
context 'without display_namespace provided' do
it 'returns all namespace without display_namespace' do
expect(finder_segments).to eq([segment_1])
expect(finder).to eq([enabled_namespace_1])
end
end
end
......
......@@ -2,12 +2,12 @@
require 'spec_helper'
RSpec.describe Resolvers::Admin::Analytics::DevopsAdoption::SegmentsResolver do
RSpec.describe Resolvers::Analytics::DevopsAdoption::EnabledNamespacesResolver do
include GraphqlHelpers
let_it_be(:admin_user) { create(:user, :admin) }
def resolve_segments(args = {}, context = {})
def resolve_enabled_namespaces(args = {}, context = {})
resolve(described_class, args: args, ctx: context)
end
......@@ -19,12 +19,12 @@ RSpec.describe Resolvers::Admin::Analytics::DevopsAdoption::SegmentsResolver do
let_it_be(:user) { create(:user) }
let_it_be(:root_group_1) { create(:group, name: 'bbb') }
let_it_be(:root_group_2) { create(:group, name: 'aaa') }
let_it_be(:segment_1) { create(:devops_adoption_segment, namespace: root_group_1, display_namespace: root_group_1) }
let_it_be(:segment_2) { create(:devops_adoption_segment, namespace: root_group_1, display_namespace: nil) }
let_it_be(:segment_3) { create(:devops_adoption_segment, namespace: root_group_2, display_namespace: root_group_2) }
let_it_be(:segment_4) { create(:devops_adoption_segment, namespace: root_group_2, display_namespace: nil) }
let_it_be(:enabled_namespace_1) { create(:devops_adoption_enabled_namespace, namespace: root_group_1, display_namespace: root_group_1) }
let_it_be(:enabled_namespace_2) { create(:devops_adoption_enabled_namespace, namespace: root_group_1, display_namespace: nil) }
let_it_be(:enabled_namespace_3) { create(:devops_adoption_enabled_namespace, namespace: root_group_2, display_namespace: root_group_2) }
let_it_be(:enabled_namespace_4) { create(:devops_adoption_enabled_namespace, namespace: root_group_2, display_namespace: nil) }
subject(:resolved_segments) { resolve_segments(params, { current_user: current_user }) }
subject(:resolved_enabled_namespaces) { resolve_enabled_namespaces(params, { current_user: current_user }) }
let(:params) { {} }
......@@ -32,8 +32,8 @@ RSpec.describe Resolvers::Admin::Analytics::DevopsAdoption::SegmentsResolver do
let(:current_user) { admin_user }
context 'as an admin user' do
it 'returns segments for all groups without display_namespace' do
expect(resolved_segments).to match_array([segment_2, segment_4])
it 'returns enabled_namespaces for all groups without display_namespace' do
expect(resolved_enabled_namespaces).to match_array([enabled_namespace_2, enabled_namespace_4])
end
end
......@@ -41,7 +41,7 @@ RSpec.describe Resolvers::Admin::Analytics::DevopsAdoption::SegmentsResolver do
let(:current_user) { user }
it 'raises ResourceNotAvailable error' do
expect { resolved_segments }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
expect { resolved_enabled_namespaces }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
......@@ -51,7 +51,7 @@ RSpec.describe Resolvers::Admin::Analytics::DevopsAdoption::SegmentsResolver do
end
it 'raises ResourceNotAvailable error' do
expect { resolved_segments }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
expect { resolved_enabled_namespaces }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
end
......@@ -65,8 +65,8 @@ RSpec.describe Resolvers::Admin::Analytics::DevopsAdoption::SegmentsResolver do
root_group_1.add_reporter(user)
end
it 'returns segments for given parent group and its descendants' do
expect(resolved_segments).to eq([segment_1])
it 'returns enabled_namespaces for given parent group and its descendants' do
expect(resolved_enabled_namespaces).to eq([enabled_namespace_1])
end
end
......@@ -76,7 +76,7 @@ RSpec.describe Resolvers::Admin::Analytics::DevopsAdoption::SegmentsResolver do
end
it 'raises ResourceNotAvailable error' do
expect { resolved_segments }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
expect { resolved_enabled_namespaces }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
......@@ -86,7 +86,7 @@ RSpec.describe Resolvers::Admin::Analytics::DevopsAdoption::SegmentsResolver do
end
it 'raises ResourceNotAvailable error' do
expect { resolved_segments }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
expect { resolved_enabled_namespaces }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
end
......
......@@ -4,13 +4,13 @@ require 'spec_helper'
RSpec.describe Analytics::DevopsAdoption::SnapshotCalculator do
let_it_be(:group1) { create(:group) }
let_it_be(:segment) { create(:devops_adoption_segment, namespace: group1) }
let_it_be(:enabled_namespace) { create(:devops_adoption_enabled_namespace, namespace: group1) }
let_it_be(:subgroup) { create(:group, parent: group1) }
let_it_be(:project) { create(:project, group: group1) }
let_it_be(:subproject) { create(:project, group: subgroup) }
let_it_be(:range_end) { Time.zone.parse('2020-12-01').end_of_month }
subject(:data) { described_class.new(segment: segment, range_end: range_end).calculate }
subject(:data) { described_class.new(enabled_namespace: enabled_namespace, range_end: range_end).calculate }
describe 'end_time' do
it 'equals to range_end' do
......@@ -98,7 +98,7 @@ RSpec.describe Analytics::DevopsAdoption::SnapshotCalculator do
let!(:deployment) { create(:deployment, :success, updated_at: deployed_at) }
let(:deployed_at) { 100.days.ago(range_end) }
let(:segment) { create(:devops_adoption_segment, namespace: group) }
let(:enabled_namespace) { create(:devops_adoption_enabled_namespace, namespace: group) }
let!(:group) do
create(:group).tap do |g|
g.projects << deployment.project
......@@ -167,9 +167,9 @@ RSpec.describe Analytics::DevopsAdoption::SnapshotCalculator do
end
context 'when snapshot already exists' do
subject(:data) { described_class.new(segment: segment, range_end: range_end, snapshot: snapshot).calculate }
subject(:data) { described_class.new(enabled_namespace: enabled_namespace, range_end: range_end, snapshot: snapshot).calculate }
let(:snapshot) { create :devops_adoption_snapshot, namespace: segment.namespace, issue_opened: true, merge_request_opened: false, total_projects_count: 1 }
let(:snapshot) { create :devops_adoption_snapshot, namespace: enabled_namespace.namespace, issue_opened: true, merge_request_opened: false, total_projects_count: 1 }
context 'for boolean metrics' do
let!(:fresh_merge_request) { create(:merge_request, source_project: project, created_at: 3.weeks.ago(range_end)) }
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Analytics::DevopsAdoption::EnabledNamespace, type: :model do
describe 'associations' do
subject { build(:devops_adoption_enabled_namespace) }
it { is_expected.to have_many(:snapshots) }
it { is_expected.to belong_to(:namespace) }
it { is_expected.to belong_to(:display_namespace) }
end
describe 'validation' do
subject { build(:devops_adoption_enabled_namespace) }
it { is_expected.to validate_presence_of(:namespace) }
it { is_expected.to validate_uniqueness_of(:namespace).scoped_to(:display_namespace_id) }
end
describe '.ordered_by_name' do
subject(:enabled_namespaces) { described_class.ordered_by_name }
it 'orders enabled_namespaces by namespace name' do
enabled_namespace_1 = create(:devops_adoption_enabled_namespace, namespace: create(:group, name: 'bbb'))
enabled_namespace_2 = create(:devops_adoption_enabled_namespace, namespace: create(:group, name: 'aaa'))
expect(enabled_namespaces).to eq([enabled_namespace_2, enabled_namespace_1])
end
end
describe '.for_namespaces' do
subject(:enabled_namespaces) { described_class.for_namespaces(namespaces) }
let_it_be(:enabled_namespace1) { create(:devops_adoption_enabled_namespace) }
let_it_be(:enabled_namespace2) { create(:devops_adoption_enabled_namespace) }
let_it_be(:enabled_namespace3) { create(:devops_adoption_enabled_namespace) }
let_it_be(:namespaces) { [enabled_namespace1.namespace, enabled_namespace2.namespace]}
it 'selects enabled_namespaces for given namespaces only' do
expect(enabled_namespaces).to match_array([enabled_namespace1, enabled_namespace2])
end
end
describe '.for_display_namespaces' do
subject(:enabled_namespaces) { described_class.for_display_namespaces(namespaces) }
let_it_be(:enabled_namespace1) { create(:devops_adoption_enabled_namespace) }
let_it_be(:enabled_namespace2) { create(:devops_adoption_enabled_namespace) }
let_it_be(:enabled_namespace3) { create(:devops_adoption_enabled_namespace) }
let_it_be(:namespaces) { [enabled_namespace1.display_namespace, enabled_namespace2.display_namespace]}
it 'selects enabled_namespaces for given namespaces only' do
expect(enabled_namespaces).to match_array([enabled_namespace1, enabled_namespace2])
end
end
describe '.for_parent' do
let_it_be(:group) { create :group }
let_it_be(:subgroup) { create :group, parent: group }
let_it_be(:group2) { create :group }
let_it_be(:enabled_namespace1) { create(:devops_adoption_enabled_namespace, namespace: group) }
let_it_be(:enabled_namespace2) { create(:devops_adoption_enabled_namespace, namespace: subgroup) }
let_it_be(:enabled_namespace3) { create(:devops_adoption_enabled_namespace, namespace: group2) }
subject(:enabled_namespaces) { described_class.for_parent(group) }
it 'selects enabled_namespaces for given namespace only' do
expect(enabled_namespaces).to match_array([enabled_namespace1, enabled_namespace2])
end
end
describe '.latest_snapshot' do
it 'loads the latest snapshot' do
enabled_namespace = create(:devops_adoption_enabled_namespace)
latest_snapshot = create(:devops_adoption_snapshot, namespace: enabled_namespace.namespace, end_time: 2.days.ago)
create(:devops_adoption_snapshot, namespace: enabled_namespace.namespace, end_time: 5.days.ago)
create(:devops_adoption_snapshot, end_time: 1.hour.ago)
expect(enabled_namespace.latest_snapshot).to eq(latest_snapshot)
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Analytics::DevopsAdoption::Segment, type: :model do
describe 'associations' do
subject { build(:devops_adoption_segment) }
it { is_expected.to have_many(:snapshots) }
it { is_expected.to belong_to(:namespace) }
it { is_expected.to belong_to(:display_namespace) }
end
describe 'validation' do
subject { build(:devops_adoption_segment) }
it { is_expected.to validate_presence_of(:namespace) }
it { is_expected.to validate_uniqueness_of(:namespace).scoped_to(:display_namespace_id) }
end
describe '.ordered_by_name' do
subject(:segments) { described_class.ordered_by_name }
it 'orders segments by namespace name' do
segment_1 = create(:devops_adoption_segment, namespace: create(:group, name: 'bbb'))
segment_2 = create(:devops_adoption_segment, namespace: create(:group, name: 'aaa'))
expect(segments).to eq([segment_2, segment_1])
end
end
describe '.for_namespaces' do
subject(:segments) { described_class.for_namespaces(namespaces) }
let_it_be(:segment1) { create(:devops_adoption_segment) }
let_it_be(:segment2) { create(:devops_adoption_segment) }
let_it_be(:segment3) { create(:devops_adoption_segment) }
let_it_be(:namespaces) { [segment1.namespace, segment2.namespace]}
it 'selects segments for given namespaces only' do
expect(segments).to match_array([segment1, segment2])
end
end
describe '.for_display_namespaces' do
subject(:segments) { described_class.for_display_namespaces(namespaces) }
let_it_be(:segment1) { create(:devops_adoption_segment) }
let_it_be(:segment2) { create(:devops_adoption_segment) }
let_it_be(:segment3) { create(:devops_adoption_segment) }
let_it_be(:namespaces) { [segment1.display_namespace, segment2.display_namespace]}
it 'selects segments for given namespaces only' do
expect(segments).to match_array([segment1, segment2])
end
end
describe '.for_parent' do
let_it_be(:group) { create :group }
let_it_be(:subgroup) { create :group, parent: group }
let_it_be(:group2) { create :group }
let_it_be(:segment1) { create(:devops_adoption_segment, namespace: group) }
let_it_be(:segment2) { create(:devops_adoption_segment, namespace: subgroup) }
let_it_be(:segment3) { create(:devops_adoption_segment, namespace: group2) }
subject(:segments) { described_class.for_parent(group) }
it 'selects segments for given namespace only' do
expect(segments).to match_array([segment1, segment2])
end
end
describe '.latest_snapshot' do
it 'loads the latest snapshot' do
segment = create(:devops_adoption_segment)
latest_snapshot = create(:devops_adoption_snapshot, namespace: segment.namespace, end_time: 2.days.ago)
create(:devops_adoption_snapshot, namespace: segment.namespace, end_time: 5.days.ago)
create(:devops_adoption_snapshot, end_time: 1.hour.ago)
expect(segment.latest_snapshot).to eq(latest_snapshot)
end
end
end
......@@ -311,7 +311,7 @@ RSpec.describe GlobalPolicy do
end
end
describe ':view_instance_devops_adoption & :manage_devops_adoption_segments', :enable_admin_mode do
describe ':view_instance_devops_adoption & :manage_devops_adoption_namespaces', :enable_admin_mode do
let(:current_user) { admin }
context 'when license does not include the feature' do
......@@ -319,7 +319,7 @@ RSpec.describe GlobalPolicy do
stub_licensed_features(instance_level_devops_adoption: false)
end
it { is_expected.to be_disallowed(:view_instance_devops_adoption, :manage_devops_adoption_segments) }
it { is_expected.to be_disallowed(:view_instance_devops_adoption, :manage_devops_adoption_namespaces) }
end
context 'when feature is enabled and license include the feature' do
......@@ -327,12 +327,12 @@ RSpec.describe GlobalPolicy do
stub_licensed_features(instance_level_devops_adoption: true)
end
it { is_expected.to be_allowed(:view_instance_devops_adoption, :manage_devops_adoption_segments) }
it { is_expected.to be_allowed(:view_instance_devops_adoption, :manage_devops_adoption_namespaces) }
context 'for non-admins' do
let(:current_user) { user }
it { is_expected.to be_disallowed(:view_instance_devops_adoption, :manage_devops_adoption_segments) }
it { is_expected.to be_disallowed(:view_instance_devops_adoption, :manage_devops_adoption_namespaces) }
end
end
end
......
......@@ -1613,9 +1613,9 @@ RSpec.describe GroupPolicy do
end
end
describe 'manage_devops_adoption_segments' do
describe 'manage_devops_adoption_namespaces' do
let(:current_user) { owner }
let(:policy) { :manage_devops_adoption_segments }
let(:policy) { :manage_devops_adoption_namespaces }
context 'when feature is disabled' do
before do
......
......@@ -2,14 +2,14 @@
require 'spec_helper'
RSpec.describe 'DevopsAdoptionSegments' do
RSpec.describe 'devopsAdoptionEnabledNamespaces' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user, :admin) }
let_it_be(:group) { create(:group, name: 'my-group') }
let_it_be(:segment) do
create(:devops_adoption_segment, namespace: group, display_namespace: group)
let_it_be(:enabled_namespace) do
create(:devops_adoption_enabled_namespace, namespace: group, display_namespace: group)
end
let_it_be(:snapshot) do
......@@ -17,7 +17,7 @@ RSpec.describe 'DevopsAdoptionSegments' do
end
let(:query) do
graphql_query_for(:devopsAdoptionSegments, { display_namespace_id: group.to_gid.to_s }, %(
graphql_query_for(:devopsAdoptionEnabledNamespaces, { display_namespace_id: group.to_gid.to_s }, %(
nodes {
id
namespace {
......@@ -41,9 +41,9 @@ RSpec.describe 'DevopsAdoptionSegments' do
end
it 'returns measurement objects' do
expect(graphql_data['devopsAdoptionSegments']['nodes']).to eq([
expect(graphql_data['devopsAdoptionEnabledNamespaces']['nodes']).to eq([
{
'id' => segment.to_gid.to_s,
'id' => enabled_namespace.to_gid.to_s,
'namespace' => { 'name' => group.name },
'displayNamespace' => { 'name' => group.name },
'latestSnapshot' => {
......
......@@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Mutations::Analytics::DevopsAdoption::Segments::BulkFindOrCreate do
RSpec.describe Mutations::Analytics::DevopsAdoption::EnabledNamespaces::BulkEnable do
include GraphqlHelpers
let_it_be(:display_group) { create(:group, name: 'dddd') }
......@@ -19,18 +19,18 @@ RSpec.describe Mutations::Analytics::DevopsAdoption::Segments::BulkFindOrCreate
end
end
let_it_be(:existing_segment) { create :devops_adoption_segment, namespace: group3, display_namespace: display_group }
let_it_be(:existing_enabled_namespace) { create :devops_adoption_enabled_namespace, namespace: group3, display_namespace: display_group }
let(:current_user) { reporter }
let(:variables) { { namespace_ids: [group.to_gid.to_s, group2.to_gid.to_s, group3.to_gid.to_s], display_namespace_id: display_group.to_gid.to_s } }
let(:mutation) do
graphql_mutation(:bulk_find_or_create_devops_adoption_segments, variables) do
graphql_mutation(:bulk_enable_devops_adoption_namespaces, variables) do
<<-QL.strip_heredoc
clientMutationId
errors
segments {
enabledNamespaces {
id
namespace {
name
......@@ -44,14 +44,14 @@ RSpec.describe Mutations::Analytics::DevopsAdoption::Segments::BulkFindOrCreate
end
def mutation_response
graphql_mutation_response(:bulk_find_or_create_devops_adoption_segments)
graphql_mutation_response(:bulk_enable_devops_adoption_namespaces)
end
before do
stub_licensed_features(group_level_devops_adoption: true)
end
context 'when the user cannot manage segments at least for one namespace' do
context 'when the user cannot manage enabled_namespaces at least for one namespace' do
let(:current_user) { create(:user).tap { |u| group.add_reporter(u) } }
it_behaves_like 'a mutation that returns a top-level access error'
......@@ -65,16 +65,16 @@ RSpec.describe Mutations::Analytics::DevopsAdoption::Segments::BulkFindOrCreate
it_behaves_like 'a mutation that returns a top-level access error'
end
it 'creates the segment for each passed namespace or returns existing segment' do
it 'creates the enabled_namespace for each passed namespace or returns existing enabled_namespace' do
post_graphql_mutation(mutation, current_user: current_user)
expect(mutation_response['errors']).to be_empty
segments = mutation_response['segments']
expect(segments.map { |s| s['namespace']['name'] }).to match_array(%w[aaaa bbbb cccc])
expect(segments.map { |s| s['displayNamespace']['name'] }).to match_array(%w[dddd dddd dddd])
expect(segments.map { |s| s['id'] }).to include(existing_segment.to_gid.to_s)
expect(::Analytics::DevopsAdoption::Segment.joins(:namespace)
.where(namespaces: { name: %w[aaaa bbbb cccc] }).count).to eq(3)
enabled_namespaces = mutation_response['enabledNamespaces']
expect(enabled_namespaces.map { |s| s['namespace']['name'] }).to match_array(%w[aaaa bbbb cccc])
expect(enabled_namespaces.map { |s| s['displayNamespace']['name'] }).to match_array(%w[dddd dddd dddd])
expect(enabled_namespaces.map { |s| s['id'] }).to include(existing_enabled_namespace.to_gid.to_s)
expect(::Analytics::DevopsAdoption::EnabledNamespace.joins(:namespace)
.where(namespaces: { name: %w[aaaa bbbb cccc] }).count).to eq(3)
end
end
......@@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Mutations::Analytics::DevopsAdoption::Segments::Delete do
RSpec.describe Mutations::Analytics::DevopsAdoption::EnabledNamespaces::Disable do
include GraphqlHelpers
let_it_be(:display_group) { create :group }
......@@ -15,12 +15,12 @@ RSpec.describe Mutations::Analytics::DevopsAdoption::Segments::Delete do
end
let(:current_user) { reporter }
let!(:segment) { create(:devops_adoption_segment, namespace: group, display_namespace: display_group) }
let!(:enabled_namespace) { create(:devops_adoption_enabled_namespace, namespace: group, display_namespace: display_group) }
let(:variables) { { id: segment.to_gid.to_s } }
let(:variables) { { id: enabled_namespace.to_gid.to_s } }
let(:mutation) do
graphql_mutation(:delete_devops_adoption_segment, variables) do
graphql_mutation(:disable_devops_adoption_namespace, variables) do
<<~QL
clientMutationId
errors
......@@ -33,10 +33,10 @@ RSpec.describe Mutations::Analytics::DevopsAdoption::Segments::Delete do
end
def mutation_response
graphql_mutation_response(:delete_devops_adoption_segment)
graphql_mutation_response(:disable_devops_adoption_namespace)
end
context 'when the user cannot manage segments' do
context 'when the user cannot manage enabled_namespaces' do
let(:current_user) { create(:user) }
it_behaves_like 'a mutation that returns a top-level access error'
......@@ -50,32 +50,32 @@ RSpec.describe Mutations::Analytics::DevopsAdoption::Segments::Delete do
it_behaves_like 'a mutation that returns a top-level access error'
end
it 'deletes the segment' do
it 'deletes the enabled_namespace' do
post_graphql_mutation(mutation, current_user: current_user)
expect(mutation_response['errors']).to be_empty
expect(::Analytics::DevopsAdoption::Segment.find_by_id(segment.id)).to eq(nil)
expect(::Analytics::DevopsAdoption::EnabledNamespace.find_by_id(enabled_namespace.id)).to eq(nil)
end
context 'with bulk ids' do
let!(:segment2) { create(:devops_adoption_segment) }
let!(:segment3) { create(:devops_adoption_segment) }
let!(:enabled_namespace2) { create(:devops_adoption_enabled_namespace) }
let!(:enabled_namespace3) { create(:devops_adoption_enabled_namespace) }
let(:variables) { { id: [segment.to_gid.to_s, segment2.to_gid.to_s] } }
let(:variables) { { id: [enabled_namespace.to_gid.to_s, enabled_namespace2.to_gid.to_s] } }
before do
segment2.namespace.add_reporter(current_user)
segment2.display_namespace.add_reporter(current_user)
segment3.namespace.add_reporter(current_user)
segment3.display_namespace.add_reporter(current_user)
enabled_namespace2.namespace.add_reporter(current_user)
enabled_namespace2.display_namespace.add_reporter(current_user)
enabled_namespace3.namespace.add_reporter(current_user)
enabled_namespace3.display_namespace.add_reporter(current_user)
end
it 'deletes the segments specified for deletion' do
it 'deletes the enabled_namespaces specified for deletion' do
post_graphql_mutation(mutation, current_user: current_user)
expect(mutation_response['errors']).to be_empty
expect(::Analytics::DevopsAdoption::Segment.where(id: [segment.id, segment2.id, segment3.id]))
.to match_array([segment3])
expect(::Analytics::DevopsAdoption::EnabledNamespace.where(id: [enabled_namespace.id, enabled_namespace2.id, enabled_namespace3.id]))
.to match_array([enabled_namespace3])
end
end
end
......@@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Mutations::Analytics::DevopsAdoption::Segments::Create do
RSpec.describe Mutations::Analytics::DevopsAdoption::EnabledNamespaces::Enable do
include GraphqlHelpers
let_it_be(:display_group) { create(:group, name: 'aaaa') }
......@@ -20,11 +20,11 @@ RSpec.describe Mutations::Analytics::DevopsAdoption::Segments::Create do
let(:variables) { { namespace_id: group.to_gid.to_s, display_namespace_id: display_group.to_gid.to_s } }
let(:mutation) do
graphql_mutation(:create_devops_adoption_segment, variables) do
graphql_mutation(:enable_devops_adoption_namespace, variables) do
<<-QL.strip_heredoc
clientMutationId
errors
segment {
enabledNamespace {
id
namespace {
name
......@@ -38,14 +38,14 @@ RSpec.describe Mutations::Analytics::DevopsAdoption::Segments::Create do
end
def mutation_response
graphql_mutation_response(:create_devops_adoption_segment)
graphql_mutation_response(:enable_devops_adoption_namespace)
end
before do
stub_licensed_features(group_level_devops_adoption: true)
end
context 'when the user cannot manage segments' do
context 'when the user cannot manage enabled_namespaces' do
let(:current_user) { create(:user) }
it_behaves_like 'a mutation that returns a top-level access error'
......@@ -59,14 +59,14 @@ RSpec.describe Mutations::Analytics::DevopsAdoption::Segments::Create do
it_behaves_like 'a mutation that returns a top-level access error'
end
it 'creates the segment with the group', :aggregate_failures do
it 'creates the enabled_namespace with the group', :aggregate_failures do
post_graphql_mutation(mutation, current_user: current_user)
expect(mutation_response['errors']).to be_empty
segment = mutation_response['segment']
expect(segment['namespace']['name']).to eq('bbbb')
expect(segment['displayNamespace']['name']).to eq('aaaa')
expect(::Analytics::DevopsAdoption::Segment.joins(:namespace).where(namespaces: { name: 'bbbb' }).count).to eq(1)
enabled_namespace = mutation_response['enabledNamespace']
expect(enabled_namespace['namespace']['name']).to eq('bbbb')
expect(enabled_namespace['displayNamespace']['name']).to eq('aaaa')
expect(::Analytics::DevopsAdoption::EnabledNamespace.joins(:namespace).where(namespaces: { name: 'bbbb' }).count).to eq(1)
end
end
......@@ -2,55 +2,55 @@
require 'spec_helper'
RSpec.describe Analytics::DevopsAdoption::Segments::BulkDeleteService do
RSpec.describe Analytics::DevopsAdoption::EnabledNamespaces::BulkDeleteService do
include AdminModeHelper
let_it_be(:group) { create(:group) }
let_it_be(:admin) { create(:user, :admin) }
let(:segment) { create(:devops_adoption_segment, namespace: group) }
let(:segment2) { create(:devops_adoption_segment) }
let(:enabled_namespace) { create(:devops_adoption_enabled_namespace, namespace: group) }
let(:enabled_namespace2) { create(:devops_adoption_enabled_namespace) }
let(:current_user) { admin }
subject(:response) { described_class.new(segments: [segment, segment2], current_user: current_user).execute }
subject(:response) { described_class.new(enabled_namespaces: [enabled_namespace, enabled_namespace2], current_user: current_user).execute }
before do
enable_admin_mode!(admin)
stub_licensed_features(group_level_devops_adoption: true, instance_level_devops_adoption: true)
end
it 'deletes the segments' do
it 'deletes the enabled_namespaces' do
expect(response).to be_success
expect(segment).not_to be_persisted
expect(segment2).not_to be_persisted
expect(enabled_namespace).not_to be_persisted
expect(enabled_namespace2).not_to be_persisted
end
context 'when deletion fails' do
it 'keeps records and returns error response' do
expect(segment).to receive(:destroy).and_raise(ActiveRecord::RecordNotDestroyed)
expect(enabled_namespace).to receive(:destroy).and_raise(ActiveRecord::RecordNotDestroyed)
expect(response).to be_error
expect(response.message).to eq('DevOps Adoption Segment deletion error')
expect(segment).to be_persisted
expect(segment2).to be_persisted
expect(response.message).to eq('DevOps Adoption EnabledNamespace deletion error')
expect(enabled_namespace).to be_persisted
expect(enabled_namespace2).to be_persisted
end
end
it 'authorizes for manage_devops_adoption' do
expect(::Ability).to receive(:allowed?)
.with(current_user, :manage_devops_adoption_segments, group)
.with(current_user, :manage_devops_adoption_namespaces, group)
.at_least(1)
.and_return(true)
expect(::Ability).to receive(:allowed?)
.with(current_user, :manage_devops_adoption_segments, segment.display_namespace)
.with(current_user, :manage_devops_adoption_namespaces, enabled_namespace.display_namespace)
.at_least(1)
.and_return(true)
expect(::Ability).to receive(:allowed?)
.with(current_user, :manage_devops_adoption_segments, segment2.namespace)
.with(current_user, :manage_devops_adoption_namespaces, enabled_namespace2.namespace)
.at_least(1)
.and_return(true)
expect(::Ability).to receive(:allowed?)
.with(current_user, :manage_devops_adoption_segments, segment2.display_namespace)
.with(current_user, :manage_devops_adoption_namespaces, enabled_namespace2.display_namespace)
.at_least(1)
.and_return(true)
......
......@@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Analytics::DevopsAdoption::Segments::BulkFindOrCreateService do
RSpec.describe Analytics::DevopsAdoption::EnabledNamespaces::BulkFindOrCreateService do
let_it_be(:group) { create(:group) }
let_it_be(:group2) { create(:group) }
let_it_be(:display_group) { create(:group) }
......@@ -15,7 +15,7 @@ RSpec.describe Analytics::DevopsAdoption::Segments::BulkFindOrCreateService do
end
end
let_it_be(:segment) { create :devops_adoption_segment, namespace: group, display_namespace: display_group }
let_it_be(:enabled_namespace) { create :devops_adoption_enabled_namespace, namespace: group, display_namespace: display_group }
let(:current_user) { reporter }
let(:params) { { namespaces: [group, group2], display_namespace: display_group } }
......@@ -28,31 +28,31 @@ RSpec.describe Analytics::DevopsAdoption::Segments::BulkFindOrCreateService do
it 'authorizes for manage_devops_adoption', :aggregate_failures do
expect(::Ability).to receive(:allowed?)
.with(current_user, :manage_devops_adoption_segments, group)
.with(current_user, :manage_devops_adoption_namespaces, group)
.at_least(1)
.and_return(true)
expect(::Ability).to receive(:allowed?)
.with(current_user, :manage_devops_adoption_segments, group2)
.with(current_user, :manage_devops_adoption_namespaces, group2)
.at_least(1)
.and_return(true)
expect(::Ability).to receive(:allowed?)
.with(current_user, :manage_devops_adoption_segments, display_group)
.with(current_user, :manage_devops_adoption_namespaces, display_group)
.at_least(2)
.and_return(true)
response
end
context 'when the user cannot manage segments at least for one namespace' do
context 'when the user cannot manage enabled_namespaces at least for one namespace' do
let(:current_user) { create(:user).tap { |u| group.add_reporter(u) } }
it 'returns forbidden error' do
expect { response }.to raise_error(Analytics::DevopsAdoption::Segments::AuthorizationError)
expect { response }.to raise_error(Analytics::DevopsAdoption::EnabledNamespaces::AuthorizationError)
end
end
it 'returns existing segments for namespaces and creates new one if none exists' do
expect { response }.to change { ::Analytics::DevopsAdoption::Segment.count }.by(1)
expect(response.payload.fetch(:segments)).to include(segment)
it 'returns existing enabled_namespaces for namespaces and creates new one if none exists' do
expect { response }.to change { ::Analytics::DevopsAdoption::EnabledNamespace.count }.by(1)
expect(response.payload.fetch(:enabled_namespaces)).to include(enabled_namespace)
end
end
......@@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Analytics::DevopsAdoption::Segments::CreateService do
RSpec.describe Analytics::DevopsAdoption::EnabledNamespaces::CreateService do
let_it_be(:group) { create(:group) }
let_it_be(:display_group) { create(:group) }
......@@ -16,7 +16,7 @@ RSpec.describe Analytics::DevopsAdoption::Segments::CreateService do
let(:current_user) { reporter }
let(:params) { { namespace: group, display_namespace: display_group } }
let(:segment) { subject.payload[:segment] }
let(:enabled_namespace) { subject.payload[:enabled_namespace] }
subject(:response) { described_class.new(params: params, current_user: current_user).execute }
......@@ -24,10 +24,10 @@ RSpec.describe Analytics::DevopsAdoption::Segments::CreateService do
stub_licensed_features(group_level_devops_adoption: true, instance_level_devops_adoption: true)
end
it 'persists the segment', :aggregate_failures do
it 'persists the enabled_namespace', :aggregate_failures do
expect(response).to be_success
expect(segment.namespace).to eq(group)
expect(segment.display_namespace).to eq(display_group)
expect(enabled_namespace.namespace).to eq(group)
expect(enabled_namespace.display_namespace).to eq(display_group)
end
it 'schedules for snapshot creation' do
......@@ -35,12 +35,12 @@ RSpec.describe Analytics::DevopsAdoption::Segments::CreateService do
response
expect(Analytics::DevopsAdoption::CreateSnapshotWorker).to have_received(:perform_async).with(Analytics::DevopsAdoption::Segment.last.id)
expect(Analytics::DevopsAdoption::CreateSnapshotWorker).to have_received(:perform_async).with(Analytics::DevopsAdoption::EnabledNamespace.last.id)
end
it 'authorizes for manage_devops_adoption', :aggregate_failures do
expect(::Ability).to receive(:allowed?).with(current_user, :manage_devops_adoption_segments, group).and_return true
expect(::Ability).to receive(:allowed?).with(current_user, :manage_devops_adoption_segments, display_group).and_return true
expect(::Ability).to receive(:allowed?).with(current_user, :manage_devops_adoption_namespaces, group).and_return true
expect(::Ability).to receive(:allowed?).with(current_user, :manage_devops_adoption_namespaces, display_group).and_return true
response
end
......@@ -51,8 +51,8 @@ RSpec.describe Analytics::DevopsAdoption::Segments::CreateService do
end
it 'authorizes for global manage_devops_adoption', :aggregate_failures do
expect(::Ability).to receive(:allowed?).with(current_user, :manage_devops_adoption_segments, group).and_return true
expect(::Ability).to receive(:allowed?).with(current_user, :manage_devops_adoption_segments, :global).and_return true
expect(::Ability).to receive(:allowed?).with(current_user, :manage_devops_adoption_namespaces, group).and_return true
expect(::Ability).to receive(:allowed?).with(current_user, :manage_devops_adoption_namespaces, :global).and_return true
response
end
......@@ -62,7 +62,7 @@ RSpec.describe Analytics::DevopsAdoption::Segments::CreateService do
let(:current_user) { create(:user) }
it 'returns forbidden error' do
expect { response }.to raise_error(Analytics::DevopsAdoption::Segments::AuthorizationError)
expect { response }.to raise_error(Analytics::DevopsAdoption::EnabledNamespaces::AuthorizationError)
end
end
end
......@@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Analytics::DevopsAdoption::Segments::DeleteService do
RSpec.describe Analytics::DevopsAdoption::EnabledNamespaces::DeleteService do
let_it_be(:group) { create(:group) }
let_it_be(:display_group) { create(:group) }
......@@ -13,41 +13,41 @@ RSpec.describe Analytics::DevopsAdoption::Segments::DeleteService do
end
end
let(:segment) { create(:devops_adoption_segment, namespace: group, display_namespace: display_group) }
let(:enabled_namespace) { create(:devops_adoption_enabled_namespace, namespace: group, display_namespace: display_group) }
let(:current_user) { reporter }
subject(:response) { described_class.new(segment: segment, current_user: current_user).execute }
subject(:response) { described_class.new(enabled_namespace: enabled_namespace, current_user: current_user).execute }
before do
stub_licensed_features(group_level_devops_adoption: true, instance_level_devops_adoption: true)
end
it 'deletes the segment' do
it 'deletes the enabled_namespace' do
expect(response).to be_success
expect(segment).not_to be_persisted
expect(enabled_namespace).not_to be_persisted
end
context 'when deletion fails' do
it 'returns error response' do
expect(segment).to receive(:destroy).and_raise(ActiveRecord::RecordNotDestroyed)
expect(enabled_namespace).to receive(:destroy).and_raise(ActiveRecord::RecordNotDestroyed)
expect(response).to be_error
expect(response.message).to eq('DevOps Adoption Segment deletion error')
expect(response.message).to eq('DevOps Adoption EnabledNamespace deletion error')
end
end
it 'authorizes for manage_devops_adoption', :aggregate_failures do
expect(::Ability).to receive(:allowed?).with(current_user, :manage_devops_adoption_segments, group).and_return true
expect(::Ability).to receive(:allowed?).with(current_user, :manage_devops_adoption_segments, display_group).and_return true
expect(::Ability).to receive(:allowed?).with(current_user, :manage_devops_adoption_namespaces, group).and_return true
expect(::Ability).to receive(:allowed?).with(current_user, :manage_devops_adoption_namespaces, display_group).and_return true
response
end
context 'when user cannot manage segments for the namespace' do
context 'when user cannot manage enabled_namespaces for the namespace' do
let(:current_user) { create(:user) }
it 'returns forbidden error' do
expect { response }.to raise_error(Analytics::DevopsAdoption::Segments::AuthorizationError)
expect { response }.to raise_error(Analytics::DevopsAdoption::EnabledNamespaces::AuthorizationError)
end
end
end
......@@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Analytics::DevopsAdoption::Segments::FindOrCreateService do
RSpec.describe Analytics::DevopsAdoption::EnabledNamespaces::FindOrCreateService do
let_it_be(:group) { create(:group) }
let_it_be(:display_group) { create(:group) }
......@@ -23,22 +23,22 @@ RSpec.describe Analytics::DevopsAdoption::Segments::FindOrCreateService do
stub_licensed_features(group_level_devops_adoption: true, instance_level_devops_adoption: true)
end
context 'when segment for given namespace & display_namespace already exists' do
let!(:segment) { create :devops_adoption_segment, namespace: group, display_namespace: display_group }
context 'when enabled_namespace for given namespace & display_namespace already exists' do
let!(:enabled_namespace) { create :devops_adoption_enabled_namespace, namespace: group, display_namespace: display_group }
it 'returns existing segment' do
expect { response }.not_to change { Analytics::DevopsAdoption::Segment.count }
it 'returns existing enabled_namespace' do
expect { response }.not_to change { Analytics::DevopsAdoption::EnabledNamespace.count }
expect(subject.payload.fetch(:segment)).to eq(segment)
expect(subject.payload.fetch(:enabled_namespace)).to eq(enabled_namespace)
end
end
context 'when segment for given namespace does not exist' do
let!(:segment2) { create :devops_adoption_segment, namespace: group }
let!(:segment3) { create :devops_adoption_segment, display_namespace: display_group }
context 'when enabled_namespace for given namespace does not exist' do
let!(:enabled_namespace2) { create :devops_adoption_enabled_namespace, namespace: group }
let!(:enabled_namespace3) { create :devops_adoption_enabled_namespace, display_namespace: display_group }
it 'calls for segment creation' do
expect_next_instance_of(Analytics::DevopsAdoption::Segments::CreateService,
it 'calls for enabled_namespace creation' do
expect_next_instance_of(Analytics::DevopsAdoption::EnabledNamespaces::CreateService,
current_user: current_user,
params: { namespace: group, display_namespace: display_group }) do |instance|
expect(instance).to receive(:execute).and_return('create_response')
......@@ -50,12 +50,12 @@ RSpec.describe Analytics::DevopsAdoption::Segments::FindOrCreateService do
it 'authorizes for manage_devops_adoption' do
expect(::Ability).to receive(:allowed?)
.with(current_user, :manage_devops_adoption_segments, group)
.with(current_user, :manage_devops_adoption_namespaces, group)
.at_least(1)
.and_return(true)
expect(::Ability).to receive(:allowed?)
.with(current_user, :manage_devops_adoption_segments, display_group)
.with(current_user, :manage_devops_adoption_namespaces, display_group)
.at_least(1)
.and_return(true)
......@@ -66,7 +66,7 @@ RSpec.describe Analytics::DevopsAdoption::Segments::FindOrCreateService do
let(:current_user) { create(:user) }
it 'returns forbidden error' do
expect { response }.to raise_error(Analytics::DevopsAdoption::Segments::AuthorizationError)
expect { response }.to raise_error(Analytics::DevopsAdoption::EnabledNamespaces::AuthorizationError)
end
end
end
......@@ -3,15 +3,15 @@
require 'spec_helper'
RSpec.describe Analytics::DevopsAdoption::Snapshots::CalculateAndSaveService do
let_it_be(:segment) { create :devops_adoption_segment }
let_it_be(:enabled_namespace) { create :devops_adoption_enabled_namespace }
subject { described_class.new(segment: segment, range_end: range_end) }
subject { described_class.new(enabled_namespace: enabled_namespace, range_end: range_end) }
let(:range_end) { Time.zone.now.end_of_month }
let(:snapshot) { nil }
before do
allow_next_instance_of(Analytics::DevopsAdoption::SnapshotCalculator, segment: segment, range_end: range_end, snapshot: snapshot) do |calc|
allow_next_instance_of(Analytics::DevopsAdoption::SnapshotCalculator, enabled_namespace: enabled_namespace, range_end: range_end, snapshot: snapshot) do |calc|
allow(calc).to receive(:calculate).and_return('calculated_data')
end
end
......@@ -25,7 +25,7 @@ RSpec.describe Analytics::DevopsAdoption::Snapshots::CalculateAndSaveService do
end
context 'when a snapshot for given range already exists' do
let(:snapshot) { create :devops_adoption_snapshot, end_time: range_end, namespace: segment.namespace }
let(:snapshot) { create :devops_adoption_snapshot, end_time: range_end, namespace: enabled_namespace.namespace }
it 'updates the snapshot with whatever snapshot calculator returns' do
expect_next_instance_of(Analytics::DevopsAdoption::Snapshots::UpdateService, snapshot: snapshot, params: 'calculated_data') do |service|
......
......@@ -17,11 +17,11 @@ RSpec.describe Analytics::DevopsAdoption::Snapshots::CreateService do
params[:recorded_at] = Time.zone.now
params[:end_time] = 1.month.ago.end_of_month
params[:namespace] = segment.namespace
params[:namespace] = enabled_namespace.namespace
params
end
let(:segment) { create(:devops_adoption_segment) }
let(:enabled_namespace) { create(:devops_adoption_enabled_namespace) }
it 'persists the snapshot' do
expect(subject).to be_success
......
......@@ -6,13 +6,13 @@ RSpec.describe Analytics::DevopsAdoption::CreateAllSnapshotsWorker do
subject(:worker) { described_class.new }
describe "#perform" do
let!(:segment1) { create :devops_adoption_segment }
let!(:segment2) { create :devops_adoption_segment }
let!(:enabled_namespace1) { create :devops_adoption_enabled_namespace }
let!(:enabled_namespace2) { create :devops_adoption_enabled_namespace }
it 'schedules workers for each individual segment' do
it 'schedules workers for each individual enabled_namespace' do
freeze_time do
expect(Analytics::DevopsAdoption::CreateSnapshotWorker).to receive(:perform_in).with(0, segment1.id)
expect(Analytics::DevopsAdoption::CreateSnapshotWorker).to receive(:perform_in).with(5, segment2.id)
expect(Analytics::DevopsAdoption::CreateSnapshotWorker).to receive(:perform_in).with(0, enabled_namespace1.id)
expect(Analytics::DevopsAdoption::CreateSnapshotWorker).to receive(:perform_in).with(5, enabled_namespace2.id)
worker.perform
end
......
......@@ -6,18 +6,18 @@ RSpec.describe Analytics::DevopsAdoption::CreateSnapshotWorker do
subject(:worker) { described_class.new }
describe "#perform" do
let!(:segment) { create :devops_adoption_segment }
let!(:enabled_namespace) { create :devops_adoption_enabled_namespace }
let!(:pending_snapshot) do
create(:devops_adoption_snapshot,
namespace: segment.namespace,
namespace: enabled_namespace.namespace,
end_time: DateTime.parse('2020-10-01').end_of_month,
recorded_at: DateTime.parse('2020-10-20')).reload
end
let!(:finalized_snapshot) do
create(:devops_adoption_snapshot,
namespace: segment.namespace,
namespace: enabled_namespace.namespace,
end_time: DateTime.parse('2020-09-01').end_of_month,
recorded_at: DateTime.parse('2020-10-20')).reload
end
......@@ -26,31 +26,31 @@ RSpec.describe Analytics::DevopsAdoption::CreateSnapshotWorker do
it 'updates metrics for all not finalized snapshots and current month' do
freeze_time do
allow_next_instance_of(Analytics::DevopsAdoption::Snapshots::CalculateAndSaveService, segment: segment, range_end: pending_snapshot.end_time) do |instance|
allow_next_instance_of(Analytics::DevopsAdoption::Snapshots::CalculateAndSaveService, enabled_namespace: enabled_namespace, range_end: pending_snapshot.end_time) do |instance|
expect(instance).to receive(:execute)
end
allow_next_instance_of(Analytics::DevopsAdoption::Snapshots::CalculateAndSaveService, segment: segment, range_end: finalized_snapshot.end_time) do |instance|
allow_next_instance_of(Analytics::DevopsAdoption::Snapshots::CalculateAndSaveService, enabled_namespace: enabled_namespace, range_end: finalized_snapshot.end_time) do |instance|
expect(instance).not_to receive(:execute)
end
allow_next_instance_of(Analytics::DevopsAdoption::Snapshots::CalculateAndSaveService, segment: segment, range_end: Time.zone.now.end_of_month) do |instance|
allow_next_instance_of(Analytics::DevopsAdoption::Snapshots::CalculateAndSaveService, enabled_namespace: enabled_namespace, range_end: Time.zone.now.end_of_month) do |instance|
expect(instance).to receive(:execute)
end
worker.perform(segment.id)
worker.perform(enabled_namespace.id)
end
end
context 'when metric for current month already exists' do
it 'calls for current month calculation only once' do
travel_to(pending_snapshot.recorded_at + 1.day) do
allow_next_instance_of(Analytics::DevopsAdoption::Snapshots::CalculateAndSaveService, segment: segment, range_end: pending_snapshot.end_time) do |instance|
allow_next_instance_of(Analytics::DevopsAdoption::Snapshots::CalculateAndSaveService, enabled_namespace: enabled_namespace, range_end: pending_snapshot.end_time) do |instance|
expect(instance).to receive(:execute).once
end
allow_next_instance_of(Analytics::DevopsAdoption::Snapshots::CalculateAndSaveService, segment: segment, range_end: finalized_snapshot.end_time) do |instance|
allow_next_instance_of(Analytics::DevopsAdoption::Snapshots::CalculateAndSaveService, enabled_namespace: enabled_namespace, range_end: finalized_snapshot.end_time) do |instance|
expect(instance).not_to receive(:execute)
end
worker.perform(segment.id)
worker.perform(enabled_namespace.id)
end
end
end
......@@ -58,17 +58,17 @@ RSpec.describe Analytics::DevopsAdoption::CreateSnapshotWorker do
context 'when today is first day of the month' do
it 'doesnt update metrics for current month' do
travel_to((pending_snapshot.recorded_at + 1.month).beginning_of_month) do
allow_next_instance_of(Analytics::DevopsAdoption::Snapshots::CalculateAndSaveService, segment: segment, range_end: pending_snapshot.end_time) do |instance|
allow_next_instance_of(Analytics::DevopsAdoption::Snapshots::CalculateAndSaveService, enabled_namespace: enabled_namespace, range_end: pending_snapshot.end_time) do |instance|
expect(instance).to receive(:execute)
end
allow_next_instance_of(Analytics::DevopsAdoption::Snapshots::CalculateAndSaveService, segment: segment, range_end: finalized_snapshot.end_time) do |instance|
allow_next_instance_of(Analytics::DevopsAdoption::Snapshots::CalculateAndSaveService, enabled_namespace: enabled_namespace, range_end: finalized_snapshot.end_time) do |instance|
expect(instance).not_to receive(:execute)
end
allow_next_instance_of(Analytics::DevopsAdoption::Snapshots::CalculateAndSaveService, segment: segment, range_end: Time.zone.now.end_of_month) do |instance|
allow_next_instance_of(Analytics::DevopsAdoption::Snapshots::CalculateAndSaveService, enabled_namespace: enabled_namespace, range_end: Time.zone.now.end_of_month) do |instance|
expect(instance).not_to receive(:execute)
end
worker.perform(segment.id)
worker.perform(enabled_namespace.id)
end
end
end
......
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