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