From 478e20ce28fb7b9df789ea42d1ecf7ab9898327c Mon Sep 17 00:00:00 2001
From: Pavel Shutsin <pshutsin@gitlab.com>
Date: Thu, 3 Jun 2021 10:37:11 +0200
Subject: [PATCH] Restore previous month perspective

DevOps adption page should display data
for previous month

Changelog: changed
EE: true
---
 config/gitlab.yml.example                     |  2 +-
 config/initializers/1_settings.rb             |  2 +-
 doc/api/graphql/reference/index.md            | 44 +++++++++++++++++-
 .../devops_adoption/constants.js              |  2 +-
 .../devops_adoption/snapshots_finder.rb       | 29 ++++++++++++
 .../enabled_namespaces_resolver.rb            | 15 +++++--
 .../devops_adoption/snapshots_resolver.rb     | 45 +++++++++++++++++++
 .../devops_adoption/enabled_namespace_type.rb |  8 +++-
 .../analytics/devops_adoption/snapshot.rb     |  7 ++-
 .../devops_adoption/create_snapshot_worker.rb |  8 ++--
 .../devops_adoption/snapshots_finder_spec.rb  | 41 +++++++++++++++++
 .../devops_adoption_section_spec.js           |  2 +-
 .../devops_adoption/snapshot_spec.rb          | 42 ++++++++++++++---
 .../enabled_namespaces_spec.rb                | 10 +++++
 .../create_snapshot_worker_spec.rb            | 23 +++++-----
 locale/gitlab.pot                             |  2 +-
 16 files changed, 246 insertions(+), 36 deletions(-)
 create mode 100644 ee/app/finders/analytics/devops_adoption/snapshots_finder.rb
 create mode 100644 ee/app/graphql/resolvers/analytics/devops_adoption/snapshots_resolver.rb
 create mode 100644 ee/spec/finders/analytics/devops_adoption/snapshots_finder_spec.rb

diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index 557fbd0a10754..c9b056ce95665 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -505,7 +505,7 @@ production: &base
   ee_cron_jobs:
     # Schedule snapshots for all devops adoption segments
     analytics_devops_adoption_create_all_snapshots_worker:
-      cron: 0 4 * * 0
+      cron: 0 0 1 * *
 
     # Snapshot active users statistics
     historical_data_worker:
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index d589bc6069a68..8f4c6492cadaa 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -586,7 +586,7 @@
 
 Gitlab.ee do
   Settings.cron_jobs['analytics_devops_adoption_create_all_snapshots_worker'] ||= Settingslogic.new({})
