From e3bef4a74a40d59af2a405f12fbe1b594e564a8b Mon Sep 17 00:00:00 2001
From: Anna Vovchenko <avovchenko@gitlab.com>
Date: Fri, 31 May 2024 11:29:24 +0000
Subject: [PATCH] Show Kustomization resource in Tree view

Update Kustomization and HelmRelease queries and resolvers;
Move queries to a parent component;
Provide Kustomization data to the Tree view.
---
 .../kubernetes/kubernetes_overview.vue        |  53 ++++-
 .../kubernetes/kubernetes_status_bar.vue      |  78 ++------
 .../kubernetes/kubernetes_summary.vue         |  18 +-
 .../components/kubernetes/kubernetes_tabs.vue |   7 +-
 .../environments/graphql/client.js            |  28 ++-
 .../flux_helm_release_status.query.graphql    |  10 +-
 .../queries/flux_kustomization.query.graphql  |  14 ++
 .../flux_kustomization_status.query.graphql   |  12 --
 .../environments/graphql/resolvers/flux.js    |  39 ++--
 .../environments/graphql/typedefs.graphql     |  23 ++-
 .../kubernetes/kubernetes_overview_spec.js    | 185 +++++++++++++++--
 .../kubernetes/kubernetes_status_bar_spec.js  | 187 +++---------------
 .../kubernetes/kubernetes_summary_spec.js     |  10 +-
 .../kubernetes/kubernetes_tabs_spec.js        |  20 +-
 .../environments/graphql/mock_data.js         |  20 +-
 .../graphql/resolvers/flux_spec.js            |  30 +--
 spec/frontend/environments/mock_data.js       |   9 +
 17 files changed, 436 insertions(+), 307 deletions(-)
 create mode 100644 app/assets/javascripts/environments/graphql/queries/flux_kustomization.query.graphql
 delete mode 100644 app/assets/javascripts/environments/graphql/queries/flux_kustomization_status.query.graphql

diff --git a/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_overview.vue b/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_overview.vue
index a3ba24e329b3f..79469c931d1b4 100644
--- a/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_overview.vue
+++ b/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_overview.vue
@@ -7,7 +7,14 @@ import { helpPagePath } from '~/helpers/help_page_helper';
 import { getIdFromGraphQLId } from '~/graphql_shared/utils';
 import { k8sResourceType } from '~/environments/graphql/resolvers/kubernetes/constants';
 import { createK8sAccessConfiguration } from '~/environments/helpers/k8s_integration_helper';
-import { CLUSTER_HEALTH_SUCCESS, CLUSTER_HEALTH_ERROR } from '~/environments/constants';
+import fluxKustomizationQuery from '~/environments/graphql/queries/flux_kustomization.query.graphql';
+import fluxHelmReleaseQueryStatus from '~/environments/graphql/queries/flux_helm_release_status.query.graphql';
+import {
+  CLUSTER_HEALTH_SUCCESS,
+  CLUSTER_HEALTH_ERROR,
+  HELM_RELEASES_RESOURCE_TYPE,
+  KUSTOMIZATIONS_RESOURCE_TYPE,
+} from '~/environments/constants';
 import KubernetesStatusBar from './kubernetes_status_bar.vue';
 import KubernetesAgentInfo from './kubernetes_agent_info.vue';
 import KubernetesTabs from './kubernetes_tabs.vue';
@@ -44,13 +51,49 @@ export default {
       default: '',
     },
   },
