Skip to content

Commit

Permalink
feat(analytics): conditionally show analytics UI elements [MA-2736]
Browse files Browse the repository at this point in the history
- Check the org configuration endpoint when rendering analytics.
- If the org doesn't have analytics, hide the UI elements.
- Show a "forbidden" screen on the analytics apps page if there's nothing to show.
- Bump portal API client version to one that supports the config endpoint.
  • Loading branch information
adorack committed May 21, 2024
1 parent 0395371 commit ef97e5f
Show file tree
Hide file tree
Showing 7 changed files with 288 additions and 169 deletions.
72 changes: 72 additions & 0 deletions cypress/e2e/specs/contextual-analytics.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
import { apps, productRegistrations } from '../fixtures/consts'

const noAnalytics = {
analytics: null
}

const hasAnalytics = {
analytics: {
percentiles: true,
retention_ms: 36720000000
}
}

const mockConfig = (analytics: boolean) => {
cy.intercept('GET', '**/api/v2/stats/config', {
statusCode: 200,
body: analytics ? hasAnalytics : noAnalytics,
delay: 0
})
}

describe('Contextual Developer Analytics', () => {
beforeEach(() => {
cy.mockPrivatePortal()
Expand All @@ -22,6 +41,8 @@ describe('Contextual Developer Analytics', () => {
}

it('My Apps – displays displays metric cards', () => {
mockConfig(true)

cy.mockApplications(apps, 4)

cy.visit('/', { useOriginalFn: true })
Expand All @@ -36,6 +57,8 @@ describe('Contextual Developer Analytics', () => {


it('App Dashboard - vitals elements load', () => {
mockConfig(true)

cy.mockApplications(apps, 4)

cy.intercept('GET', `**/api/v2/applications/${apps[0].id}`, {
Expand Down Expand Up @@ -79,4 +102,53 @@ describe('Contextual Developer Analytics', () => {
cy.get('[data-testid="k-multiselect-input"]').should('exist').click()
cy.get('.k-multiselect-item').first().should('contain', mockedServiceVersionName)
})

it('My Apps – metric cards if no analytics', () => {
mockConfig(false)

cy.mockApplications(apps, 4)

cy.visit('/', { useOriginalFn: true })
cy.visit('/my-apps')

cy.get(selectors.metricCardsParent).should('not.exist')

cy.get('[data-testid="applications-table"]').find('.actions-badge').first().click()
cy.get(selectors.dashboardDropdownLink).should('not.exist')
})

it('App Dashboard - no vitals elements if no analytics', () => {
mockConfig(false)

cy.mockApplications(apps, 4)

cy.intercept('GET', `**/api/v2/applications/${apps[0].id}`, {
statusCode: 200,
body: apps[0],
delay: 0
}).as('getSingleApplication')

cy.intercept(
'GET',
`**/api/v2/applications/${apps[0].id}/registrations*`,
{
body: {
data: productRegistrations,
meta: {
page: {
total: 1,
number: 1,
size: 1
}
}
},
delay: 0
}
).as('getApplicationRegistration')

cy.visit(`/application/${apps[0].id}/application-dashboard`)

// There should be a forbidden page.
cy.get('section.forbidden').should('exist')
})
})
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,16 @@
},
"dependencies": {
"@kong-ui-public/analytics-chart": "0.15.2",
"@kong-ui-public/analytics-config-store": "0.4.2",
"@kong-ui-public/analytics-metric-provider": "1.2.9",
"@kong-ui-public/analytics-utilities": "0.10.1",
"@kong-ui-public/copy-uuid": "1.3.12",
"@kong-ui-public/document-viewer": "2.0.50",
"@kong-ui-public/portal-analytics-bridge": "0.1.0",
"@kong-ui-public/spec-renderer": "2.1.23",
"@kong/kong-auth-elements": "2.12.1",
"@kong/kongponents": "8.127.0",
"@kong/sdk-portal-js": "2.10.0",
"@kong/sdk-portal-js": "2.11.1",
"@xstate/vue": "2.0.0",
"axios": "1.6.7",
"date-fns": "3.3.0",
Expand Down
5 changes: 5 additions & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import '@kong-ui-public/copy-uuid/dist/style.css'
import useToaster from './composables/useToaster'
import usePortalApi from './hooks/usePortalApi'
import { createRedirectHandler } from './helpers/auth'
import portalAnalyticsBridge from '@kong-ui-public/portal-analytics-bridge'

/**
* Initialize application
Expand Down Expand Up @@ -83,6 +84,10 @@ async function init () {

await initLaunchDarkly()

app.use(portalAnalyticsBridge, {
apiClient: portalApiV2.value.service.applicationAnalyticsApi
})

if (!isPublic) {
if (session.authenticatedWithIdp()) {
let res
Expand Down
203 changes: 105 additions & 98 deletions src/views/Applications/ApplicationDashboard.vue
Original file line number Diff line number Diff line change
@@ -1,106 +1,111 @@
<template>
<Content>
<KSkeleton v-if="currentState.matches('pending')" />
<KBreadcrumbs
v-if="!currentState.matches('pending')"
:items="breadcrumbs"
/>
<EmptyState
v-else-if="currentState.matches('error')"
is-error
:message="errorMessage"
/>
<section
v-if="currentState.matches('success')"
>
<PageTitle
class="mb-5"
:title="helpText.analytics.dashboard"
<AnalyticsConfigCheck require-analytics>
<Content>
<KSkeleton v-if="currentState.matches('pending')" />
<KBreadcrumbs
v-if="!currentState.matches('pending')"
:items="breadcrumbs"
/>
<div v-if="hasProductVersions">
<div
class="analytics-filters d-flex flex-grow-1 justify-content-between align-items-baseline mb-6"
>
<KMultiselect
v-model="versionMultiSelectModel"
autosuggest
collapsed-context
data-testid="analytics-service-filter"
class="analytics-service-filter flex-grow-1"
:dropdown-footer-text="multiselectFooter"
dropdown-footer-text-position="static"
:items="multiselectItems"
:label="helpText.analytics.filterLabelProductVersions"
:loading="filterMultiselectLoading"
@change="handleChangedItem"
@query-change="handleProductVersionSearch"
@selected="handleProductVersionSelection"
/>
<div>
<KLabel for="dateTimePicker">
{{ helpText.analytics.timeRange }}
</KLabel>
<KDateTimePicker
id="analytics-timepicker"
v-model="timeframe"
data-test-id="analytics-timepicker"
class="analytics-timepicker"
:min-date="minDateCalendar"
:max-date="new Date()"
:mode="hideCalendar ? 'relative': 'date'"
:placeholder="helpText.analytics.selectDateRange"
:time-periods="timePeriods"
:range="true"
width="100%"
@change="changeTimeframe"
<EmptyState
v-else-if="currentState.matches('error')"
is-error
:message="errorMessage"
/>
<section
v-if="currentState.matches('success')"
>
<PageTitle
class="mb-5"
:title="helpText.analytics.dashboard"
/>
<div v-if="hasProductVersions">
<div
class="analytics-filters d-flex flex-grow-1 justify-content-between align-items-baseline mb-6"
>
<KMultiselect
v-model="versionMultiSelectModel"
autosuggest
collapsed-context
data-testid="analytics-service-filter"
class="analytics-service-filter flex-grow-1"
:dropdown-footer-text="multiselectFooter"
dropdown-footer-text-position="static"
:items="multiselectItems"
:label="helpText.analytics.filterLabelProductVersions"
:loading="filterMultiselectLoading"
@change="handleChangedItem"
@query-change="handleProductVersionSearch"
@selected="handleProductVersionSelection"
/>
<div>
<KLabel for="dateTimePicker">
{{ helpText.analytics.timeRange }}
</KLabel>
<KDateTimePicker
id="analytics-timepicker"
v-model="timeframe"
data-test-id="analytics-timepicker"
class="analytics-timepicker"
:min-date="minDateCalendar"
:max-date="new Date()"
:mode="hideCalendar ? 'relative': 'date'"
:placeholder="helpText.analytics.selectDateRange"
:time-periods="timePeriods"
:range="true"
width="100%"
@change="changeTimeframe"
/>
</div>
</div>
<div class="mb-6">
<h2 class="font-normal type-lg mb-4">
{{ helpText.analytics.summary }}
</h2>
<AnalyticsMetricsCard
v-if="!vitalsLoading"
class="mb-6"
data-testid="analytics-metric-cards"
:application-id="(appId as string)"
:timeframe="(selectedTimeframe as Timeframe)"
:product-version-ids="selectedProductVersionIds"
/>
<h2 class="font-normal type-lg mb-4">
{{ helpText.analytics.chartOverview }}
</h2>
<ChartPanel
v-model="chartFilters"
:app-id="(appId as string)"
/>
</div>
</div>
<div class="mb-6">
<h2 class="font-normal type-lg mb-4">
{{ helpText.analytics.summary }}
</h2>
<AnalyticsMetricsCard
v-if="!vitalsLoading"
class="mb-6"
data-testid="analytics-metric-cards"
:application-id="(appId as string)"
:timeframe="(selectedTimeframe as Timeframe)"
:product-version-ids="selectedProductVersionIds"
/>
<h2 class="font-normal type-lg mb-4">
{{ helpText.analytics.chartOverview }}
</h2>
<ChartPanel
v-model="chartFilters"
:app-id="(appId as string)"
/>
</div>
</div>
<AnalyticsEmptyState
v-else-if="!filterMultiselectLoading"
icon="stateNoData"
icon-size="96"
:title="helpText.analytics.selectProductVersions"
:message="helpText.analytics.selectProductVersions"
>
<template #message>
<p class="mb-4">
{{ helpText.productVersion.noProductVersionsDetail }}
</p>
<KButton
appearance="primary"
:is-rounded="false"
data-testid="copy-btn"
icon="plus"
:to="{ name: 'catalog' }"
>
{{ helpText.productVersion.registerProductVersion }}
</KButton>
</template>
</AnalyticsEmptyState>
</section>
</Content>
<AnalyticsEmptyState
v-else-if="!filterMultiselectLoading"
icon="stateNoData"
icon-size="96"
:title="helpText.analytics.selectProductVersions"
:message="helpText.analytics.selectProductVersions"
>
<template #message>
<p class="mb-4">
{{ helpText.productVersion.noProductVersionsDetail }}
</p>
<KButton
appearance="primary"
:is-rounded="false"
data-testid="copy-btn"
icon="plus"
:to="{ name: 'catalog' }"
>
{{ helpText.productVersion.registerProductVersion }}
</KButton>
</template>
</AnalyticsEmptyState>
</section>
</Content>
<template #fallback>
<Forbidden />
</template>
</AnalyticsConfigCheck>
</template>

<script setup lang="ts">
Expand Down Expand Up @@ -128,6 +133,8 @@ import PageTitle from '@/components/PageTitle.vue'
import { useAppStore, useI18nStore } from '@/stores'
import cloneDeep from 'lodash.clonedeep'
import { PortalTimeframeKeys } from '@/types/vitals'
import { AnalyticsConfigCheck } from '@kong-ui-public/analytics-config-store'
import Forbidden from '@/views/Forbidden.vue'
const { notify } = useToaster()
const errorMessage = ref('')
Expand Down
Loading

0 comments on commit ef97e5f

Please sign in to comment.