From a016ebc4f7dfe4651d59bc39e7a8a77cf2dad30c Mon Sep 17 00:00:00 2001
From: Hinam Mehra <hmehra@gitlab.com>
Date: Mon, 4 Sep 2023 07:38:57 +0000
Subject: [PATCH] Display activity for similar open abuse reports when
 aggregated

- When an abuse report is aggregated, in it's detail view,
display the activity for the aggregated reports as well

Changelog: added
---
 .../components/abuse_report_app.vue           | 24 ++++++--
 .../components/activity_events_list.vue       | 19 ++++++
 .../components/activity_history_item.vue      | 42 +++++++++++++
 .../abuse_report/components/history_items.vue | 49 ---------------
 .../components/abuse_report_app_spec.js       | 59 +++++++++++++------
 .../components/activity_events_list_spec.js   | 30 ++++++++++
 ..._spec.js => activity_history_item_spec.js} | 13 ++--
 7 files changed, 158 insertions(+), 78 deletions(-)
 create mode 100644 app/assets/javascripts/admin/abuse_report/components/activity_events_list.vue
 create mode 100644 app/assets/javascripts/admin/abuse_report/components/activity_history_item.vue
 delete mode 100644 app/assets/javascripts/admin/abuse_report/components/history_items.vue
 create mode 100644 spec/frontend/admin/abuse_report/components/activity_events_list_spec.js
 rename spec/frontend/admin/abuse_report/components/{history_items_spec.js => activity_history_item_spec.js} (79%)

diff --git a/app/assets/javascripts/admin/abuse_report/components/abuse_report_app.vue b/app/assets/javascripts/admin/abuse_report/components/abuse_report_app.vue
index 3aa1289261d03..3c46de7c2be22 100644
--- a/app/assets/javascripts/admin/abuse_report/components/abuse_report_app.vue
+++ b/app/assets/javascripts/admin/abuse_report/components/abuse_report_app.vue
@@ -5,7 +5,8 @@ import ReportHeader from './report_header.vue';
 import UserDetails from './user_details.vue';
 import ReportDetails from './report_details.vue';
 import ReportedContent from './reported_content.vue';