-
+  apollo: {
+    fluxKustomization: {
+      query: fluxKustomizationQuery,
+      variables() {
+        return {
+          configuration: this.k8sAccessConfiguration,
+          fluxResourcePath: this.fluxResourcePath,
+        };
+      },
+      skip() {
+        return Boolean(
+          !this.fluxResourcePath || this.fluxResourcePath?.includes(HELM_RELEASES_RESOURCE_TYPE),
+        );
+      },
+      error(err) {
+        this.fluxApiError = err.message;
+      },
+    },
+    fluxHelmReleaseStatus: {
+      query: fluxHelmReleaseQueryStatus,
+      variables() {
+        return {
+          configuration: this.k8sAccessConfiguration,
+          fluxResourcePath: this.fluxResourcePath,
+        };
+      },
+      skip() {
+        return Boolean(
+          !this.fluxResourcePath || this.fluxResourcePath?.includes(KUSTOMIZATIONS_RESOURCE_TYPE),
+        );
+      },
+      error(err) {
+        this.fluxApiError = err.message;
+      },
+    },
+  },
   data() {
     return {
       error: null,
       failedState: {},
       podsLoading: false,
       activeTab: k8sResourceType.k8sPods,
+      fluxApiError: '',
     };
   },
   computed: {
@@ -72,6 +115,9 @@ export default {
     hasFailedState() {
       return Object.values(this.failedState).some((item) => item);
     },
+    fluxResourceStatus() {
+      return this.fluxKustomization?.conditions || this.fluxHelmReleaseStatus?.conditions;
+    },
   },
   methods: {
     handleError(message) {
@@ -114,6 +160,8 @@ export default {
         :environment-name="environmentName"
         :flux-resource-path="fluxResourcePath"
         :resource-type="activeTab"
+        :flux-resource-status="fluxResourceStatus"
+        :flux-api-error="fluxApiError"
         @error="handleError"
       />
     </div>
@@ -126,6 +174,7 @@ export default {
       v-model="activeTab"
       :configuration="k8sAccessConfiguration"
       :namespace="kubernetesNamespace"
+      :flux-kustomization="fluxKustomization"
       class="gl-mb-5"
       @cluster-error="handleError"
       @loading="podsLoading = $event"
diff --git a/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_status_bar.vue b/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_status_bar.vue
index cca5f0c8b9d3b..f2c1d0b2bdb41 100644
--- a/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_status_bar.vue
+++ b/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_status_bar.vue
@@ -14,8 +14,6 @@ import {
   HELM_RELEASES_RESOURCE_TYPE,
   KUSTOMIZATIONS_RESOURCE_TYPE,
 } from '~/environments/constants';
-import fluxKustomizationStatusQuery from '~/environments/graphql/queries/flux_kustomization_status.query.graphql';
-import fluxHelmReleaseStatusQuery from '~/environments/graphql/queries/flux_helm_release_status.query.graphql';
 import KubernetesConnectionStatus from '~/environments/environment_details/components/kubernetes/kubernetes_connection_status.vue';
 import KubernetesConnectionStatusBadge from '~/environments/environment_details/components/kubernetes/kubernetes_connection_status_badge.vue';
 import {
@@ -64,46 +62,19 @@ export default {
       type: String,
       required: true,
     },
-  },
-  apollo: {
-    fluxKustomizationStatus: {
-      query: fluxKustomizationStatusQuery,
-      variables() {
-        return {
-          configuration: this.configuration,
-          fluxResourcePath: this.fluxResourcePath,
-        };
-      },
-      skip() {
-        return Boolean(
-          !this.fluxResourcePath || this.fluxResourcePath?.includes(HELM_RELEASES_RESOURCE_TYPE),
-        );
-      },
-      error(err) {
-        this.fluxApiError = err.message;
-      },
+    fluxResourceStatus: {
+      type: Array,
+      required: false,
+      default: () => [],
     },
-    fluxHelmReleaseStatus: {
-      query: fluxHelmReleaseStatusQuery,
-      variables() {
-        return {
-          configuration: this.configuration,
-          fluxResourcePath: this.fluxResourcePath,
-        };
-      },
-      skip() {
-        return Boolean(
-          !this.fluxResourcePath || this.fluxResourcePath?.includes(KUSTOMIZATIONS_RESOURCE_TYPE),
-        );
-      },
-      error(err) {
-        this.fluxApiError = err.message;
-      },
+    fluxApiError: {
+      type: String,
+      required: false,
+      default: '',
     },
   },
   data() {
     return {
-      fluxApiError: '',
       clusterResourceTypeParams: {
         [k8sResourceType.k8sServices]: {
           resourceType: k8sResourceType.k8sServices,
@@ -157,35 +128,16 @@ export default {
     healthBadge() {
       return HEALTH_BADGES[this.clusterHealthStatus];
     },
-    hasKustomizations() {
-      return this.fluxKustomizationStatus?.length;
-    },
-    hasHelmReleases() {
-      return this.fluxHelmReleaseStatus?.length;
-    },
-    isLoading() {
-      return (
-        this.$apollo.queries.fluxKustomizationStatus.loading ||
-        this.$apollo.queries.fluxHelmReleaseStatus.loading
-      );
-    },
     fluxBadgeId() {
       return `${this.environmentName}-flux-sync-badge`;
     },
-    fluxCRD() {
-      if (!this.hasKustomizations && !this.hasHelmReleases) {
-        return [];
-      }
-
-      return this.hasKustomizations ? this.fluxKustomizationStatus : this.fluxHelmReleaseStatus;
-    },
     fluxAnyStalled() {
-      return this.fluxCRD.find((condition) => {
+      return this.fluxResourceStatus.find((condition) => {
         return condition.status === STATUS_TRUE && condition.type === 'Stalled';
       });
     },
     fluxAnyReconcilingWithBadConfig() {
-      return this.fluxCRD.find((condition) => {
+      return this.fluxResourceStatus.find((condition) => {
         return (
           condition.status === STATUS_UNKNOWN &&
           condition.type === 'Ready' &&
@@ -194,25 +146,25 @@ export default {
       });
     },
     fluxAnyReconciling() {
-      return this.fluxCRD.find((condition) => {
+      return this.fluxResourceStatus.find((condition) => {
         return condition.status === STATUS_TRUE && condition.type === 'Reconciling';
       });
     },
     fluxAnyReconciled() {
-      return this.fluxCRD.find((condition) => {
+      return this.fluxResourceStatus.find((condition) => {
         return condition.status === STATUS_TRUE && condition.type === 'Ready';
       });
     },
     fluxAnyFailed() {
-      return this.fluxCRD.find((condition) => {
+      return this.fluxResourceStatus.find((condition) => {
         return condition.status === STATUS_FALSE && condition.type === 'Ready';
       });
     },
     syncStatusBadge() {
-      if (!this.fluxCRD.length && this.fluxApiError) {
+      if (!this.fluxResourceStatus.length && this.fluxApiError) {
         return { ...SYNC_STATUS_BADGES.unavailable, popoverText: this.fluxApiError };
       }
-      if (!this.fluxCRD.length) {
+      if (!this.fluxResourceStatus.length) {
         return SYNC_STATUS_BADGES.unavailable;
       }
       if (this.fluxAnyFailed) {
diff --git a/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_summary.vue b/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_summary.vue
index b5c3f5d860aec..188656afe3ca3 100644
--- a/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_summary.vue
+++ b/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_summary.vue
@@ -1,4 +1,5 @@
 <script>
+import { isEmpty } from 'lodash';
 import { GlTab } from '@gitlab/ui';
 import { s__ } from '~/locale';
 
@@ -10,10 +11,25 @@ export default {
     summaryTitle: s__('Environment|Summary'),
     treeView: s__('Environment|Tree view'),
   },
+  props: {
+    fluxKustomization: {
+      required: true,
+      type: Object,
+    },
+  },
+  computed: {
+    hasFluxKustomization() {
+      return !isEmpty(this.fluxKustomization);
+    },
+  },
 };
 </script>
 <template>
   <gl-tab :title="$options.i18n.summaryTitle">
     <p class="gl-mt-3 gl-text-secondary">{{ $options.i18n.treeView }}</p>
-  </gl-tab>
+
+    <p v-if="hasFluxKustomization">
+      {{ fluxKustomization.kind }}: {{ fluxKustomization.metadata.name }}
+    </p></gl-tab
+  >
 </template>
diff --git a/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_tabs.vue b/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_tabs.vue
index fee8ab5737297..b9c0064a44dbf 100644
--- a/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_tabs.vue
+++ b/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_tabs.vue
@@ -34,6 +34,11 @@ export default {
       required: true,
       type: String,
     },
+    fluxKustomization: {
+      required: false,
+      type: Object,
+      default: () => ({}),
+    },
   },
   data() {
     return {
@@ -70,7 +75,7 @@ export default {
 <template>
   <div>
     <gl-tabs v-model="activeTabIndex">
-      <kubernetes-summary v-if="renderTreeView" />
+      <kubernetes-summary v-if="renderTreeView" :flux-kustomization="fluxKustomization" />
 
       <kubernetes-pods
         :namespace="namespace"
diff --git a/app/assets/javascripts/environments/graphql/client.js b/app/assets/javascripts/environments/graphql/client.js
index 0f47cbff15988..524018d3d5a9a 100644
--- a/app/assets/javascripts/environments/graphql/client.js
+++ b/app/assets/javascripts/environments/graphql/client.js
@@ -9,7 +9,7 @@ import k8sPodsQuery from './queries/k8s_pods.query.graphql';
 import k8sConnectionStatusQuery from './queries/k8s_connection_status.query.graphql';
 import k8sServicesQuery from './queries/k8s_services.query.graphql';
 import k8sNamespacesQuery from './queries/k8s_namespaces.query.graphql';
-import fluxKustomizationStatusQuery from './queries/flux_kustomization_status.query.graphql';
+import fluxKustomizationQuery from './queries/flux_kustomization.query.graphql';
 import fluxHelmReleaseStatusQuery from './queries/flux_helm_release_status.query.graphql';
 import { resolvers } from './resolvers';
 import typeDefs from './typedefs.graphql';
@@ -128,21 +128,29 @@ export const apolloProvider = (endpoint) => {
     },
   });
   cache.writeQuery({
-    query: fluxKustomizationStatusQuery,
+    query: fluxKustomizationQuery,
     data: {
-      message: '',
-      reason: '',
-      status: '',
-      type: '',
+      kind: '',
+      metadata: {
+        name: '',
+      },
+      conditions: {
+        message: '',
+        reason: '',
+        status: '',
+        type: '',
+      },
     },
   });
   cache.writeQuery({
     query: fluxHelmReleaseStatusQuery,
     data: {
-      message: '',
-      reason: '',
-      status: '',
-      type: '',
+      conditions: {
+        message: '',
+        reason: '',
+        status: '',
+        type: '',
+      },
     },
   });
 
diff --git a/app/assets/javascripts/environments/graphql/queries/flux_helm_release_status.query.graphql b/app/assets/javascripts/environments/graphql/queries/flux_helm_release_status.query.graphql
index 35f7fe56b479b..6cac95a75366a 100644
--- a/app/assets/javascripts/environments/graphql/queries/flux_helm_release_status.query.graphql
+++ b/app/assets/javascripts/environments/graphql/queries/flux_helm_release_status.query.graphql
@@ -1,9 +1,11 @@
 query getFluxHelmReleaseStatusQuery($configuration: LocalConfiguration, $fluxResourcePath: String) {
   fluxHelmReleaseStatus(configuration: $configuration, fluxResourcePath: $fluxResourcePath)
     @client {
-    message
-    reason
-    status
-    type
+    conditions {
+      message
+      reason
+      status
+      type
+    }
   }
 }
diff --git a/app/assets/javascripts/environments/graphql/queries/flux_kustomization.query.graphql b/app/assets/javascripts/environments/graphql/queries/flux_kustomization.query.graphql
new file mode 100644
index 0000000000000..ea992540d2ce4
--- /dev/null
+++ b/app/assets/javascripts/environments/graphql/queries/flux_kustomization.query.graphql
@@ -0,0 +1,14 @@
+query getFluxKustomizationQuery($configuration: LocalConfiguration, $fluxResourcePath: String) {
+  fluxKustomization(configuration: $configuration, fluxResourcePath: $fluxResourcePath) @client {
+    kind
+    metadata {
+      name
+    }
+    conditions {
+      message
+      reason
+      status
+      type
+    }
+  }
+}
diff --git a/app/assets/javascripts/environments/graphql/queries/flux_kustomization_status.query.graphql b/app/assets/javascripts/environments/graphql/queries/flux_kustomization_status.query.graphql
deleted file mode 100644
index 8564b306d5b22..0000000000000
--- a/app/assets/javascripts/environments/graphql/queries/flux_kustomization_status.query.graphql
+++ /dev/null
@@ -1,12 +0,0 @@
-query getFluxHelmKustomizationStatusQuery(
-  $configuration: LocalConfiguration
-  $fluxResourcePath: String
-) {
-  fluxKustomizationStatus(configuration: $configuration, fluxResourcePath: $fluxResourcePath)
-    @client {
-    message
-    reason
-    status
-    type
-  }
-}
diff --git a/app/assets/javascripts/environments/graphql/resolvers/flux.js b/app/assets/javascripts/environments/graphql/resolvers/flux.js
index 60b24d47472b2..8bb6e88f80438 100644
--- a/app/assets/javascripts/environments/graphql/resolvers/flux.js
+++ b/app/assets/javascripts/environments/graphql/resolvers/flux.js
@@ -6,14 +6,14 @@ import {
 } from '~/environments/constants';
 import { updateConnectionStatus } from '~/environments/graphql/resolvers/kubernetes/k8s_connection_status';
 import { connectionStatus } from '~/environments/graphql/resolvers/kubernetes/constants';
-import fluxKustomizationStatusQuery from '../queries/flux_kustomization_status.query.graphql';
+import fluxKustomizationQuery from '../queries/flux_kustomization.query.graphql';
 import fluxHelmReleaseStatusQuery from '../queries/flux_helm_release_status.query.graphql';
 
 const helmReleasesApiVersion = 'helm.toolkit.fluxcd.io/v2beta1';
 const kustomizationsApiVersion = 'kustomize.toolkit.fluxcd.io/v1';
 
 const helmReleaseField = 'fluxHelmReleaseStatus';
-const kustomizationField = 'fluxKustomizationStatus';
+const kustomizationField = 'fluxKustomization';
 
 const handleClusterError = (err) => {
   const error = err?.response?.data?.message ? new Error(err.response.data.message) : err;
@@ -28,6 +28,21 @@ export const buildFluxResourceWatchPath = ({ namespace, apiVersion, resourceType
   return `/apis/${apiVersion}/namespaces/${namespace}/${resourceType}`;
 };
 
+const mapFluxItems = (fluxItem, resourceType) => {
+  if (resourceType === KUSTOMIZATIONS_RESOURCE_TYPE) {
+    return {
+      kind: fluxItem?.kind || '',
+      metadata: {
+        name: fluxItem?.metadata?.name || '',
+      },
+      conditions: fluxItem?.status?.conditions || [],
+    };
+  }
+  return {
+    conditions: fluxItem?.status?.conditions || [],
+  };
+};
+
 const watchFluxResource = ({
   watchPath,
   resourceName,
@@ -55,7 +70,7 @@ const watchFluxResource = ({
       let result = [];
 
       watcher.on(EVENT_DATA, (data) => {
-        result = data[0]?.status?.conditions;
+        result = mapFluxItems(data[0], resourceType);
 
         client.writeQuery({
           query,
@@ -85,7 +100,7 @@ const watchFluxResource = ({
     });
 };
 
-const getFluxResourceStatus = ({ query, variables, field, resourceType, client }) => {
+const getFluxResource = ({ query, variables, field, resourceType, client }) => {
   const { headers } = variables.configuration;
   const withCredentials = true;
   const url = `${variables.configuration.basePath}/apis/${variables.fluxResourcePath}`;
@@ -113,7 +128,7 @@ const getFluxResourceStatus = ({ query, variables, field, resourceType, client }
         });
       }
 
-      return fluxData?.status?.conditions || [];
+      return mapFluxItems(fluxData, resourceType);
     })
     .catch((err) => {
       handleClusterError(err);
@@ -121,12 +136,12 @@ const getFluxResourceStatus = ({ query, variables, field, resourceType, client }
 };
 
 export const watchFluxKustomization = ({ configuration, client, fluxResourcePath }) => {
-  const query = fluxKustomizationStatusQuery;
+  const query = fluxKustomizationQuery;
   const variables = { configuration, fluxResourcePath };
   const field = kustomizationField;
   const resourceType = KUSTOMIZATIONS_RESOURCE_TYPE;
 
-  getFluxResourceStatus({ query, variables, field, resourceType, client });
+  getFluxResource({ query, variables, field, resourceType, client });
 };
 
 export const watchFluxHelmRelease = ({ configuration, client, fluxResourcePath }) => {
@@ -135,7 +150,7 @@ export const watchFluxHelmRelease = ({ configuration, client, fluxResourcePath }
   const field = helmReleaseField;
   const resourceType = HELM_RELEASES_RESOURCE_TYPE;
 
-  getFluxResourceStatus({ query, variables, field, resourceType, client });
+  getFluxResource({ query, variables, field, resourceType, client });
 };
 
 const getFluxResources = (configuration, url) => {
@@ -164,9 +179,9 @@ const getFluxResources = (configuration, url) => {
 };
 
 export default {
-  fluxKustomizationStatus(_, { configuration, fluxResourcePath }, { client }) {
-    return getFluxResourceStatus({
-      query: fluxKustomizationStatusQuery,
+  fluxKustomization(_, { configuration, fluxResourcePath }, { client }) {
+    return getFluxResource({
+      query: fluxKustomizationQuery,
       variables: { configuration, fluxResourcePath },
       field: kustomizationField,
       resourceType: KUSTOMIZATIONS_RESOURCE_TYPE,
@@ -174,7 +189,7 @@ export default {
     });
   },
   fluxHelmReleaseStatus(_, { configuration, fluxResourcePath }, { client }) {
-    return getFluxResourceStatus({
+    return getFluxResource({
       query: fluxHelmReleaseStatusQuery,
       variables: { configuration, fluxResourcePath },
       field: helmReleaseField,
diff --git a/app/assets/javascripts/environments/graphql/typedefs.graphql b/app/assets/javascripts/environments/graphql/typedefs.graphql
index 6cece654fdab1..c651c94cb054d 100644
--- a/app/assets/javascripts/environments/graphql/typedefs.graphql
+++ b/app/assets/javascripts/environments/graphql/typedefs.graphql
@@ -70,11 +70,26 @@ type LocalK8sNamespaces {
   metadata: k8sNamespaceMetadata
 }
 
-type LocalFluxResourceStatus {
+type LocalFluxResourceMetadata {
+  name: String
+}
+
+type LocalFluxResourceConditions {
+  message: String
+  reason: String
   status: String
   type: String
 }
 
+type LocalFluxKustomization {
+  kind: String
+  metadata: LocalFluxResourceMetadata
+  conditions: [LocalFluxResourceConditions]
+}
+type LocalFluxHelmReleaseStatus {
+  conditions: [LocalFluxResourceConditions]
+}
+
 type K8sResources {
   k8sPods: K8sResource
   k8sServices: K8sResource
@@ -95,14 +110,14 @@ extend type Query {
   k8sPods(configuration: LocalConfiguration, namespace: String): [LocalWorkloadItem]
   k8sServices(configuration: LocalConfiguration, namespace: String): [LocalWorkloadItem]
   k8sConnection(configuration: LocalConfiguration): K8sResources
-  fluxKustomizationStatus(
+  fluxKustomization(
     configuration: LocalConfiguration
     fluxResourcePath: String
-  ): LocalFluxResourceStatus
+  ): LocalFluxKustomization
   fluxHelmReleaseStatus(
     configuration: LocalConfiguration
     fluxResourcePath: String
-  ): LocalFluxResourceStatus
+  ): LocalFluxHelmReleaseStatus
 }
 
 input ResourceTypeParam {
diff --git a/spec/frontend/environments/environment_details/components/kubernetes/kubernetes_overview_spec.js b/spec/frontend/environments/environment_details/components/kubernetes/kubernetes_overview_spec.js
index a5b7b91274431..03c1fd24bb53b 100644
--- a/spec/frontend/environments/environment_details/components/kubernetes/kubernetes_overview_spec.js
+++ b/spec/frontend/environments/environment_details/components/kubernetes/kubernetes_overview_spec.js
@@ -1,13 +1,18 @@
-import { nextTick } from 'vue';
+import Vue, { nextTick } from 'vue';
+import VueApollo from 'vue-apollo';
 import { GlEmptyState, GlAlert } from '@gitlab/ui';
 import { shallowMount } from '@vue/test-utils';
+import waitForPromises from 'helpers/wait_for_promises';
+import createMockApollo from 'helpers/mock_apollo_helper';
 import KubernetesOverview from '~/environments/environment_details/components/kubernetes/kubernetes_overview.vue';
 import KubernetesStatusBar from '~/environments/environment_details/components/kubernetes/kubernetes_status_bar.vue';
 import KubernetesAgentInfo from '~/environments/environment_details/components/kubernetes/kubernetes_agent_info.vue';
 import KubernetesTabs from '~/environments/environment_details/components/kubernetes/kubernetes_tabs.vue';
 import { k8sResourceType } from '~/environments/graphql/resolvers/kubernetes/constants';
-import { agent, kubernetesNamespace, fluxResourcePathMock } from '../../../graphql/mock_data';
-import { mockKasTunnelUrl } from '../../../mock_data';
+import { agent, kubernetesNamespace } from '../../../graphql/mock_data';
+import { mockKasTunnelUrl, fluxResourceStatus, fluxKustomization } from '../../../mock_data';
+
+Vue.use(VueApollo);
 
 describe('~/environments/environment_details/components/kubernetes/kubernetes_overview.vue', () => {
   let wrapper;
@@ -15,7 +20,6 @@ describe('~/environments/environment_details/components/kubernetes/kubernetes_ov
   const defaultProps = {
     environmentName: 'production',
     kubernetesNamespace,
-    fluxResourcePath: fluxResourcePathMock,
   };
 
   const provide = {
@@ -32,13 +36,36 @@ describe('~/environments/environment_details/components/kubernetes/kubernetes_ov
     credentials: 'include',
   };
 
-  const createWrapper = (clusterAgent = agent) => {
+  const kustomizationResourcePath =
+    'kustomize.toolkit.fluxcd.io/v1/namespaces/my-namespace/kustomizations/app';
+
+  const fluxKustomizationQuery = jest.fn().mockReturnValue({});
+  const fluxHelmReleaseStatusQuery = jest.fn().mockReturnValue({});
+
+  const createApolloProvider = () => {
+    const mockResolvers = {
+      Query: {
+        fluxKustomization: fluxKustomizationQuery,
+        fluxHelmReleaseStatus: fluxHelmReleaseStatusQuery,
+      },
+    };
+
+    return createMockApollo([], mockResolvers);
+  };
+
+  const createWrapper = ({
+    clusterAgent = agent,
+    fluxResourcePath = kustomizationResourcePath,
+    apolloProvider = createApolloProvider(),
+  } = {}) => {
     return shallowMount(KubernetesOverview, {
       provide,
       propsData: {
         ...defaultProps,
         clusterAgent,
+        fluxResourcePath,
       },
+      apolloProvider,
     });
   };
 
@@ -46,38 +73,46 @@ describe('~/environments/environment_details/components/kubernetes/kubernetes_ov
   const findKubernetesStatusBar = () => wrapper.findComponent(KubernetesStatusBar);
   const findKubernetesTabs = () => wrapper.findComponent(KubernetesTabs);
   const findEmptyState = () => wrapper.findComponent(GlEmptyState);
-
   const findAlert = () => wrapper.findComponent(GlAlert);
 
   describe('when the agent data is present', () => {
-    beforeEach(() => {
+    it('renders kubernetes agent info', () => {
       wrapper = createWrapper();
-    });
 
-    it('renders kubernetes agent info', () => {
       expect(findAgentInfo().props('clusterAgent')).toEqual(agent);
     });
 
     it('renders kubernetes tabs', () => {
-      expect(findKubernetesTabs().props()).toEqual({
+      wrapper = createWrapper();
+
+      expect(findKubernetesTabs().props()).toMatchObject({
         namespace: kubernetesNamespace,
         configuration,
         value: k8sResourceType.k8sPods,
+        fluxKustomization: {},
       });
     });
 
     it('renders kubernetes status bar', () => {
+      wrapper = createWrapper();
+
       expect(findKubernetesStatusBar().props()).toEqual({
         clusterHealthStatus: 'success',
         configuration,
         environmentName: defaultProps.environmentName,
-        fluxResourcePath: fluxResourcePathMock,
+        fluxResourcePath: kustomizationResourcePath,
         namespace: kubernetesNamespace,
         resourceType: k8sResourceType.k8sPods,
+        fluxApiError: '',
+        fluxResourceStatus: [],
       });
     });
 
     describe('Kubernetes health status', () => {
+      beforeEach(() => {
+        wrapper = createWrapper();
+      });
+
       it("doesn't set `clusterHealthStatus` when pods are still loading", async () => {
         findKubernetesTabs().vm.$emit('loading', true);
         await nextTick();
@@ -107,6 +142,132 @@ describe('~/environments/environment_details/components/kubernetes/kubernetes_ov
       });
     });
 
+    describe('Flux resource', () => {
+      describe('when no flux resource path is provided', () => {
+        beforeEach(() => {
+          wrapper = createWrapper({ fluxResourcePath: '' });
+        });
+
+        it("doesn't request Kustomizations and HelmReleases", () => {
+          expect(fluxKustomizationQuery).not.toHaveBeenCalled();
+          expect(fluxHelmReleaseStatusQuery).not.toHaveBeenCalled();
+        });
+
+        it('provides empty `fluxResourceStatus` to KubernetesStatusBar', () => {
+          expect(findKubernetesStatusBar().props('fluxResourceStatus')).toEqual([]);
+        });
+
+        it('provides empty `fluxKustomization` to KubernetesTabs', () => {
+          expect(findKubernetesTabs().props('fluxKustomization')).toEqual({});
+        });
+      });
+
+      describe('when flux resource path is provided', () => {
+        describe('if the provided resource is a Kustomization', () => {
+          beforeEach(() => {
+            wrapper = createWrapper({ fluxResourcePath: kustomizationResourcePath });
+          });
+
+          it('requests the Kustomization resource status', () => {
+            expect(fluxKustomizationQuery).toHaveBeenCalledWith(
+              {},
+              expect.objectContaining({
+                configuration,
+                fluxResourcePath: kustomizationResourcePath,
+              }),
+              expect.any(Object),
+              expect.any(Object),
+            );
+          });
+
+          it("doesn't request HelmRelease resource status", () => {
+            expect(fluxHelmReleaseStatusQuery).not.toHaveBeenCalled();
+          });
+        });
+
+        describe('if the provided resource is a helmRelease', () => {
+          const helmResourcePath =
+            'helm.toolkit.fluxcd.io/v2beta1/namespaces/my-namespace/helmreleases/app';
+
+          beforeEach(() => {
+            createWrapper({ fluxResourcePath: helmResourcePath });
+          });
+
+          it('requests the HelmRelease resource status', () => {
+            expect(fluxHelmReleaseStatusQuery).toHaveBeenCalledWith(
+              {},
+              expect.objectContaining({
+                configuration,
+                fluxResourcePath: helmResourcePath,
+              }),
+              expect.any(Object),
+              expect.any(Object),
+            );
+          });
+
+          it("doesn't request Kustomization resource status", () => {
+            expect(fluxKustomizationQuery).not.toHaveBeenCalled();
+          });
+        });
+
+        describe('with Flux Kustomizations available', () => {
+          const createApolloProviderWithKustomizations = () => {
+            const mockResolvers = {
+              Query: {
+                fluxKustomization: jest.fn().mockReturnValue(fluxKustomization),
+                fluxHelmReleaseStatus: fluxHelmReleaseStatusQuery,
+              },
+            };
+
+            return createMockApollo([], mockResolvers);
+          };
+
+          beforeEach(async () => {
+            wrapper = createWrapper({
+              apolloProvider: createApolloProviderWithKustomizations(),
+            });
+            await waitForPromises();
+          });
+          it('provides correct `fluxResourceStatus` to KubernetesStatusBar', () => {
+            expect(findKubernetesStatusBar().props('fluxResourceStatus')).toEqual(
+              fluxResourceStatus,
+            );
+          });
+
+          it('provides correct `fluxKustomization` to KubernetesTabs', () => {
+            expect(findKubernetesTabs().props('fluxKustomization')).toEqual(fluxKustomization);
+          });
+        });
+
+        describe('when Flux API errored', () => {
+          const error = new Error('Error from the cluster_client API');
+          const createApolloProviderWithErrors = () => {
+            const mockResolvers = {
+              Query: {
+                fluxKustomization: jest.fn().mockRejectedValueOnce(error),
+                fluxHelmReleaseStatus: jest.fn().mockRejectedValueOnce(error),
+              },
+            };
+
+            return createMockApollo([], mockResolvers);
+          };
+
+          beforeEach(async () => {
+            wrapper = createWrapper({
+              apolloProvider: createApolloProviderWithErrors(),
+              fluxResourcePath:
+                'kustomize.toolkit.fluxcd.io/v1/namespaces/my-namespace/kustomizations/app',
+            });
+            await waitForPromises();
+          });
+
+          it('provides api error to KubernetesStatusBar', () => {
+            expect(findKubernetesStatusBar().props('fluxApiError')).toEqual(error.message);
+          });
+        });
+      });
+    });
+
     describe('on child component error', () => {
       beforeEach(() => {
         wrapper = createWrapper();
@@ -128,7 +289,7 @@ describe('~/environments/environment_details/components/kubernetes/kubernetes_ov
 
   describe('when there is no cluster agent data', () => {
     beforeEach(() => {
-      wrapper = createWrapper(null);
+      wrapper = createWrapper({ clusterAgent: null, fluxResourcePath: '' });
     });
 
     it('renders empty state component', () => {
diff --git a/spec/frontend/environments/environment_details/components/kubernetes/kubernetes_status_bar_spec.js b/spec/frontend/environments/environment_details/components/kubernetes/kubernetes_status_bar_spec.js
index cbb31c82411d5..034fc6c6111db 100644
--- a/spec/frontend/environments/environment_details/components/kubernetes/kubernetes_status_bar_spec.js
+++ b/spec/frontend/environments/environment_details/components/kubernetes/kubernetes_status_bar_spec.js
@@ -1,5 +1,3 @@
-import Vue from 'vue';
-import VueApollo from 'vue-apollo';
 import { GlLoadingIcon, GlPopover, GlSprintf } from '@gitlab/ui';
 import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
 import KubernetesStatusBar from '~/environments/environment_details/components/kubernetes/kubernetes_status_bar.vue';
@@ -15,14 +13,10 @@ import {
   connectionStatus,
   k8sResourceType,
 } from '~/environments/graphql/resolvers/kubernetes/constants';
-import waitForPromises from 'helpers/wait_for_promises';
-import createMockApollo from 'helpers/mock_apollo_helper';
 import { stubComponent } from 'helpers/stub_component';
 import { mockKasTunnelUrl } from '../../../mock_data';
 import { kubernetesNamespace } from '../../../graphql/mock_data';
 
-Vue.use(VueApollo);
-
 const configuration = {
   basePath: mockKasTunnelUrl.replace(/\/$/, ''),
   baseOptions: {
@@ -45,24 +39,11 @@ describe('~/environments/environment_details/components/kubernetes/kubernetes_st
   const findFluxConnectionStatusBadge = () => wrapper.findByTestId('flux-status-badge');
   const findFluxConnectionStatus = () => wrapper.findByTestId('flux-connection-status');
 
-  const fluxKustomizationStatusQuery = jest.fn().mockReturnValue([]);
-  const fluxHelmReleaseStatusQuery = jest.fn().mockReturnValue([]);
-
-  const createApolloProvider = () => {
-    const mockResolvers = {
-      Query: {
-        fluxKustomizationStatus: fluxKustomizationStatusQuery,
-        fluxHelmReleaseStatus: fluxHelmReleaseStatusQuery,
-      },
-    };
-
-    return createMockApollo([], mockResolvers);
-  };
-
   const createWrapper = ({
-    apolloProvider = createApolloProvider(),
     clusterHealthStatus = '',
     fluxResourcePath = '',
+    fluxResourceStatus = [],
+    fluxApiError = '',
     namespace = kubernetesNamespace,
     resourceType = k8sResourceType.k8sPods,
     connectionStatusValue = connectionStatus.connected,
@@ -75,8 +56,9 @@ describe('~/environments/environment_details/components/kubernetes/kubernetes_st
         fluxResourcePath,
         namespace,
         resourceType,
+        fluxResourceStatus,
+        fluxApiError,
       },
-      apolloProvider,
       stubs: {
         GlSprintf,
         KubernetesConnectionStatus: stubComponent(KubernetesConnectionStatus, {
@@ -180,153 +162,46 @@ describe('~/environments/environment_details/components/kubernetes/kubernetes_st
         createWrapper();
       });
 
-      it("doesn't request Kustomizations and HelmReleases", () => {
-        expect(fluxKustomizationStatusQuery).not.toHaveBeenCalled();
-        expect(fluxHelmReleaseStatusQuery).not.toHaveBeenCalled();
-      });
-
       it('renders sync status as Unavailable', () => {
         expect(findSyncBadge().text()).toBe('Unavailable');
       });
     });
 
-    describe('when flux resource path is provided', () => {
-      let fluxResourcePath;
-
-      describe('if the provided resource is a Kustomization', () => {
-        beforeEach(() => {
-          fluxResourcePath = kustomizationResourcePath;
-
-          createWrapper({ fluxResourcePath });
-        });
-
-        it('requests the Kustomization resource status', () => {
-          expect(fluxKustomizationStatusQuery).toHaveBeenCalledWith(
-            {},
-            expect.objectContaining({
-              configuration,
-              fluxResourcePath,
-            }),
-            expect.any(Object),
-            expect.any(Object),
-          );
-        });
-
-        it("doesn't request HelmRelease resource status", () => {
-          expect(fluxHelmReleaseStatusQuery).not.toHaveBeenCalled();
-        });
-      });
-
-      describe('if the provided resource is a helmRelease', () => {
-        beforeEach(() => {
-          fluxResourcePath =
-            'helm.toolkit.fluxcd.io/v2beta1/namespaces/my-namespace/helmreleases/app';
-
-          createWrapper({ fluxResourcePath });
-        });
-
-        it('requests the HelmRelease resource status', () => {
-          expect(fluxHelmReleaseStatusQuery).toHaveBeenCalledWith(
-            {},
-            expect.objectContaining({
-              configuration,
-              fluxResourcePath,
-            }),
-            expect.any(Object),
-            expect.any(Object),
-          );
-        });
-
-        it("doesn't request Kustomization resource status", () => {
-          expect(fluxKustomizationStatusQuery).not.toHaveBeenCalled();
-        });
-      });
-
-      describe('with Flux Kustomizations available', () => {
-        const createApolloProviderWithKustomizations = ({
-          result = { status: 'True', type: 'Ready', message: '' },
-        } = {}) => {
-          const mockResolvers = {
-            Query: {
-              fluxKustomizationStatus: jest.fn().mockReturnValue([result]),
-              fluxHelmReleaseStatus: fluxHelmReleaseStatusQuery,
-            },
-          };
-
-          return createMockApollo([], mockResolvers);
-        };
-
-        it("doesn't request HelmReleases when the Kustomizations were found", async () => {
+    describe('when flux status data is provided', () => {
+      const message = 'Message from Flux';
+
+      it.each`
+        status       | type             | reason           | statusText       | statusPopover
+        ${'True'}    | ${'Stalled'}     | ${''}            | ${'Stalled'}     | ${message}
+        ${'True'}    | ${'Reconciling'} | ${''}            | ${'Reconciling'} | ${'Flux sync reconciling'}
+        ${'Unknown'} | ${'Ready'}       | ${'Progressing'} | ${'Reconciling'} | ${message}
+        ${'True'}    | ${'Ready'}       | ${''}            | ${'Reconciled'}  | ${'Flux sync reconciled successfully'}
+        ${'False'}   | ${'Ready'}       | ${''}            | ${'Failed'}      | ${message}
+        ${'Unknown'} | ${'Ready'}       | ${''}            | ${'Unknown'}     | ${'Unable to detect state. How are states detected?'}
+      `(
+        'renders sync status as $statusText when status is $status, type is $type, and reason is $reason',
+        ({ status, type, reason, statusText, statusPopover }) => {
           createWrapper({
-            apolloProvider: createApolloProviderWithKustomizations(),
-          });
-          await waitForPromises();
-
-          expect(fluxHelmReleaseStatusQuery).not.toHaveBeenCalled();
-        });
-      });
-
-      describe('when receives data from the Flux', () => {
-        const createApolloProviderWithKustomizations = (result) => {
-          const mockResolvers = {
-            Query: {
-              fluxKustomizationStatus: jest.fn().mockReturnValue([result]),
-              fluxHelmReleaseStatus: fluxHelmReleaseStatusQuery,
-            },
-          };
-
-          return createMockApollo([], mockResolvers);
-        };
-        const message = 'Message from Flux';
-
-        it.each`
-          status       | type             | reason           | statusText       | statusPopover
-          ${'True'}    | ${'Stalled'}     | ${''}            | ${'Stalled'}     | ${message}
-          ${'True'}    | ${'Reconciling'} | ${''}            | ${'Reconciling'} | ${'Flux sync reconciling'}
-          ${'Unknown'} | ${'Ready'}       | ${'Progressing'} | ${'Reconciling'} | ${message}
-          ${'True'}    | ${'Ready'}       | ${''}            | ${'Reconciled'}  | ${'Flux sync reconciled successfully'}
-          ${'False'}   | ${'Ready'}       | ${''}            | ${'Failed'}      | ${message}
-          ${'Unknown'} | ${'Ready'}       | ${''}            | ${'Unknown'}     | ${'Unable to detect state. How are states detected?'}
-        `(
-          'renders sync status as $statusText when status is $status, type is $type, and reason is $reason',
-          async ({ status, type, reason, statusText, statusPopover }) => {
-            createWrapper({
-              fluxResourcePath: kustomizationResourcePath,
-              apolloProvider: createApolloProviderWithKustomizations({
+            fluxResourceStatus: [
+              {
                 status,
                 type,
                 reason,
                 message,
-              }),
-            });
-            await waitForPromises();
+              },
+            ],
+          });
 
-            expect(findSyncBadge().text()).toBe(statusText);
-            expect(findPopover().text()).toBe(statusPopover);
-          },
-        );
-      });
+          expect(findSyncBadge().text()).toBe(statusText);
+          expect(findPopover().text()).toBe(statusPopover);
+        },
+      );
 
       describe('when Flux API errored', () => {
-        const error = new Error('Error from the cluster_client API');
-        const createApolloProviderWithErrors = () => {
-          const mockResolvers = {
-            Query: {
-              fluxKustomizationStatus: jest.fn().mockRejectedValueOnce(error),
-              fluxHelmReleaseStatus: jest.fn().mockRejectedValueOnce(error),
-            },
-          };
-
-          return createMockApollo([], mockResolvers);
-        };
+        const fluxApiError = 'Error from the cluster_client API';
 
-        beforeEach(async () => {
-          createWrapper({
-            apolloProvider: createApolloProviderWithErrors(),
-            fluxResourcePath:
-              'kustomize.toolkit.fluxcd.io/v1/namespaces/my-namespace/kustomizations/app',
-          });
-          await waitForPromises();
+        beforeEach(() => {
+          createWrapper({ fluxApiError });
         });
 
         it('renders sync badge as unavailable', () => {
@@ -340,7 +215,7 @@ describe('~/environments/environment_details/components/kubernetes/kubernetes_st
         });
 
         it('renders popover with an API error message', () => {
-          expect(findPopover().text()).toBe(error.message);
+          expect(findPopover().text()).toBe(fluxApiError);
           expect(findPopover().props('title')).toBe('Flux sync status is unavailable');
         });
       });
diff --git a/spec/frontend/environments/environment_details/components/kubernetes/kubernetes_summary_spec.js b/spec/frontend/environments/environment_details/components/kubernetes/kubernetes_summary_spec.js
index 8c6a745a0d31e..0d2888d82abbd 100644
--- a/spec/frontend/environments/environment_details/components/kubernetes/kubernetes_summary_spec.js
+++ b/spec/frontend/environments/environment_details/components/kubernetes/kubernetes_summary_spec.js
@@ -1,6 +1,7 @@
 import { shallowMount } from '@vue/test-utils';
 import { GlTab } from '@gitlab/ui';
 import KubernetesSummary from '~/environments/environment_details/components/kubernetes/kubernetes_summary.vue';
+import { fluxKustomization } from '../../../mock_data';
 
 describe('~/environments/environment_details/components/kubernetes/kubernetes_summary.vue', () => {
   let wrapper;
@@ -9,6 +10,9 @@ describe('~/environments/environment_details/components/kubernetes/kubernetes_su
 
   const createWrapper = () => {
     wrapper = shallowMount(KubernetesSummary, {
+      propsData: {
+        fluxKustomization,
+      },
       stubs: { GlTab },
     });
   };
@@ -23,7 +27,11 @@ describe('~/environments/environment_details/components/kubernetes/kubernetes_su
     });
 
     it('renders tree view title', () => {
-      expect(findTab().text()).toBe('Tree view');
+      expect(findTab().text()).toContain('Tree view');
+    });
+
+    it('renders kustomization resource data', () => {
+      expect(findTab().text()).toContain('Kustomization: my-kustomization');
     });
   });
 });
diff --git a/spec/frontend/environments/environment_details/components/kubernetes/kubernetes_tabs_spec.js b/spec/frontend/environments/environment_details/components/kubernetes/kubernetes_tabs_spec.js
index db5f3fe07b3c5..6dc86f1292749 100644
--- a/spec/frontend/environments/environment_details/components/kubernetes/kubernetes_tabs_spec.js
+++ b/spec/frontend/environments/environment_details/components/kubernetes/kubernetes_tabs_spec.js
@@ -7,7 +7,7 @@ import KubernetesServices from '~/environments/environment_details/components/ku
 import KubernetesSummary from '~/environments/environment_details/components/kubernetes/kubernetes_summary.vue';
 import WorkloadDetails from '~/kubernetes_dashboard/components/workload_details.vue';
 import { k8sResourceType } from '~/environments/graphql/resolvers/kubernetes/constants';
-import { mockKasTunnelUrl } from 'jest/environments/mock_data';
+import { mockKasTunnelUrl, fluxKustomization } from 'jest/environments/mock_data';
 import { mockPodsTableItems } from 'jest/kubernetes_dashboard/graphql/mock_data';
 
 describe('~/environments/environment_details/components/kubernetes/kubernetes_tabs.vue', () => {
@@ -20,6 +20,7 @@ describe('~/environments/environment_details/components/kubernetes/kubernetes_ta
       headers: { 'GitLab-Agent-Id': '1' },
     },
   };
+
   const findTabs = () => wrapper.findComponent(GlTabs);
   const findKubernetesPods = () => wrapper.findComponent(KubernetesPods);
   const findKubernetesServices = () => wrapper.findComponent(KubernetesServices);
@@ -35,15 +36,15 @@ describe('~/environments/environment_details/components/kubernetes/kubernetes_ta
       provide: {
         glFeatures: { k8sTreeView: k8sTreeViewEnabled },
       },
-      propsData: { configuration, namespace, value: activeTab },
+      propsData: { configuration, namespace, fluxKustomization, value: activeTab },
       stubs: { GlDrawer },
     });
   };
 
   describe('mounted', () => {
-    describe('when `k8sTreeView feature flag is disabled', () => {
+    describe('when `k8sTreeView feature flag is enabled', () => {
       beforeEach(() => {
-        createWrapper();
+        createWrapper({ k8sTreeViewEnabled: true });
       });
 
       it('shows tabs', () => {
@@ -58,14 +59,15 @@ describe('~/environments/environment_details/components/kubernetes/kubernetes_ta
         expect(findKubernetesServices().props()).toEqual({ namespace, configuration });
       });
 
-      it("doesn't render summary tab", () => {
-        expect(findKubernetesSummary().exists()).toBe(false);
+      it('renders summary tab', () => {
+        expect(findKubernetesSummary().props('fluxKustomization')).toEqual(fluxKustomization);
       });
     });
 
-    it('renders summary tab if the feature flag is enabled', () => {
-      createWrapper({ k8sTreeViewEnabled: true });
-      expect(findKubernetesSummary().exists()).toBe(true);
+    it('renders summary tab if the feature flag is disabled', () => {
+      createWrapper();
+
+      expect(findKubernetesSummary().exists()).toBe(false);
     });
   });
 
diff --git a/spec/frontend/environments/graphql/mock_data.js b/spec/frontend/environments/graphql/mock_data.js
index 9501887b10c21..b4104f303e0fb 100644
--- a/spec/frontend/environments/graphql/mock_data.js
+++ b/spec/frontend/environments/graphql/mock_data.js
@@ -815,12 +815,20 @@ export const k8sNamespacesMock = [
   { metadata: { name: 'agent' } },
 ];
 
-export const fluxKustomizationsMock = [
-  {
-    status: 'True',
-    type: 'Ready',
-  },
-];
+const fluxResourceStatusMock = [{ status: 'True', type: 'Ready', message: '', reason: '' }];
+export const fluxKustomizationMock = {
+  kind: 'Kustomization',
+  metadata: { name: 'custom-resource', namespace: 'custom-namespace' },
+  status: { conditions: fluxResourceStatusMock },
+};
+export const fluxKustomizationMapped = {
+  kind: 'Kustomization',
+  metadata: { name: 'custom-resource' },
+  conditions: fluxResourceStatusMock,
+};
+export const fluxHelmReleaseMapped = {
+  conditions: fluxResourceStatusMock,
+};
 
 export const fluxResourcePathMock = 'kustomize.toolkit.fluxcd.io/v1/path/to/flux/resource';
 
diff --git a/spec/frontend/environments/graphql/resolvers/flux_spec.js b/spec/frontend/environments/graphql/resolvers/flux_spec.js
index 9f002d2727760..dc2e0f14853c2 100644
--- a/spec/frontend/environments/graphql/resolvers/flux_spec.js
+++ b/spec/frontend/environments/graphql/resolvers/flux_spec.js
@@ -8,7 +8,11 @@ import {
   connectionStatus,
   k8sResourceType,
 } from '~/environments/graphql/resolvers/kubernetes/constants';
-import { fluxKustomizationsMock } from '../mock_data';
+import {
+  fluxKustomizationMock,
+  fluxKustomizationMapped,
+  fluxHelmReleaseMapped,
+} from '../mock_data';
 
 jest.mock('~/environments/graphql/resolvers/kubernetes/k8s_connection_status');
 
@@ -32,7 +36,7 @@ describe('~/frontend/environments/graphql/resolvers', () => {
     mock.reset();
   });
 
-  describe('fluxKustomizationStatus', () => {
+  describe('fluxKustomization', () => {
     const client = { writeQuery: jest.fn() };
     const fluxResourcePath =
       'kustomize.toolkit.fluxcd.io/v1/namespaces/my-namespace/kustomizations/app';
@@ -44,7 +48,7 @@ describe('~/frontend/environments/graphql/resolvers', () => {
     });
     const mockOnDataFn = jest.fn().mockImplementation((eventName, callback) => {
       if (eventName === 'data') {
-        callback(fluxKustomizationsMock);
+        callback([fluxKustomizationMock]);
       }
     });
     const resourceName = 'custom-resource';
@@ -62,12 +66,11 @@ describe('~/frontend/environments/graphql/resolvers', () => {
           .onGet(endpoint, { withCredentials: true, headers: configuration.baseOptions.headers })
           .reply(HTTP_STATUS_OK, {
             apiVersion,
-            metadata: { name: resourceName, namespace: resourceNamespace },
-            status: { conditions: fluxKustomizationsMock },
+            ...fluxKustomizationMock,
           });
       });
       it('should watch Kustomization by the metadata name from the cluster_client library when the data is present', async () => {
-        await mockResolvers.Query.fluxKustomizationStatus(
+        await mockResolvers.Query.fluxKustomization(
           null,
           {
             configuration,
@@ -92,7 +95,7 @@ describe('~/frontend/environments/graphql/resolvers', () => {
       });
 
       it('should return data when received from the library', async () => {
-        const kustomizationStatus = await mockResolvers.Query.fluxKustomizationStatus(
+        const kustomizationStatus = await mockResolvers.Query.fluxKustomization(
           null,
           {
             configuration,
@@ -101,7 +104,7 @@ describe('~/frontend/environments/graphql/resolvers', () => {
           { client },
         );
 
-        expect(kustomizationStatus).toEqual(fluxKustomizationsMock);
+        expect(kustomizationStatus).toEqual(fluxKustomizationMapped);
       });
     });
 
@@ -110,7 +113,7 @@ describe('~/frontend/environments/graphql/resolvers', () => {
         .onGet(endpoint, { withCredentials: true, headers: configuration.baseOptions.headers })
         .reply(HTTP_STATUS_OK, {});
 
-      await mockResolvers.Query.fluxKustomizationStatus(
+      await mockResolvers.Query.fluxKustomization(
         null,
         {
           configuration,
@@ -128,7 +131,7 @@ describe('~/frontend/environments/graphql/resolvers', () => {
         .onGet(endpoint, { withCredentials: true, headers: configuration.base })
         .reply(HTTP_STATUS_UNAUTHORIZED, { message: apiError });
 
-      const fluxKustomizationsError = mockResolvers.Query.fluxKustomizationStatus(
+      const fluxKustomizationsError = mockResolvers.Query.fluxKustomization(
         null,
         {
           configuration,
@@ -153,7 +156,7 @@ describe('~/frontend/environments/graphql/resolvers', () => {
     });
     const mockOnDataFn = jest.fn().mockImplementation((eventName, callback) => {
       if (eventName === 'data') {
-        callback(fluxKustomizationsMock);
+        callback([fluxKustomizationMock]);
       }
     });
     const resourceName = 'custom-resource';
@@ -171,8 +174,7 @@ describe('~/frontend/environments/graphql/resolvers', () => {
           .onGet(endpoint, { withCredentials: true, headers: configuration.baseOptions.headers })
           .reply(HTTP_STATUS_OK, {
             apiVersion,
-            metadata: { name: resourceName, namespace: resourceNamespace },
-            status: { conditions: fluxKustomizationsMock },
+            ...fluxKustomizationMock,
           });
       });
       it('should watch HelmRelease by the metadata name from the cluster_client library when the data is present', async () => {
@@ -204,7 +206,7 @@ describe('~/frontend/environments/graphql/resolvers', () => {
           { client },
         );
 
-        expect(fluxHelmReleaseStatus).toEqual(fluxKustomizationsMock);
+        expect(fluxHelmReleaseStatus).toEqual(fluxHelmReleaseMapped);
       });
     });
 
diff --git a/spec/frontend/environments/mock_data.js b/spec/frontend/environments/mock_data.js
index 0efc2fb10abe9..7504054412ac8 100644
--- a/spec/frontend/environments/mock_data.js
+++ b/spec/frontend/environments/mock_data.js
@@ -315,6 +315,13 @@ const createEnvironment = (data = {}) => ({
 
 const mockKasTunnelUrl = 'https://kas.gitlab.com/k8s-proxy';
 
+const fluxResourceStatus = [{ status: 'True', type: 'Ready', message: '', reason: '' }];
+const fluxKustomization = {
+  kind: 'Kustomization',
+  metadata: { name: 'my-kustomization' },
+  conditions: fluxResourceStatus,
+};
+
 export {
   environment,
   environmentsList,
@@ -324,4 +331,6 @@ export {
   deployBoardMockData,
   createEnvironment,
   mockKasTunnelUrl,
+  fluxResourceStatus,
+  fluxKustomization,
 };
-- 
GitLab