Commit 0dbbea19 authored by Rémy Coutable's avatar Rémy Coutable

Merge branch '324414-pivot-graphql-api-from-segments-to-groups' into 'master'

Rename Devops Adoption Segment term into "EnabledNamespace" [RUN ALL RSPEC]

See merge request gitlab-org/gitlab!62834
parents 9ea89b24 55481425
This diff is collapsed.
......@@ -17,8 +17,8 @@ import {
TRACK_ADOPTION_TAB_CLICK_EVENT,
TRACK_DEVOPS_SCORE_TAB_CLICK_EVENT,
} from '../constants';
import bulkFindOrCreateDevopsAdoptionSegmentsMutation from '../graphql/mutations/bulk_find_or_create_devops_adoption_segments.mutation.graphql';
import devopsAdoptionSegmentsQuery from '../graphql/queries/devops_adoption_segments.query.graphql';
import bulkEnableDevopsAdoptionNamespacesMutation from '../graphql/mutations/bulk_enable_devops_adoption_namespaces.mutation.graphql';
import devopsAdoptionEnabledNamespacesQuery from '../graphql/queries/devops_adoption_enabled_namespaces.query.graphql';
import getGroupsQuery from '../graphql/queries/get_groups.query.graphql';
import { addSegmentsToCache, deleteSegmentsFromCache } from '../utils/cache_updates';
import { shouldPollTableData } from '../utils/helpers';
......@@ -85,14 +85,14 @@ export default {
};
},
apollo: {
devopsAdoptionSegments: {
query: devopsAdoptionSegmentsQuery,
devopsAdoptionEnabledNamespaces: {
query: devopsAdoptionEnabledNamespacesQuery,
variables() {
return this.segmentsQueryVariables;
},
result({ data }) {
if (this.isGroup) {
const groupEnabled = data.devopsAdoptionSegments.nodes.some(
const groupEnabled = data.devopsAdoptionEnabledNamespaces.nodes.some(
({ namespace: { id } }) => id === this.groupGid,
);
......@@ -114,14 +114,14 @@ export default {
return Boolean(this.groups?.nodes?.length);
},
hasSegmentsData() {
return Boolean(this.devopsAdoptionSegments?.nodes?.length);
return Boolean(this.devopsAdoptionEnabledNamespaces?.nodes?.length);
},
hasLoadingError() {
return Object.values(this.errors).some((error) => error === true);
},
timestamp() {
return dateformat(
this.devopsAdoptionSegments?.nodes[0]?.latestSnapshot?.recordedAt,
this.devopsAdoptionEnabledNamespaces?.nodes[0]?.latestSnapshot?.recordedAt,
DATE_TIME_FORMAT,
);
},
......@@ -129,11 +129,11 @@ export default {
return (
this.isLoadingGroups ||
this.isLoadingEnableGroup ||
this.$apollo.queries.devopsAdoptionSegments.loading
this.$apollo.queries.devopsAdoptionEnabledNamespaces.loading
);
},
segmentLimitReached() {
return this.devopsAdoptionSegments?.nodes?.length > this.$options.maxSegments;
return this.devopsAdoptionEnabledNamespaces?.nodes?.length > this.$options.maxSegments;
},
editGroupsButtonLabel() {
return this.isGroup
......@@ -165,19 +165,19 @@ export default {
this.$apollo
.mutate({
mutation: bulkFindOrCreateDevopsAdoptionSegmentsMutation,
mutation: bulkEnableDevopsAdoptionNamespacesMutation,
variables: {
namespaceIds: [this.groupGid],
},
update: (store, { data }) => {
const {
bulkFindOrCreateDevopsAdoptionSegments: { segments, errors },
bulkEnableDevopsAdoptionNamespaces: { enabledNamespaces, errors },
} = data;
if (errors.length) {
this.handleError(DEVOPS_ADOPTION_ERROR_KEYS.addSegment, errors);
} else {
this.addSegmentsToCache(segments);
this.addSegmentsToCache(enabledNamespaces);
}
},
})
......@@ -190,13 +190,13 @@ export default {
},
pollTableData() {
const shouldPoll = shouldPollTableData({
segments: this.devopsAdoptionSegments.nodes,
timestamp: this.devopsAdoptionSegments?.nodes[0]?.latestSnapshot?.recordedAt,
segments: this.devopsAdoptionEnabledNamespaces.nodes,
timestamp: this.devopsAdoptionEnabledNamespaces?.nodes[0]?.latestSnapshot?.recordedAt,
openModal: this.openModal,
});
if (shouldPoll) {
this.$apollo.queries.devopsAdoptionSegments.refetch();
this.$apollo.queries.devopsAdoptionEnabledNamespaces.refetch();
}
},
trackModalOpenState(state) {
......@@ -311,7 +311,7 @@ export default {
:segment-limit-reached="segmentLimitReached"
:edit-groups-button-label="editGroupsButtonLabel"
:cols="tab.cols"
:segments="devopsAdoptionSegments"
:segments="devopsAdoptionEnabledNamespaces"
@segmentsRemoved="deleteSegmentsFromCache"
@openAddRemoveModal="openAddRemoveModal"
/>
......@@ -327,7 +327,7 @@ export default {
v-if="canRenderModal"
ref="addRemoveModal"
:groups="groups.nodes"
:enabled-groups="devopsAdoptionSegments.nodes"
:enabled-groups="devopsAdoptionEnabledNamespaces.nodes"
@segmentsAdded="addSegmentsToCache"
@segmentsRemoved="deleteSegmentsFromCache"
@trackModalOpenState="trackModalOpenState"
......
......@@ -2,7 +2,7 @@
import { GlModal, GlSprintf, GlAlert } from '@gitlab/ui';
import * as Sentry from '@sentry/browser';
import { DEVOPS_ADOPTION_STRINGS, DEVOPS_ADOPTION_SEGMENT_DELETE_MODAL_ID } from '../constants';
import deleteDevopsAdoptionSegmentMutation from '../graphql/mutations/delete_devops_adoption_segment.mutation.graphql';
import disableDevopsAdoptionNamespaceMutation from '../graphql/mutations/disable_devops_adoption_namespace.mutation.graphql';
export default {
name: 'DevopsAdoptionDeleteModal',
......@@ -57,10 +57,10 @@ export default {
const {
data: {
deleteDevopsAdoptionSegment: { errors },
disableDevopsAdoptionNamespace: { errors },
},
} = await this.$apollo.mutate({
mutation: deleteDevopsAdoptionSegmentMutation,
mutation: disableDevopsAdoptionNamespaceMutation,
variables: {
id: [id],
},
......
......@@ -8,8 +8,8 @@ import {
DEVOPS_ADOPTION_SEGMENT_MODAL_ID,
DEVOPS_ADOPTION_GROUP_LEVEL_LABEL,
} from '../constants';
import bulkFindOrCreateDevopsAdoptionSegmentsMutation from '../graphql/mutations/bulk_find_or_create_devops_adoption_segments.mutation.graphql';
import deleteDevopsAdoptionSegmentMutation from '../graphql/mutations/delete_devops_adoption_segment.mutation.graphql';
import bulkEnableDevopsAdoptionNamespacesMutation from '../graphql/mutations/bulk_enable_devops_adoption_namespaces.mutation.graphql';
import disableDevopsAdoptionNamespaceMutation from '../graphql/mutations/disable_devops_adoption_namespace.mutation.graphql';
export default {
name: 'DevopsAdoptionSegmentModal',
......@@ -139,20 +139,20 @@ export default {
this.loadingAdd = true;
const {
data: {
bulkFindOrCreateDevopsAdoptionSegments: { errors },
bulkEnableDevopsAdoptionNamespaces: { errors },
},
} = await this.$apollo.mutate({
mutation: bulkFindOrCreateDevopsAdoptionSegmentsMutation,
mutation: bulkEnableDevopsAdoptionNamespacesMutation,
variables: {
namespaceIds,
displayNamespaceId: this.groupGid,
},
update: (store, { data }) => {
const {
bulkFindOrCreateDevopsAdoptionSegments: { segments, errors: requestErrors },
bulkEnableDevopsAdoptionNamespaces: { enabledNamespaces, errors: requestErrors },
} = data;
if (!requestErrors.length) this.$emit('segmentsAdded', segments);
if (!requestErrors.length) this.$emit('segmentsAdded', enabledNamespaces);
},
});
......@@ -182,16 +182,16 @@ export default {
const {
data: {
deleteDevopsAdoptionSegment: { errors },
disableDevopsAdoptionNamespace: { errors },
},
} = await this.$apollo.mutate({
mutation: deleteDevopsAdoptionSegmentMutation,
mutation: disableDevopsAdoptionNamespaceMutation,
variables: {
id: removedGroupGids,
},
update: (store, { data }) => {
const {
deleteDevopsAdoptionSegment: { errors: requestErrors },
disableDevopsAdoptionNamespace: { errors: requestErrors },
} = data;
if (!requestErrors.length) this.$emit('segmentsRemoved', removedGroupGids);
......
mutation($namespaceIds: [NamespaceID!]!, $displayNamespaceId: NamespaceID) {
bulkFindOrCreateDevopsAdoptionSegments(
bulkEnableDevopsAdoptionNamespaces(
input: { namespaceIds: $namespaceIds, displayNamespaceId: $displayNamespaceId }
) {
segments {
enabledNamespaces {
id
latestSnapshot {
issueOpened
......
mutation($id: [AnalyticsDevopsAdoptionSegmentID!]!) {
deleteDevopsAdoptionSegment(input: { id: $id }) {
errors
}
}
mutation($id: [AnalyticsDevopsAdoptionEnabledNamespaceID!]!) {
disableDevopsAdoptionNamespace(input: { id: $id }) {
errors
}
}
query devopsAdoptionSegments($displayNamespaceId: NamespaceID) {
devopsAdoptionSegments(displayNamespaceId: $displayNamespaceId) {
query devopsAdoptionEnabledNamespaces($displayNamespaceId: NamespaceID) {
devopsAdoptionEnabledNamespaces(displayNamespaceId: $displayNamespaceId) {
nodes {
id
latestSnapshot {
......
import produce from 'immer';
import devopsAdoptionSegmentsQuery from '../graphql/queries/devops_adoption_segments.query.graphql';
import devopsAdoptionEnabledNamespacesQuery from '../graphql/queries/devops_adoption_enabled_namespaces.query.graphql';
export const addSegmentsToCache = (store, segments, variables) => {
const sourceData = store.readQuery({
query: devopsAdoptionSegmentsQuery,
query: devopsAdoptionEnabledNamespacesQuery,
variables,
});
const data = produce(sourceData, (draftData) => {
draftData.devopsAdoptionSegments.nodes = [
...draftData.devopsAdoptionSegments.nodes,
draftData.devopsAdoptionEnabledNamespaces.nodes = [
...draftData.devopsAdoptionEnabledNamespaces.nodes,
...segments,
];
});
store.writeQuery({
query: devopsAdoptionSegmentsQuery,
query: devopsAdoptionEnabledNamespacesQuery,
variables,
data,
});
......@@ -23,18 +23,18 @@ export const addSegmentsToCache = (store, segments, variables) => {
export const deleteSegmentsFromCache = (store, segmentIds, variables) => {
const sourceData = store.readQuery({
query: devopsAdoptionSegmentsQuery,
query: devopsAdoptionEnabledNamespacesQuery,
variables,
});
const updatedData = produce(sourceData, (draftData) => {
draftData.devopsAdoptionSegments.nodes = draftData.devopsAdoptionSegments.nodes.filter(
draftData.devopsAdoptionEnabledNamespaces.nodes = draftData.devopsAdoptionEnabledNamespaces.nodes.filter(
({ id }) => !segmentIds.includes(id),
);
});
store.writeQuery({
query: devopsAdoptionSegmentsQuery,
query: devopsAdoptionEnabledNamespacesQuery,
variables,
data: updatedData,
});
......
......@@ -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
......
......@@ -11,8 +11,8 @@ import {
DEFAULT_POLLING_INTERVAL,
DEVOPS_ADOPTION_TABLE_CONFIGURATION,
} from 'ee/analytics/devops_report/devops_adoption/constants';
import bulkFindOrCreateDevopsAdoptionSegmentsMutation from 'ee/analytics/devops_report/devops_adoption/graphql/mutations/bulk_find_or_create_devops_adoption_segments.mutation.graphql';
import devopsAdoptionSegments from 'ee/analytics/devops_report/devops_adoption/graphql/queries/devops_adoption_segments.query.graphql';
import bulkEnableDevopsAdoptionNamespacesMutation from 'ee/analytics/devops_report/devops_adoption/graphql/mutations/bulk_enable_devops_adoption_namespaces.mutation.graphql';
import devopsAdoptionEnabledNamespaces from 'ee/analytics/devops_report/devops_adoption/graphql/queries/devops_adoption_enabled_namespaces.query.graphql';
import getGroupsQuery from 'ee/analytics/devops_report/devops_adoption/graphql/queries/get_groups.query.graphql';
import { addSegmentsToCache } from 'ee/analytics/devops_report/devops_adoption/utils/cache_updates';
import createMockApollo from 'helpers/mock_apollo_helper';
......@@ -24,8 +24,8 @@ import {
groupNodes,
nextGroupNode,
groupPageInfo,
devopsAdoptionSegmentsData,
devopsAdoptionSegmentsDataEmpty,
devopsAdoptionNamespaceData,
devopsAdoptionNamespaceDataEmpty,
} from '../mock_data';
jest.mock('ee/analytics/devops_report/devops_adoption/utils/cache_updates', () => ({
......@@ -45,13 +45,13 @@ describe('DevopsAdoptionApp', () => {
let wrapper;
const groupsEmpty = jest.fn().mockResolvedValue({ __typename: 'Groups', nodes: [] });
const segmentsEmpty = jest
.fn()
.mockResolvedValue({ data: { devopsAdoptionSegments: devopsAdoptionSegmentsDataEmpty } });
const segmentsEmpty = jest.fn().mockResolvedValue({
data: { devopsAdoptionEnabledNamespaces: devopsAdoptionNamespaceDataEmpty },
});
const addSegmentMutationSpy = jest.fn().mockResolvedValue({
data: {
bulkFindOrCreateDevopsAdoptionSegments: {
segments: [devopsAdoptionSegmentsData.nodes[0]],
bulkEnableDevopsAdoptionNamespaces: {
enabledNamespaces: [devopsAdoptionNamespaceData.nodes[0]],
errors: [],
},
},
......@@ -66,8 +66,8 @@ describe('DevopsAdoptionApp', () => {
const mockApollo = createMockApollo(
[
[bulkFindOrCreateDevopsAdoptionSegmentsMutation, addSegmentsSpy],
[devopsAdoptionSegments, segmentsSpy],
[bulkEnableDevopsAdoptionNamespacesMutation, addSegmentsSpy],
[devopsAdoptionEnabledNamespaces, segmentsSpy],
],
{
Query: {
......@@ -291,12 +291,12 @@ describe('DevopsAdoptionApp', () => {
});
describe('when there is an active group', () => {
const groupGid = devopsAdoptionSegmentsData.nodes[0].namespace.id;
const groupGid = devopsAdoptionNamespaceData.nodes[0].namespace.id;
describe('which is enabled', () => {
beforeEach(async () => {
const segmentsWithData = jest.fn().mockResolvedValue({
data: { devopsAdoptionSegments: devopsAdoptionSegmentsData },
data: { devopsAdoptionEnabledNamespaces: devopsAdoptionNamespaceData },
});
const mockApollo = createMockApolloProvider({
segmentsSpy: segmentsWithData,
......@@ -341,7 +341,7 @@ describe('DevopsAdoptionApp', () => {
expect(addSegmentsToCache).toHaveBeenCalledTimes(1);
expect(addSegmentsToCache).toHaveBeenCalledWith(
expect.anything(),
[devopsAdoptionSegmentsData.nodes[0]],
[devopsAdoptionNamespaceData.nodes[0]],
{
displayNamespaceId: groupGid,
},
......@@ -523,7 +523,7 @@ describe('DevopsAdoptionApp', () => {
mockApollo,
provide: {
isGroup: true,
groupGid: devopsAdoptionSegmentsData.nodes[0].namespace.id,
groupGid: devopsAdoptionNamespaceData.nodes[0].namespace.id,
},
});
});
......
......@@ -5,13 +5,13 @@ import Vue from 'vue';
import VueApollo from 'vue-apollo';
import DevopsAdoptionDeleteModal from 'ee/analytics/devops_report/devops_adoption/components/devops_adoption_delete_modal.vue';
import { DEVOPS_ADOPTION_SEGMENT_DELETE_MODAL_ID } from 'ee/analytics/devops_report/devops_adoption/constants';
import deleteDevopsAdoptionSegmentMutation from 'ee/analytics/devops_report/devops_adoption/graphql/mutations/delete_devops_adoption_segment.mutation.graphql';
import disableDevopsAdoptionNamespaceMutation from 'ee/analytics/devops_report/devops_adoption/graphql/mutations/disable_devops_adoption_namespace.mutation.graphql';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import {
genericDeleteErrorMessage,
dataErrorMessage,
devopsAdoptionSegmentsData,
devopsAdoptionNamespaceData,
} from '../mock_data';
const localVue = createLocalVue();
......@@ -20,14 +20,14 @@ Vue.use(VueApollo);
const mockEvent = { preventDefault: jest.fn() };
const mutate = jest.fn().mockResolvedValue({
data: {
deleteDevopsAdoptionSegment: {
disableDevopsAdoptionNamespace: {
errors: [],
},
},
});
const mutateWithDataErrors = jest.fn().mockResolvedValue({
data: {
deleteDevopsAdoptionSegment: {
disableDevopsAdoptionNamespace: {
errors: [dataErrorMessage],
},
},
......@@ -39,13 +39,15 @@ describe('DevopsAdoptionDeleteModal', () => {
let wrapper;
const createComponent = ({ deleteSegmentsSpy = mutate, props = {} } = {}) => {
const mockApollo = createMockApollo([[deleteDevopsAdoptionSegmentMutation, deleteSegmentsSpy]]);
const mockApollo = createMockApollo([
[disableDevopsAdoptionNamespaceMutation, deleteSegmentsSpy],
]);
wrapper = shallowMount(DevopsAdoptionDeleteModal, {
localVue,
apolloProvider: mockApollo,
propsData: {
segment: devopsAdoptionSegmentsData.nodes[0],
segment: devopsAdoptionNamespaceData.nodes[0],
...props,
},
stubs: {
......@@ -75,7 +77,7 @@ describe('DevopsAdoptionDeleteModal', () => {
});
it('displays the confirmation message', () => {
const text = `Are you sure that you would like to remove ${devopsAdoptionSegmentsData.nodes[0].namespace.fullName} from the table?`;
const text = `Are you sure that you would like to remove ${devopsAdoptionNamespaceData.nodes[0].namespace.fullName} from the table?`;
expect(findModal().text()).toBe(text);
});
......@@ -136,14 +138,14 @@ describe('DevopsAdoptionDeleteModal', () => {
it('submits the correct request variables', () => {
expect(mutate).toHaveBeenCalledWith({
id: [devopsAdoptionSegmentsData.nodes[0].id],
id: [devopsAdoptionNamespaceData.nodes[0].id],
});
});
it('emits segmentsRemoved with the correct variables', () => {
const [params] = wrapper.emitted().segmentsRemoved[0];
expect(params).toStrictEqual([devopsAdoptionSegmentsData.nodes[0].id]);
expect(params).toStrictEqual([devopsAdoptionNamespaceData.nodes[0].id]);
});
it('closes the modal after a successful mutation', () => {
......
......@@ -7,7 +7,7 @@ import DevopsAdoptionTable from 'ee/analytics/devops_report/devops_adoption/comp
import { DEVOPS_ADOPTION_TABLE_CONFIGURATION } from 'ee/analytics/devops_report/devops_adoption/constants';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import { devopsAdoptionSegmentsData } from '../mock_data';
import { devopsAdoptionNamespaceData } from '../mock_data';
describe('DevopsAdoptionSection', () => {
let wrapper;
......@@ -23,7 +23,7 @@ describe('DevopsAdoptionSection', () => {
segmentLimitReached: false,
editGroupsButtonLabel: 'Add/Remove groups',
cols: DEVOPS_ADOPTION_TABLE_CONFIGURATION[0].cols,
segments: devopsAdoptionSegmentsData,
segments: devopsAdoptionNamespaceData,
addSegmentButtonTooltipText: 'Maximum 30 groups allowed',
...props,
},
......
......@@ -5,8 +5,8 @@ import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import DevopsAdoptionSegmentModal from 'ee/analytics/devops_report/devops_adoption/components/devops_adoption_segment_modal.vue';
import { DEVOPS_ADOPTION_SEGMENT_MODAL_ID } from 'ee/analytics/devops_report/devops_adoption/constants';
import bulkFindOrCreateDevopsAdoptionSegmentsMutation from 'ee/analytics/devops_report/devops_adoption/graphql/mutations/bulk_find_or_create_devops_adoption_segments.mutation.graphql';
import deleteDevopsAdoptionSegmentMutation from 'ee/analytics/devops_report/devops_adoption/graphql/mutations/delete_devops_adoption_segment.mutation.graphql';
import bulkEnableDevopsAdoptionNamespacesMutation from 'ee/analytics/devops_report/devops_adoption/graphql/mutations/bulk_enable_devops_adoption_namespaces.mutation.graphql';
import disableDevopsAdoptionNamespaceMutation from 'ee/analytics/devops_report/devops_adoption/graphql/mutations/disable_devops_adoption_namespace.mutation.graphql';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import {
......@@ -16,7 +16,7 @@ import {
genericErrorMessage,
dataErrorMessage,
groupNodeLabelValues,
devopsAdoptionSegmentsData,
devopsAdoptionNamespaceData,
} from '../mock_data';
const localVue = createLocalVue();
......@@ -25,25 +25,25 @@ Vue.use(VueApollo);
const mockEvent = { preventDefault: jest.fn() };
const mutate = jest.fn().mockResolvedValue({
data: {
bulkFindOrCreateDevopsAdoptionSegments: {
segments: [devopsAdoptionSegmentsData.nodes[0]],
bulkEnableDevopsAdoptionNamespaces: {
enabledNamespaces: [devopsAdoptionNamespaceData.nodes[0]],
errors: [],
},
deleteDevopsAdoptionSegment: {
segments: [devopsAdoptionSegmentsData.nodes[0]],
disableDevopsAdoptionNamespace: {
enabledNamespaces: [devopsAdoptionNamespaceData.nodes[0]],
errors: [],
},
},
});
const mutateWithDataErrors = jest.fn().mockResolvedValue({
data: {
bulkFindOrCreateDevopsAdoptionSegments: {
bulkEnableDevopsAdoptionNamespaces: {
errors: [dataErrorMessage],
segments: [],
enabledNamespaces: [],
},
deleteDevopsAdoptionSegment: {
disableDevopsAdoptionNamespace: {
errors: [dataErrorMessage],
segments: [],
enabledNamespaces: [],
},
},
});
......@@ -60,8 +60,8 @@ describe('DevopsAdoptionSegmentModal', () => {
provide = {},
} = {}) => {
const mockApollo = createMockApollo([
[deleteDevopsAdoptionSegmentMutation, deleteSegmentsSpy],
[bulkFindOrCreateDevopsAdoptionSegmentsMutation, addSegmentsSpy],
[disableDevopsAdoptionNamespaceMutation, deleteSegmentsSpy],
[bulkEnableDevopsAdoptionNamespacesMutation, addSegmentsSpy],
]);
wrapper = shallowMount(DevopsAdoptionSegmentModal, {
......@@ -236,7 +236,7 @@ describe('DevopsAdoptionSegmentModal', () => {
const enableFirstGroup = { checkboxValues: [groupIds[0]] };
const enableSecondGroup = { checkboxValues: [groupIds[1]] };
const noEnabledGroups = { checkboxValues: [] };
const firstGroupEnabledData = [devopsAdoptionSegmentsData.nodes[0]];
const firstGroupEnabledData = [devopsAdoptionNamespaceData.nodes[0]];
const firstGroupId = [groupIds[0]];
const firstGroupGid = [groupGids[0]];
const secondGroupGid = [groupGids[1]];
......@@ -311,7 +311,7 @@ describe('DevopsAdoptionSegmentModal', () => {
it('emits segmentsAdded with the correct variables', () => {
const [params] = wrapper.emitted().segmentsAdded[0];
expect(params).toStrictEqual([devopsAdoptionSegmentsData.nodes[0]]);
expect(params).toStrictEqual([devopsAdoptionNamespaceData.nodes[0]]);
});
}
......
......@@ -10,7 +10,7 @@ import {
} from 'ee/analytics/devops_report/devops_adoption/constants';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import { devopsAdoptionSegmentsData, devopsAdoptionTableHeaders } from '../mock_data';
import { devopsAdoptionNamespaceData, devopsAdoptionTableHeaders } from '../mock_data';
describe('DevopsAdoptionTable', () => {
let wrapper;
......@@ -21,7 +21,7 @@ describe('DevopsAdoptionTable', () => {
wrapper = mount(DevopsAdoptionTable, {
propsData: {
cols: DEVOPS_ADOPTION_TABLE_CONFIGURATION[0].cols,
segments: devopsAdoptionSegmentsData.nodes,
segments: devopsAdoptionNamespaceData.nodes,
},
provide,
directives: {
......@@ -112,7 +112,7 @@ describe('DevopsAdoptionTable', () => {
});
describe('"This group" badge', () => {
const thisGroupGid = devopsAdoptionSegmentsData.nodes[0].namespace.id;
const thisGroupGid = devopsAdoptionNamespaceData.nodes[0].namespace.id;
it.each`
scenario | expected | provide
......@@ -165,9 +165,9 @@ describe('DevopsAdoptionTable', () => {
});
describe.each`
scenario | tooltipText | provide | disabled
${'not active group'} | ${'Remove Group from the table.'} | ${{}} | ${false}
${'active group'} | ${'You cannot remove the group you are currently in.'} | ${{ groupGid: devopsAdoptionSegmentsData.nodes[0].namespace.id }} | ${true}
scenario | tooltipText | provide | disabled
${'not active group'} | ${'Remove Group from the table.'} | ${{}} | ${false}
${'active group'} | ${'You cannot remove the group you are currently in.'} | ${{ groupGid: devopsAdoptionNamespaceData.nodes[0].namespace.id }} | ${true}
`('actions column when $scenario', ({ tooltipText, provide, disabled }) => {
beforeEach(() => {
createComponent({ provide });
......@@ -197,7 +197,9 @@ describe('DevopsAdoptionTable', () => {
beforeEach(() => {
createComponent();
wrapper.setData({ selectedSegment: devopsAdoptionSegmentsData.nodes[0] });
wrapper.setData({
selectedSegment: devopsAdoptionNamespaceData.nodes[0],
});
});
it('re emits trackModalOpenState with the given value', async () => {
......
......@@ -39,7 +39,7 @@ export const groupPageInfo = {
nextPage: 2,
};
export const devopsAdoptionSegmentsData = {
export const devopsAdoptionNamespaceData = {
nodes: [
{
id: 1,
......@@ -59,7 +59,7 @@ export const devopsAdoptionSegmentsData = {
recordedAt: '2020-10-31T23:59:59Z',
__typename: 'latestSnapshot',
},
__typename: 'devopsSegment',
__typename: 'devopsAdoptionEnabledNamespace',
},
{
id: 2,
......@@ -68,13 +68,13 @@ export const devopsAdoptionSegmentsData = {
id: 'gid://gitlab/Group/2',
},
latestSnapshot: null,
__typename: 'devopsSegment',
__typename: 'devopsAdoptionEnabledNamespace',
},
],
__typename: 'devopsAdoptionSegments',
__typename: 'devopsAdoptionEnabledNamespaces',
};
export const devopsAdoptionSegmentsDataEmpty = {
export const devopsAdoptionNamespaceDataEmpty = {
nodes: [],
__typename: 'devopsAdoptionSegments',
};
......
......@@ -2,22 +2,22 @@ import {
deleteSegmentsFromCache,
addSegmentsToCache,
} from 'ee/analytics/devops_report/devops_adoption/utils/cache_updates';
import { devopsAdoptionSegmentsData } from '../mock_data';
import { devopsAdoptionNamespaceData } from '../mock_data';
describe('addSegmentsToCache', () => {
const store = {
readQuery: jest.fn(() => ({ devopsAdoptionSegments: { nodes: [] } })),
readQuery: jest.fn(() => ({ devopsAdoptionEnabledNamespaces: { nodes: [] } })),
writeQuery: jest.fn(),
};
it('calls writeQuery with the correct response', () => {
addSegmentsToCache(store, devopsAdoptionSegmentsData.nodes);
addSegmentsToCache(store, devopsAdoptionNamespaceData.nodes);
expect(store.writeQuery).toHaveBeenCalledWith(
expect.objectContaining({
data: {
devopsAdoptionSegments: {
nodes: devopsAdoptionSegmentsData.nodes,
devopsAdoptionEnabledNamespaces: {
nodes: devopsAdoptionNamespaceData.nodes,
},
},
}),
......@@ -27,20 +27,20 @@ describe('addSegmentsToCache', () => {
describe('deleteSegmentsFromCache', () => {
const store = {
readQuery: jest.fn(() => ({ devopsAdoptionSegments: devopsAdoptionSegmentsData })),
readQuery: jest.fn(() => ({ devopsAdoptionEnabledNamespaces: devopsAdoptionNamespaceData })),
writeQuery: jest.fn(),
};
it('calls writeQuery with the correct response', () => {
// Remove the item at the first index
deleteSegmentsFromCache(store, [devopsAdoptionSegmentsData.nodes[0].id]);
deleteSegmentsFromCache(store, [devopsAdoptionNamespaceData.nodes[0].id]);
expect(store.writeQuery).toHaveBeenCalledWith(
expect.not.objectContaining({
data: {
devopsAdoptionSegments: {
__typename: 'devopsAdoptionSegments',
nodes: devopsAdoptionSegmentsData.nodes,
devopsAdoptionEnabledNamespaces: {
__typename: 'devopsAdoptionEnabledNamespaces',
nodes: devopsAdoptionNamespaceData.nodes,
},
},
}),
......@@ -48,10 +48,10 @@ describe('deleteSegmentsFromCache', () => {
expect(store.writeQuery).toHaveBeenCalledWith(
expect.objectContaining({
data: {
devopsAdoptionSegments: {
__typename: 'devopsAdoptionSegments',
devopsAdoptionEnabledNamespaces: {
__typename: 'devopsAdoptionEnabledNamespaces',
// Remove the item at the first index
nodes: devopsAdoptionSegmentsData.nodes.slice(1),
nodes: devopsAdoptionNamespaceData.nodes.slice(1),
},
},
}),
......
import { shouldPollTableData } from 'ee/analytics/devops_report/devops_adoption/utils/helpers';
import { devopsAdoptionSegmentsData } from '../mock_data';
import { devopsAdoptionNamespaceData } from '../mock_data';
describe('shouldPollTableData', () => {
const { nodes: pendingData } = devopsAdoptionSegmentsData;
const { nodes: pendingData } = devopsAdoptionNamespaceData;
const comepleteData = [pendingData[0]];
const mockDate = '2020-07-06T00:00:00.000Z';
const previousDay = '2020-07-05T00:00:00.000Z';
......
......@@ -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