-import HistoryItems from './history_items.vue';
+import ActivityEventsList from './activity_events_list.vue';
+import ActivityHistoryItem from './activity_history_item.vue';
 
 const alertDefaults = {
   visible: false,
@@ -21,7 +22,8 @@ export default {
     UserDetails,
     ReportDetails,
     ReportedContent,
-    HistoryItems,
+    ActivityEventsList,
+    ActivityHistoryItem,
   },
   mixins: [glFeatureFlagsMixin()],
   props: {
@@ -75,10 +77,24 @@ export default {
 
     <reported-content :report="abuseReport.report" data-testid="reported-content" />
 
-    <div v-for="report in similarOpenReports" :key="report.id" data-testid="similar-open-reports">
+    <div
+      v-for="report in similarOpenReports"
+      :key="report.id"
+      data-testid="reported-content-similar-open-reports"
+    >
       <reported-content :report="report" />
     </div>
 
-    <history-items :report="abuseReport.report" />
+    <activity-events-list>
+      <template #history-items>
+        <activity-history-item :report="abuseReport.report" data-testid="activity" />
+        <activity-history-item
+          v-for="report in similarOpenReports"
+          :key="report.id"
+          :report="report"
+          data-testid="activity-similar-open-reports"
+        />
+      </template>
+    </activity-events-list>
   </section>
 </template>
diff --git a/app/assets/javascripts/admin/abuse_report/components/activity_events_list.vue b/app/assets/javascripts/admin/abuse_report/components/activity_events_list.vue
new file mode 100644
index 0000000000000..8c4c1da28b845
--- /dev/null
+++ b/app/assets/javascripts/admin/abuse_report/components/activity_events_list.vue
@@ -0,0 +1,19 @@
+<script>
+import { HISTORY_ITEMS_I18N } from '../constants';
+
+export default {
+  name: 'ActivityEventsList',
+  i18n: HISTORY_ITEMS_I18N,
+};
+</script>
+
+<template>
+  <!-- The styles `issuable-discussion`, `timeline`, `main-notes-list` and `notes` used below
+       are declared in app/assets/stylesheets/pages/notes.scss -->
+  <section class="gl-pt-6 issuable-discussion">
+    <h2 class="gl-font-lg gl-mt-0 gl-mb-2">{{ $options.i18n.activity }}</h2>
+    <ul class="timeline main-notes-list notes">
+      <slot name="history-items"></slot>
+    </ul>
+  </section>
+</template>
diff --git a/app/assets/javascripts/admin/abuse_report/components/activity_history_item.vue b/app/assets/javascripts/admin/abuse_report/components/activity_history_item.vue
new file mode 100644
index 0000000000000..5962203c382ae
--- /dev/null
+++ b/app/assets/javascripts/admin/abuse_report/components/activity_history_item.vue
@@ -0,0 +1,42 @@
+<script>
+import { GlSprintf } from '@gitlab/ui';
+import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+import HistoryItem from '~/vue_shared/components/registry/history_item.vue';
+import { HISTORY_ITEMS_I18N } from '../constants';
+
+export default {
+  name: 'ActivityHistoryItem',
+  components: {
+    GlSprintf,
+    TimeAgoTooltip,
+    HistoryItem,
+  },
+  props: {
+    report: {
+      type: Object,
+      required: true,
+    },
+  },
+  computed: {
+    reporter() {
+      return this.report.reporter;
+    },
+    reporterName() {
+      return this.reporter?.name || this.$options.i18n.deletedReporter;
+    },
+  },
+  i18n: HISTORY_ITEMS_I18N,
+};
+</script>
+
+<template>
+  <history-item icon="warning">
+    <div class="gl-display-flex gl-xs-flex-direction-column">
+      <gl-sprintf :message="$options.i18n.reportedByForCategory">
+        <template #name>{{ reporterName }}</template>
+        <template #category>{{ report.category }}</template>
+      </gl-sprintf>
+      <time-ago-tooltip :time="report.reportedAt" class="gl-text-secondary gl-sm-ml-3" />
+    </div>
+  </history-item>
+</template>
diff --git a/app/assets/javascripts/admin/abuse_report/components/history_items.vue b/app/assets/javascripts/admin/abuse_report/components/history_items.vue
deleted file mode 100644
index 619a8bcfe9281..0000000000000
--- a/app/assets/javascripts/admin/abuse_report/components/history_items.vue
+++ /dev/null
@@ -1,49 +0,0 @@
-<script>
-import { GlSprintf } from '@gitlab/ui';
-import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
-import HistoryItem from '~/vue_shared/components/registry/history_item.vue';
-import { HISTORY_ITEMS_I18N } from '../constants';
-
-export default {
-  name: 'HistoryItems',
-  components: {
-    GlSprintf,
-    TimeAgoTooltip,
-    HistoryItem,
-  },
-  props: {
-    report: {
-      type: Object,
-      required: true,
-    },
-  },
-  computed: {
-    reporter() {
-      return this.report.reporter;
-    },
-    reporterName() {
-      return this.reporter?.name || this.$options.i18n.deletedReporter;
-    },
-  },
-  i18n: HISTORY_ITEMS_I18N,
-};
-</script>
-
-<template>
-  <!-- The styles `issuable-discussion`, `timeline`, `main-notes-list` and `notes` used below
-       are declared in app/assets/stylesheets/pages/notes.scss -->
-  <section class="gl-pt-6 issuable-discussion">
-    <h2 class="gl-font-lg gl-mt-0 gl-mb-2">{{ $options.i18n.activity }}</h2>
-    <ul class="timeline main-notes-list notes">
-      <history-item icon="warning">
-        <div class="gl-display-flex gl-xs-flex-direction-column">
-          <gl-sprintf :message="$options.i18n.reportedByForCategory">
-            <template #name>{{ reporterName }}</template>
-            <template #category>{{ report.category }}</template>
-          </gl-sprintf>
-          <time-ago-tooltip :time="report.reportedAt" class="gl-text-secondary gl-sm-ml-3" />
-        </div>
-      </history-item>
-    </ul>
-  </section>
-</template>
diff --git a/spec/frontend/admin/abuse_report/components/abuse_report_app_spec.js b/spec/frontend/admin/abuse_report/components/abuse_report_app_spec.js
index d704a91b93d57..4340699a7ed32 100644
--- a/spec/frontend/admin/abuse_report/components/abuse_report_app_spec.js
+++ b/spec/frontend/admin/abuse_report/components/abuse_report_app_spec.js
@@ -5,21 +5,33 @@ import ReportHeader from '~/admin/abuse_report/components/report_header.vue';
 import UserDetails from '~/admin/abuse_report/components/user_details.vue';
 import ReportDetails from '~/admin/abuse_report/components/report_details.vue';
 import ReportedContent from '~/admin/abuse_report/components/reported_content.vue';
-import HistoryItems from '~/admin/abuse_report/components/history_items.vue';
+import ActivityEventsList from '~/admin/abuse_report/components/activity_events_list.vue';
+import ActivityHistoryItem from '~/admin/abuse_report/components/activity_history_item.vue';
 import { SUCCESS_ALERT } from '~/admin/abuse_report/constants';
 import { mockAbuseReport } from '../mock_data';
 
 describe('AbuseReportApp', () => {
   let wrapper;
 
+  const { similarOpenReports } = mockAbuseReport.user;
+
   const findAlert = () => wrapper.findComponent(GlAlert);
   const findReportHeader = () => wrapper.findComponent(ReportHeader);
   const findUserDetails = () => wrapper.findComponent(UserDetails);
+
   const findReportedContent = () => wrapper.findByTestId('reported-content');
-  const findSimilarOpenReports = () => wrapper.findAllByTestId('similar-open-reports');
-  const findSimilarReportedContent = () =>
-    findSimilarOpenReports().at(0).findComponent(ReportedContent);
-  const findHistoryItems = () => wrapper.findComponent(HistoryItems);
+  const findReportedContentForSimilarReports = () =>
+    wrapper.findAllByTestId('reported-content-similar-open-reports');
+  const firstReportedContentForSimilarReports = () =>
+    findReportedContentForSimilarReports().at(0).findComponent(ReportedContent);
+
+  const findActivityList = () => wrapper.findComponent(ActivityEventsList);
+  const findActivityItem = () => wrapper.findByTestId('activity');
+  const findActivityForSimilarReports = () =>
+    wrapper.findAllByTestId('activity-similar-open-reports');
+  const firstActivityForSimilarReports = () =>
+    findActivityForSimilarReports().at(0).findComponent(ActivityHistoryItem);
+
   const findReportDetails = () => wrapper.findComponent(ReportDetails);
 
   const createComponent = (props = {}, provide = {}) => {
@@ -70,7 +82,7 @@ describe('AbuseReportApp', () => {
     });
   });
 
-  describe('ReportHeader', () => {
+  describe('Report header', () => {
     it('renders ReportHeader', () => {
       expect(findReportHeader().props('user')).toBe(mockAbuseReport.user);
       expect(findReportHeader().props('report')).toBe(mockAbuseReport.report);
@@ -89,7 +101,7 @@ describe('AbuseReportApp', () => {
     });
   });
 
-  describe('UserDetails', () => {
+  describe('User Details', () => {
     it('renders UserDetails', () => {
       expect(findUserDetails().props('user')).toBe(mockAbuseReport.user);
     });
@@ -107,6 +119,17 @@ describe('AbuseReportApp', () => {
     });
   });
 
+  describe('Reported Content', () => {
+    it('renders ReportedContent', () => {
+      expect(findReportedContent().props('report')).toBe(mockAbuseReport.report);
+    });
+
+    it('renders similar abuse reports', () => {
+      expect(findReportedContentForSimilarReports()).toHaveLength(similarOpenReports.length);
+      expect(firstReportedContentForSimilarReports().props('report')).toBe(similarOpenReports[0]);
+    });
+  });
+
   describe('ReportDetails', () => {
     describe('when abuseReportLabels feature flag is enabled', () => {
       it('renders ReportDetails', () => {
@@ -125,18 +148,18 @@ describe('AbuseReportApp', () => {
     });
   });
 
-  it('renders ReportedContent', () => {
-    expect(findReportedContent().props('report')).toBe(mockAbuseReport.report);
-  });
-
-  it('renders similar abuse reports', () => {
-    const { similarOpenReports } = mockAbuseReport.user;
+  describe('Activity', () => {
+    it('renders the activity events list', () => {
+      expect(findActivityList().exists()).toBe(true);
+    });
 
-    expect(findSimilarOpenReports()).toHaveLength(similarOpenReports.length);
-    expect(findSimilarReportedContent().props('report')).toBe(similarOpenReports[0]);
-  });
+    it('renders activity item for abuse report', () => {
+      expect(findActivityItem().props('report')).toBe(mockAbuseReport.report);
+    });
 
-  it('renders HistoryItems', () => {
-    expect(findHistoryItems().props('report')).toBe(mockAbuseReport.report);
+    it('renders activity items for similar abuse reports', () => {
+      expect(findActivityForSimilarReports()).toHaveLength(similarOpenReports.length);
+      expect(firstActivityForSimilarReports().props('report')).toBe(similarOpenReports[0]);
+    });
   });
 });
diff --git a/spec/frontend/admin/abuse_report/components/activity_events_list_spec.js b/spec/frontend/admin/abuse_report/components/activity_events_list_spec.js
new file mode 100644
index 0000000000000..cd1120d2db40a
--- /dev/null
+++ b/spec/frontend/admin/abuse_report/components/activity_events_list_spec.js
@@ -0,0 +1,30 @@
+import { shallowMount } from '@vue/test-utils';
+import ActivityEventsList from '~/admin/abuse_report/components/activity_events_list.vue';
+
+describe('ActivityEventsList', () => {
+  let wrapper;
+
+  const mockSlotContent = 'Test slot content';
+
+  const findActivityEventsList = () => wrapper.findComponent(ActivityEventsList);
+
+  const createComponent = () => {
+    wrapper = shallowMount(ActivityEventsList, {
+      slots: {
+        'history-items': mockSlotContent,
+      },
+    });
+  };
+
+  beforeEach(() => {
+    createComponent();
+  });
+
+  it('renders activity title', () => {
+    expect(findActivityEventsList().text()).toContain('Activity');
+  });
+
+  it('renders history-items slot', () => {
+    expect(findActivityEventsList().text()).toContain(mockSlotContent);
+  });
+});
diff --git a/spec/frontend/admin/abuse_report/components/history_items_spec.js b/spec/frontend/admin/abuse_report/components/activity_history_item_spec.js
similarity index 79%
rename from spec/frontend/admin/abuse_report/components/history_items_spec.js
rename to spec/frontend/admin/abuse_report/components/activity_history_item_spec.js
index e88888100959e..3f430b0143e10 100644
--- a/spec/frontend/admin/abuse_report/components/history_items_spec.js
+++ b/spec/frontend/admin/abuse_report/components/activity_history_item_spec.js
@@ -1,13 +1,12 @@
 import { GlSprintf } from '@gitlab/ui';
 import { shallowMount } from '@vue/test-utils';
 import { sprintf } from '~/locale';
-import HistoryItems from '~/admin/abuse_report/components/history_items.vue';
+import AcitivityHistoryItem from '~/admin/abuse_report/components/activity_history_item.vue';
 import HistoryItem from '~/vue_shared/components/registry/history_item.vue';
 import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
-import { HISTORY_ITEMS_I18N } from '~/admin/abuse_report/constants';
 import { mockAbuseReport } from '../mock_data';
 
-describe('HistoryItems', () => {
+describe('AcitivityHistoryItem', () => {
   let wrapper;
 
   const { report } = mockAbuseReport;
@@ -16,7 +15,7 @@ describe('HistoryItems', () => {
   const findTimeAgo = () => wrapper.findComponent(TimeAgoTooltip);
 
   const createComponent = (props = {}) => {
-    wrapper = shallowMount(HistoryItems, {
+    wrapper = shallowMount(AcitivityHistoryItem, {
       propsData: {
         report,
         ...props,
@@ -37,7 +36,7 @@ describe('HistoryItems', () => {
 
   describe('rendering the title', () => {
     it('renders the reporters name and the category', () => {
-      const title = sprintf(HISTORY_ITEMS_I18N.reportedByForCategory, {
+      const title = sprintf('Reported by %{name} for %{category}.', {
         name: report.reporter.name,
         category: report.category,
       });
@@ -50,8 +49,8 @@ describe('HistoryItems', () => {
       });
 
       it('renders the `No user found` as the reporters name and the category', () => {
-        const title = sprintf(HISTORY_ITEMS_I18N.reportedByForCategory, {
-          name: HISTORY_ITEMS_I18N.deletedReporter,
+        const title = sprintf('Reported by %{name} for %{category}.', {
+          name: 'No user found',
           category: report.category,
         });
         expect(findHistoryItem().text()).toContain(title);
-- 
GitLab