-  Settings.cron_jobs['analytics_devops_adoption_create_all_snapshots_worker']['cron'] ||= '0 4 * * 0'
+  Settings.cron_jobs['analytics_devops_adoption_create_all_snapshots_worker']['cron'] ||= '0 0 1 * *'
   Settings.cron_jobs['analytics_devops_adoption_create_all_snapshots_worker']['job_class'] = 'Analytics::DevopsAdoption::CreateAllSnapshotsWorker'
   Settings.cron_jobs['active_user_count_threshold_worker'] ||= Settingslogic.new({})
   Settings.cron_jobs['active_user_count_threshold_worker']['cron'] ||= '0 12 * * *'
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 4bf40e1c69aa8..be544331e8b92 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -5033,6 +5033,29 @@ The edge type for [`DevopsAdoptionEnabledNamespace`](#devopsadoptionenablednames
 | <a id="devopsadoptionenablednamespaceedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
 | <a id="devopsadoptionenablednamespaceedgenode"></a>`node` | [`DevopsAdoptionEnabledNamespace`](#devopsadoptionenablednamespace) | The item at the end of the edge. |
 
+#### `DevopsAdoptionSnapshotConnection`
+
+The connection type for [`DevopsAdoptionSnapshot`](#devopsadoptionsnapshot).
+
+##### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="devopsadoptionsnapshotconnectionedges"></a>`edges` | [`[DevopsAdoptionSnapshotEdge]`](#devopsadoptionsnapshotedge) | A list of edges. |
+| <a id="devopsadoptionsnapshotconnectionnodes"></a>`nodes` | [`[DevopsAdoptionSnapshot]`](#devopsadoptionsnapshot) | A list of nodes. |
+| <a id="devopsadoptionsnapshotconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
+
+#### `DevopsAdoptionSnapshotEdge`
+
+The edge type for [`DevopsAdoptionSnapshot`](#devopsadoptionsnapshot).
+
+##### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="devopsadoptionsnapshotedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
+| <a id="devopsadoptionsnapshotedgenode"></a>`node` | [`DevopsAdoptionSnapshot`](#devopsadoptionsnapshot) | The item at the end of the edge. |
+
 #### `DiscussionConnection`
 
 The connection type for [`Discussion`](#discussion).
@@ -8191,9 +8214,28 @@ Enabled namespace for DevopsAdoption.
 | ---- | ---- | ----------- |
 | <a id="devopsadoptionenablednamespacedisplaynamespace"></a>`displayNamespace` | [`Namespace`](#namespace) | Namespace where data should be displayed. |
 | <a id="devopsadoptionenablednamespaceid"></a>`id` | [`ID!`](#id) | ID of the enabled namespace. |
-| <a id="devopsadoptionenablednamespacelatestsnapshot"></a>`latestSnapshot` | [`DevopsAdoptionSnapshot`](#devopsadoptionsnapshot) | The latest adoption metrics for the enabled namespace. |
+| <a id="devopsadoptionenablednamespacelatestsnapshot"></a>`latestSnapshot` | [`DevopsAdoptionSnapshot`](#devopsadoptionsnapshot) | Metrics snapshot for previous month for the enabled namespace. |
 | <a id="devopsadoptionenablednamespacenamespace"></a>`namespace` | [`Namespace`](#namespace) | Namespace which should be calculated. |
 
+#### Fields with arguments
+
+##### `DevopsAdoptionEnabledNamespace.snapshots`
+
+Data snapshots of the namespace.
+
+Returns [`DevopsAdoptionSnapshotConnection`](#devopsadoptionsnapshotconnection).
+
+This field returns a [connection](#connections). It accepts the
+four standard [pagination arguments](#connection-pagination-arguments):
+`before: String`, `after: String`, `first: Int`, `last: Int`.
+
+###### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="devopsadoptionenablednamespacesnapshotsendtimeafter"></a>`endTimeAfter` | [`Time`](#time) | Filter to snapshots with month end after the provided date. |
+| <a id="devopsadoptionenablednamespacesnapshotsendtimebefore"></a>`endTimeBefore` | [`Time`](#time) | Filter to snapshots with month end before the provided date. |
+
 ### `DevopsAdoptionSnapshot`
 
 Snapshot.
diff --git a/ee/app/assets/javascripts/analytics/devops_report/devops_adoption/constants.js b/ee/app/assets/javascripts/analytics/devops_report/devops_adoption/constants.js
index 2f95d92bb77bb..ba883ebf6718f 100644
--- a/ee/app/assets/javascripts/analytics/devops_report/devops_adoption/constants.js
+++ b/ee/app/assets/javascripts/analytics/devops_report/devops_adoption/constants.js
@@ -19,7 +19,7 @@ export const DEVOPS_ADOPTION_ERROR_KEYS = {
 };
 
 export const TABLE_HEADER_TEXT = s__(
-  'DevopsAdoption|Feature adoption is based on usage in the current calendar month. Last updated: %{timestamp}.',
+  'DevopsAdoption|Feature adoption is based on usage in the previous calendar month. Last updated: %{timestamp}.',
 );
 
 export const DEVOPS_ADOPTION_GROUP_LEVEL_LABEL = s__('DevopsAdoption|Add/remove sub-groups');
diff --git a/ee/app/finders/analytics/devops_adoption/snapshots_finder.rb b/ee/app/finders/analytics/devops_adoption/snapshots_finder.rb
new file mode 100644
index 0000000000000..73f4b20568234
--- /dev/null
+++ b/ee/app/finders/analytics/devops_adoption/snapshots_finder.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module Analytics
+  module DevopsAdoption
+    class SnapshotsFinder
+      attr_reader :params
+
+      def initialize(params:)
+        @params = params
+      end
+
+      def execute
+        scope = Snapshot.by_end_time
+        scope = by_namespace(scope)
+        by_timespan(scope)
+      end
+
+      private
+
+      def by_namespace(scope)
+        scope.for_namespaces(params[:namespace_id])
+      end
+
+      def by_timespan(scope)
+        scope.for_timespan(from: params[:end_time_after], to: params[:end_time_before])
+      end
+    end
+  end
+end
diff --git a/ee/app/graphql/resolvers/analytics/devops_adoption/enabled_namespaces_resolver.rb b/ee/app/graphql/resolvers/analytics/devops_adoption/enabled_namespaces_resolver.rb
index d4af87ce6e6c8..d08404392f305 100644
--- a/ee/app/graphql/resolvers/analytics/devops_adoption/enabled_namespaces_resolver.rb
+++ b/ee/app/graphql/resolvers/analytics/devops_adoption/enabled_namespaces_resolver.rb
@@ -6,6 +6,7 @@ module DevopsAdoption
       class EnabledNamespacesResolver < BaseResolver
         include Gitlab::Graphql::Authorize::AuthorizeResource
         include Gitlab::Allowable
+        include LooksAhead
 
         type Types::Analytics::DevopsAdoption::EnabledNamespaceType, null: true
 
@@ -13,19 +14,25 @@ class EnabledNamespacesResolver < BaseResolver
                  required: false,
                  description: 'Filter by display namespace.'
 
-        def resolve(display_namespace_id: nil, **)
+        def resolve_with_lookahead(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
+          apply_lookahead(finder_class.new(current_user, params: { display_namespace: display_namespace }).execute)
+        end
+
+        def unconditional_includes
+          [:display_namespace]
         end
 
         private
 
+        def finder_class
+          ::Analytics::DevopsAdoption::EnabledNamespacesFinder
+        end
+
         def authorize!(display_namespace)
           display_namespace ? authorize_with_namespace!(display_namespace) : authorize_global!
         end
diff --git a/ee/app/graphql/resolvers/analytics/devops_adoption/snapshots_resolver.rb b/ee/app/graphql/resolvers/analytics/devops_adoption/snapshots_resolver.rb
new file mode 100644
index 0000000000000..70a18ecdde0cf
--- /dev/null
+++ b/ee/app/graphql/resolvers/analytics/devops_adoption/snapshots_resolver.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+module Resolvers
+  module Analytics
+    module DevopsAdoption
+      class SnapshotsResolver < BaseResolver
+        include Gitlab::Allowable
+
+        type Types::Analytics::DevopsAdoption::SnapshotType.connection_type, null: true
+
+        argument :end_time_before,
+                 ::Types::TimeType,
+                 required: false,
+                 description: 'Filter to snapshots with month end before the provided date.'
+
+        argument :end_time_after,
+                 ::Types::TimeType,
+                 required: false,
+                 description: 'Filter to snapshots with month end after the provided date.'
+
+        def resolve(end_time_after: nil, end_time_before: nil)
+          return [] unless authorize(object)
+
+          params = {
+            end_time_after: end_time_after,
+            end_time_before: end_time_before,
+            namespace_id: object.namespace_id
+          }
+
+          ::Analytics::DevopsAdoption::SnapshotsFinder.new(params: params).execute
+        end
+
+        private
+
+        def authorize(enabled_namespace)
+          if enabled_namespace.display_namespace
+            can?(current_user, :view_group_devops_adoption, enabled_namespace.display_namespace)
+          else
+            can?(current_user, :view_instance_devops_adoption, :global)
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/ee/app/graphql/types/analytics/devops_adoption/enabled_namespace_type.rb b/ee/app/graphql/types/analytics/devops_adoption/enabled_namespace_type.rb
index 33802cca22b26..d6d30e3773cb6 100644
--- a/ee/app/graphql/types/analytics/devops_adoption/enabled_namespace_type.rb
+++ b/ee/app/graphql/types/analytics/devops_adoption/enabled_namespace_type.rb
@@ -17,11 +17,15 @@ class EnabledNamespaceType < BaseObject
         field :display_namespace, Types::NamespaceType, null: true,
               description: 'Namespace where data should be displayed.'
 
+        field :snapshots,
+              description: 'Data snapshots of the namespace.',
+              resolver: Resolvers::Analytics::DevopsAdoption::SnapshotsResolver
+
         field :latest_snapshot, SnapshotType, null: true,
-              description: 'The latest adoption metrics for the enabled namespace.'
+              description: 'Metrics snapshot for previous month for the enabled namespace.'
 
         def latest_snapshot
-          BatchLoader::GraphQL.for(object.namespace_id).batch(key: :devops_adoption_latest_snapshots) do |ids, loader, args|
+          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)
diff --git a/ee/app/models/analytics/devops_adoption/snapshot.rb b/ee/app/models/analytics/devops_adoption/snapshot.rb
index ec976cf9a7d3a..34addf604eecc 100644
--- a/ee/app/models/analytics/devops_adoption/snapshot.rb
+++ b/ee/app/models/analytics/devops_adoption/snapshot.rb
@@ -25,6 +25,7 @@ class Analytics::DevopsAdoption::Snapshot < ApplicationRecord
   scope :latest_snapshot_for_namespace_ids, -> (ids) do
     inner_select = model
       .default_scoped
+      .finalized
       .distinct
       .select("FIRST_VALUE(id) OVER (PARTITION BY namespace_id ORDER BY end_time DESC) as id")
       .where(namespace_id: ids)
@@ -33,9 +34,13 @@ class Analytics::DevopsAdoption::Snapshot < ApplicationRecord
   end
 
   scope :for_month, -> (month_date) { where(end_time: month_date.end_of_month) }
-  scope :not_finalized, -> { where(arel_table[:recorded_at].lteq(arel_table[:end_time])) }
+  scope :not_finalized, -> { where(arel_table[:recorded_at].lt(arel_table[:end_time])) }
+  scope :finalized, -> { where(arel_table[:recorded_at].gteq(arel_table[:end_time])) }
   scope :by_end_time, -> { order(end_time: :desc) }
 
+  scope :for_timespan, -> (from: nil, to: nil) { where(end_time: from..to) }
+  scope :for_namespaces, -> (ids) { where(namespace: ids) }
+
   def start_time
     end_time.beginning_of_month
   end
diff --git a/ee/app/workers/analytics/devops_adoption/create_snapshot_worker.rb b/ee/app/workers/analytics/devops_adoption/create_snapshot_worker.rb
index fc29f67ec066a..21e3b456304f3 100644
--- a/ee/app/workers/analytics/devops_adoption/create_snapshot_worker.rb
+++ b/ee/app/workers/analytics/devops_adoption/create_snapshot_worker.rb
@@ -3,7 +3,6 @@
 module Analytics
   module DevopsAdoption
     # Updates all pending snapshots for given enabled_namespace (from previous month)
-    # and creates or update snapshot for current month
     class CreateSnapshotWorker
       include ApplicationWorker
 
@@ -27,10 +26,9 @@ def perform(enabled_namespace_id)
       def pending_ranges(enabled_namespace)
         end_times = enabled_namespace.snapshots.not_finalized.pluck(:end_time)
 
-        now = Time.zone.now
-
-        if !now.end_of_month.to_date.in?(end_times.map(&:to_date)) && now.day > 1
-          end_times << now.end_of_month
+        prev_month = Time.current.last_month.end_of_month
+        unless prev_month.to_date.in?(end_times.map(&:to_date)) || enabled_namespace.snapshots.for_month(prev_month).exists?
+          end_times << prev_month
         end
 
         end_times
diff --git a/ee/spec/finders/analytics/devops_adoption/snapshots_finder_spec.rb b/ee/spec/finders/analytics/devops_adoption/snapshots_finder_spec.rb
new file mode 100644
index 0000000000000..07407b5cdb246
--- /dev/null
+++ b/ee/spec/finders/analytics/devops_adoption/snapshots_finder_spec.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Analytics::DevopsAdoption::SnapshotsFinder do
+  let_it_be(:enabled_namespace) { create(:devops_adoption_enabled_namespace) }
+  let_it_be(:first_end_time) { 1.year.ago.end_of_month }
+  let_it_be(:snapshot1) { create(:devops_adoption_snapshot, namespace_id: enabled_namespace.namespace_id, end_time: first_end_time) }
+  let_it_be(:snapshot2) do
+    create(:devops_adoption_snapshot, namespace_id: enabled_namespace.namespace_id, end_time: 2.months.after(first_end_time).end_of_month)
+  end
+
+  let_it_be(:snapshot3) do
+    create(:devops_adoption_snapshot, namespace_id: enabled_namespace.namespace_id, end_time: 3.months.after(first_end_time).end_of_month)
+  end
+
+  let(:finder) { described_class.new(params: params) }
+
+  let(:params) { { namespace_id: enabled_namespace.namespace_id } }
+
+  describe '#execute' do
+    subject(:snapshots) { finder.execute }
+
+    context 'with timespan provided' do
+      before do
+        params[:end_time_before] = 1.day.before(snapshot3.end_time)
+        params[:end_time_after] = 1.day.after(first_end_time)
+      end
+
+      it 'returns snapshots in given timespan' do
+        expect(snapshots).to match_array([snapshot2])
+      end
+    end
+
+    context 'without timespan provided' do
+      it 'returns all snapshots ordered by end_time' do
+        expect(snapshots).to eq([snapshot3, snapshot2, snapshot1])
+      end
+    end
+  end
+end
diff --git a/ee/spec/frontend/analytics/devops_report/devops_adoption/components/devops_adoption_section_spec.js b/ee/spec/frontend/analytics/devops_report/devops_adoption/components/devops_adoption_section_spec.js
index 9dfb5aa65b84e..1a36354ffc6d9 100644
--- a/ee/spec/frontend/analytics/devops_report/devops_adoption/components/devops_adoption_section_spec.js
+++ b/ee/spec/frontend/analytics/devops_report/devops_adoption/components/devops_adoption_section_spec.js
@@ -92,7 +92,7 @@ describe('DevopsAdoptionSection', () => {
       createComponent();
 
       const text =
-        'Feature adoption is based on usage in the current calendar month. Last updated: 2020-10-31 23:59.';
+        'Feature adoption is based on usage in the previous calendar month. Last updated: 2020-10-31 23:59.';
       expect(getByText(wrapper.element, text)).not.toBeNull();
     });
 
diff --git a/ee/spec/models/analytics/devops_adoption/snapshot_spec.rb b/ee/spec/models/analytics/devops_adoption/snapshot_spec.rb
index 558c490fc6827..3275020d92d8d 100644
--- a/ee/spec/models/analytics/devops_adoption/snapshot_spec.rb
+++ b/ee/spec/models/analytics/devops_adoption/snapshot_spec.rb
@@ -10,14 +10,14 @@
   it { is_expected.to validate_presence_of(:end_time) }
 
   describe '.latest_snapshot_for_namespace_ids' do
-    it 'returns the latest snapshot for the given namespace ids based on snapshot end_time' do
+    it 'returns the latest finalized snapshot for the given namespace ids based on snapshot end_time' do
       group1 = create(:group)
-      group1_latest_snapshot = create(:devops_adoption_snapshot, namespace: group1, end_time: 1.week.ago)
-      create(:devops_adoption_snapshot, namespace: group1, end_time: 2.weeks.ago)
+      group1_latest_snapshot = create(:devops_adoption_snapshot, namespace: group1, end_time: 1.week.ago, recorded_at: 1.day.ago)
+      create(:devops_adoption_snapshot, namespace: group1, end_time: 2.weeks.ago, recorded_at: 1.day.ago)
 
       group2 = create(:group)
-      group2_latest_snapshot = create(:devops_adoption_snapshot, namespace: group2, end_time: 1.year.ago)
-      create(:devops_adoption_snapshot, namespace: group2, end_time: 2.years.ago)
+      group2_latest_snapshot = create(:devops_adoption_snapshot, namespace: group2, end_time: 1.year.ago, recorded_at: 1.day.ago)
+      create(:devops_adoption_snapshot, namespace: group2, end_time: 2.years.ago, recorded_at: 1.day.ago)
 
       latest_snapshots = described_class.latest_snapshot_for_namespace_ids([group1.id, group2.id])
 
@@ -46,6 +46,38 @@
     end
   end
 
+  describe '.finalized' do
+    it 'returns all snapshots which were recorded later than snapshot end_time' do
+      create(:devops_adoption_snapshot, recorded_at: 1.day.ago, end_time: Time.zone.now)
+      snapshot1 = create(:devops_adoption_snapshot, recorded_at: 1.day.ago, end_time: 2.days.ago)
+
+      expect(described_class.finalized).to match_array([snapshot1])
+    end
+  end
+
+  describe '.for_timespan' do
+    let_it_be(:first_date) { DateTime.parse('2021-05-10').end_of_month }
+    let_it_be(:snapshot1) { create(:devops_adoption_snapshot, recorded_at: 1.day.ago, end_time: first_date)}
+    let_it_be(:snapshot2) { create(:devops_adoption_snapshot, recorded_at: 1.day.ago, end_time: first_date + 1.month)}
+    let_it_be(:snapshot3) { create(:devops_adoption_snapshot, recorded_at: 1.day.ago, end_time: first_date + 2.months)}
+
+    it 'returns snapshots for given timespan', :aggregate_failures do
+      expect(described_class.for_timespan(to: first_date + 1.week)).to match_array([snapshot1])
+      expect(described_class.for_timespan(from: first_date + 1.week)).to match_array([snapshot2, snapshot3])
+      expect(described_class.for_timespan(from: first_date + 1.week, to: first_date + 40.days)).to match_array([snapshot2])
+    end
+  end
+
+  describe '.for_namespaces' do
+    it 'returns all snapshots with given namespaces' do
+      snapshot1 = create(:devops_adoption_snapshot)
+      snapshot2 = create(:devops_adoption_snapshot)
+      create(:devops_adoption_snapshot)
+
+      expect(described_class.for_namespaces([snapshot1.namespace, snapshot2.namespace])).to match_array([snapshot1, snapshot2])
+    end
+  end
+
   describe '#start_time' do
     subject(:snapshot) { described_class.new(end_time: end_time) }
 
diff --git a/ee/spec/requests/api/graphql/analytics/devops_adoption/enabled_namespaces_spec.rb b/ee/spec/requests/api/graphql/analytics/devops_adoption/enabled_namespaces_spec.rb
index d244316f9cff3..58ef4ed806762 100644
--- a/ee/spec/requests/api/graphql/analytics/devops_adoption/enabled_namespaces_spec.rb
+++ b/ee/spec/requests/api/graphql/analytics/devops_adoption/enabled_namespaces_spec.rb
@@ -26,6 +26,12 @@
         displayNamespace {
           name
         }
+        snapshots {
+          nodes {
+            issueOpened
+            mergeRequestOpened
+          }
+        }
         latestSnapshot {
           issueOpened
           mergeRequestOpened
@@ -46,6 +52,10 @@
         'id' => enabled_namespace.to_gid.to_s,
         'namespace' => { 'name' => group.name },
         'displayNamespace' => { 'name' => group.name },
+        'snapshots' => { 'nodes' => [{
+          'mergeRequestOpened' => false,
+          'issueOpened' => true
+        }] },
         'latestSnapshot' => {
           'mergeRequestOpened' => false,
           'issueOpened' => true
diff --git a/ee/spec/workers/analytics/devops_adoption/create_snapshot_worker_spec.rb b/ee/spec/workers/analytics/devops_adoption/create_snapshot_worker_spec.rb
index 1e29b01de38ac..3a8404e62d5e9 100644
--- a/ee/spec/workers/analytics/devops_adoption/create_snapshot_worker_spec.rb
+++ b/ee/spec/workers/analytics/devops_adoption/create_snapshot_worker_spec.rb
@@ -24,7 +24,7 @@
 
     let(:service_mock) { instance_double('Analytics::DevopsAdoption::Snapshots::CalculateAndSaveService', execute: true) }
 
-    it 'updates metrics for all not finalized snapshots and current month' do
+    it 'updates metrics for all not finalized snapshots and previous month' do
       freeze_time do
         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)
@@ -32,7 +32,7 @@
         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, enabled_namespace: enabled_namespace, 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 - 1.month).end_of_month) do |instance|
           expect(instance).to receive(:execute)
         end
 
@@ -40,9 +40,9 @@
       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
+    context 'when pending metric for previous month already exists' do
+      it 'calls for previous month calculation only once' do
+        travel_to(pending_snapshot.recorded_at + 1.month) do
           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
@@ -55,18 +55,15 @@
       end
     end
 
-    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, enabled_namespace: enabled_namespace, range_end: pending_snapshot.end_time) do |instance|
-            expect(instance).to receive(:execute)
+    context 'when metric for previous month already finalized' do
+      it 'does not call for previous month calculation' do
+        travel_to(finalized_snapshot.recorded_at + 1.month) do
+          allow_next_instance_of(Analytics::DevopsAdoption::Snapshots::CalculateAndSaveService) do |instance|
+            allow(instance).to receive(:execute).and_call_original
           end
           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, enabled_namespace: enabled_namespace, range_end: Time.zone.now.end_of_month) do |instance|
-            expect(instance).not_to receive(:execute)
-          end
 
           worker.perform(enabled_namespace.id)
         end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 1cdedf752fce0..9d593acfa2630 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -11297,7 +11297,7 @@ msgstr ""
 msgid "DevopsAdoption|DevOps adoption tracks the use of key features across your favorite groups. Add a group to the table to begin."
 msgstr ""
 
-msgid "DevopsAdoption|Feature adoption is based on usage in the current calendar month. Last updated: %{timestamp}."
+msgid "DevopsAdoption|Feature adoption is based on usage in the previous calendar month. Last updated: %{timestamp}."
 msgstr ""
 
 msgid "DevopsAdoption|Filter by name"
-- 
GitLab