AIRAVATA-3691 Only show GRP compute resources in statistics filter
diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/statistics/ExperimentStatisticsContainer.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/statistics/ExperimentStatisticsContainer.vue
index f6c2dce..db4628e 100644
--- a/django_airavata/apps/admin/static/django_airavata_admin/src/components/statistics/ExperimentStatisticsContainer.vue
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/statistics/ExperimentStatisticsContainer.vue
@@ -335,6 +335,7 @@
       hostnameFilter: null,
       appInterfaces: null,
       computeResourceNames: null,
+      groupResourceProfiles: null,
       experimentDetailTabs: [],
       experimentId: null,
       jobId: null,
@@ -345,6 +346,7 @@
     this.loadStatistics();
     this.loadApplicationInterfaces();
     this.loadComputeResources();
+    this.loadGroupResourceProfiles();
   },
   components: {
     ExperimentDetailsView,
@@ -454,13 +456,22 @@
       }
     },
     hostnameOptions() {
-      if (this.computeResourceNames) {
-        const options = this.computeResourceNames.map((name) => {
-          return {
-            value: name.host_id,
-            text: name.host,
-          };
-        });
+      if (this.computeResourceNames && this.groupResourceProfiles) {
+        // Only show compute resources that are configured in the Group Resource Profiles
+        // First create a Set of all compute resource ids in the GRPs
+        const groupResourceProfileCompResources = new Set(
+          this.groupResourceProfiles.flatMap((grp) =>
+            grp.computePreferences.map((cp) => cp.computeResourceId)
+          )
+        );
+        const options = this.computeResourceNames
+          .filter((name) => groupResourceProfileCompResources.has(name.host_id))
+          .map((name) => {
+            return {
+              value: name.host_id,
+              text: name.host,
+            };
+          });
         return utils.StringUtils.sortIgnoreCase(options, (o) => o.text);
       } else {
         return [];
@@ -505,6 +516,9 @@
         (names) => (this.computeResourceNames = names)
       );
     },
+    async loadGroupResourceProfiles() {
+      this.groupResourceProfiles = await services.GroupResourceProfileService.list();
+    },
     loadStatistics() {
       const requestData = {
         fromTime: this.fromTime.toJSON(),
diff --git a/django_airavata/apps/admin/static/django_airavata_admin/tests/unit/components/statistics/ExperimentStatisticsContainer.spec.js b/django_airavata/apps/admin/static/django_airavata_admin/tests/unit/components/statistics/ExperimentStatisticsContainer.spec.js
index 5bdba0b..f593f2b 100644
--- a/django_airavata/apps/admin/static/django_airavata_admin/tests/unit/components/statistics/ExperimentStatisticsContainer.spec.js
+++ b/django_airavata/apps/admin/static/django_airavata_admin/tests/unit/components/statistics/ExperimentStatisticsContainer.spec.js
@@ -35,19 +35,25 @@
       FullExperimentService: {
         retrieve: jest.fn(),
       },
+      GroupResourceProfileService: {
+        list: jest.fn(),
+      },
     },
   };
 });
 
-// beforeEach(() => {
-//   jest.resetAllMocks();
-// });
+beforeEach(() => {
+  jest.resetAllMocks();
 
-test("load experiment by job id when job id matches unique experiment", async () => {
   const spinner = document.createElement("div");
   spinner.id = "airavata-spinner";
   document.body.appendChild(spinner);
 
+  // jsdom doesn't implement scrollIntoView so just provide a stubbed implementation
+  Element.prototype.scrollIntoView = jest.fn();
+});
+
+test("load experiment by job id when job id matches unique experiment", async () => {
   // Service call mocks
   services.ApplicationInterfaceService.list.mockResolvedValue([]);
   services.ExperimentStatisticsService.get.mockResolvedValue(
@@ -108,9 +114,6 @@
     })
   );
 
-  // jsdom doesn't implement scrollIntoView so just provide a stubbed implementation
-  Element.prototype.scrollIntoView = jest.fn();
-
   // The render method returns a collection of utilities to query your component.
   const { findByText, findByPlaceholderText } = render(
     ExperimentStatisticsContainer
@@ -148,3 +151,89 @@
     lookup: experiment.experimentId,
   });
 });
+
+test("Hostname filter only shows compute resources that are configured in a GRP", async () => {
+  // Service call mocks
+  services.ApplicationInterfaceService.list.mockResolvedValue([]);
+  services.ExperimentStatisticsService.get.mockResolvedValue(
+    new utils.PaginationIterator(
+      {
+        count: 0,
+        next: null,
+        previous: null,
+        results: {
+          allExperimentCount: 0,
+          completedExperimentCount: 0,
+          cancelledExperimentCount: 0,
+          failedExperimentCount: 0,
+          createdExperimentCount: 0,
+          runningExperimentCount: 0,
+          allExperiments: [],
+          completedExperiments: [],
+          failedExperiments: [],
+          cancelledExperiments: [],
+          createdExperiments: [],
+          runningExperiments: [],
+        },
+        limit: 50,
+        offset: 0,
+      },
+      models.ExperimentStatistics
+    )
+  );
+  services.ComputeResourceService.namesList.mockResolvedValue([
+    { host_id: "compute4-abcd", host: "d-compute4" },
+    { host_id: "compute2-abcd", host: "b-compute2" },
+    { host_id: "compute5-abcd", host: "e-compute5" },
+    { host_id: "compute3-abcd", host: "c-compute3" },
+    { host_id: "compute1-abcd", host: "a-compute1" },
+  ]);
+
+  services.GroupResourceProfileService.list.mockResolvedValue([
+    new models.GroupResourceProfile({
+      computePreferences: [
+        new models.GroupComputeResourcePreference({
+          computeResourceId: "compute1-abcd",
+        }),
+        new models.GroupComputeResourcePreference({
+          computeResourceId: "compute3-abcd",
+        }),
+      ],
+    }),
+    new models.GroupResourceProfile({
+      computePreferences: [
+        new models.GroupComputeResourcePreference({
+          computeResourceId: "compute1-abcd",
+        }),
+        new models.GroupComputeResourcePreference({
+          computeResourceId: "compute4-abcd",
+        }),
+      ],
+    }),
+  ]);
+
+  // The render method returns a collection of utilities to query your component.
+  const { findByText } = render(ExperimentStatisticsContainer);
+
+  const addFiltersMenu = await findByText("Add Filters");
+
+  await fireEvent.click(addFiltersMenu);
+
+  const hostnameMenuItem = await findByText("Hostname");
+
+  await fireEvent.click(hostnameMenuItem);
+
+  const computeResourcesSelect = await findByText(
+    "Select compute resource to filter on"
+  );
+
+  const options = computeResourcesSelect.parentElement.options;
+
+  expect(options.length).toBe(4);
+  // option 0 is the null one ("Select compute resource to filter on")
+  // verify that options 1-3 are compute resources 1, 3, 4. That is, verify that
+  // filtering worked and that they were sorted.
+  expect(options[1].value).toBe("compute1-abcd");
+  expect(options[2].value).toBe("compute3-abcd");
+  expect(options[3].value).toBe("compute4-abcd");
+});