diff --git a/packages/analytics/analytics-chart/sandbox/pages/TimeSeriesChartDemo.vue b/packages/analytics/analytics-chart/sandbox/pages/TimeSeriesChartDemo.vue index e50e982a9b..5bb3b3da73 100644 --- a/packages/analytics/analytics-chart/sandbox/pages/TimeSeriesChartDemo.vue +++ b/packages/analytics/analytics-chart/sandbox/pages/TimeSeriesChartDemo.vue @@ -204,6 +204,7 @@ :chart-data="(exploreResult)" :chart-options="analyticsChartOptions" chart-title="Request count by Status Code" + :go-to-explore="(exploreLink)" :legend-position="legendPosition" :show-annotations="showAnnotationsToggle" :show-legend-values="showLegendValuesToggle" @@ -330,6 +331,8 @@ const exportCsv = () => { setModalVisibility(true) } +const exploreLink: string = 'https://cloud.konghq.tech/us/analytics/explorer' + const exploreResultText = ref('') const hasError = computed(() => !isValidJson(exploreResultText.value)) const isValid = computed(() => exploreResultText.value !== undefined && diff --git a/packages/analytics/analytics-chart/src/components/AnalyticsChart.cy.ts b/packages/analytics/analytics-chart/src/components/AnalyticsChart.cy.ts index 10e94a9cba..fdb0f693d2 100644 --- a/packages/analytics/analytics-chart/src/components/AnalyticsChart.cy.ts +++ b/packages/analytics/analytics-chart/src/components/AnalyticsChart.cy.ts @@ -22,6 +22,9 @@ function mouseMove(x1: number, y1: number, x2: number, y2: number, duration: num describe('', () => { beforeEach(() => { cy.viewport(1280, 800) + cy.stub(composables, 'useEvaluateFeatureFlag').returns({ + evaluateFeatureFlag: () => true, + }) }) it('Renders a line chart for total requests count with status code dimension', () => { @@ -251,13 +254,37 @@ describe('', () => { chartTitle: 'Requests', }, }) - cy.getTestId('csv-export-button').should('not.exist') + + cy.getTestId('chart-action-menu').should('not.exist') }) - it('does not render an "Export" button if chart data is present but prop is set to `false`', () => { + it('renders the kebab menu with only the "Jump to Explore" link if no data is provided', () => { + cy.mount(AnalyticsChart, { + props: { + allowCsvExport: true, + goToExplore: 'https://cloud.konghq.tech/us/analytics/explorer', + chartData: emptyExploreResult, + chartOptions: { + type: 'timeseries_line', + }, + chartTitle: 'Requests', + }, + }) + + cy.getTestId('chart-action-menu').should('exist') + + // eslint-disable-next-line cypress/unsafe-to-chain-command + cy.getTestId('chart-action-menu').click().then(() => { + cy.getTestId('chart-jump-to-explore').should('exist') + cy.getTestId('csv-export-button').should('not.exist') + }) + }) + + it('does not render an "Export" link in the kebab actions if chart data is present but prop is set to `false`', () => { cy.mount(AnalyticsChart, { props: { allowCsvExport: false, + goToExplore: 'https://cloud.konghq.tech/us/analytics/explorer', chartData: exploreResult, chartOptions: { type: 'timeseries_line', @@ -265,7 +292,14 @@ describe('', () => { chartTitle: 'Requests', }, }) - cy.getTestId('csv-export-button').should('not.exist') + + cy.getTestId('chart-action-menu').should('exist') + + // eslint-disable-next-line cypress/unsafe-to-chain-command + cy.getTestId('chart-action-menu').click().then(() => { + cy.getTestId('chart-jump-to-explore').should('exist') + cy.getTestId('csv-export-modal').should('not.exist') + }) }) it('Renders an "Export" button, and tabulated data in the modal preview', () => { @@ -280,10 +314,13 @@ describe('', () => { }, }) - cy.getTestId('csv-export-button').should('exist') + cy.getTestId('chart-action-menu').should('exist') // eslint-disable-next-line cypress/unsafe-to-chain-command - cy.getTestId('csv-export-button').click().then(() => { + cy.getTestId('chart-action-menu').click().then(() => { + cy.getTestId('chart-jump-to-explore').should('not.exist') + + cy.getTestId('csv-export-button').click() cy.getTestId('csv-export-modal').should('exist') cy.get('.modal-content .vitals-table').should('exist') }) diff --git a/packages/analytics/analytics-chart/src/components/AnalyticsChart.vue b/packages/analytics/analytics-chart/src/components/AnalyticsChart.vue index b1b3dba506..6cf40329ff 100644 --- a/packages/analytics/analytics-chart/src/components/AnalyticsChart.vue +++ b/packages/analytics/analytics-chart/src/components/AnalyticsChart.vue @@ -28,7 +28,7 @@
+ + + + + + + , required: true, @@ -190,6 +237,9 @@ const emit = defineEmits<{ }>() const { i18n } = composables.useI18n() +const { evaluateFeatureFlag } = composables.useEvaluateFeatureFlag() + +const hasKebabMenuAccess = evaluateFeatureFlag('ma-3043-analytics-chart-kebab-menu', false) const rawChartData = toRef(props, 'chartData') @@ -211,6 +261,15 @@ const computedChartData = computed(() => { ).value }) +const exportModalVisible = ref(false) +const setExportModalVisibility = (val: boolean) => { + exportModalVisible.value = val +} +const csvFilename = computed(() => props.filenamePrefix || i18n.t('csvExport.defaultFilename')) +const exportCsv = () => { + setExportModalVisibility(true) +} + const timeRangeMs = computed(() => { if (!props.chartData?.meta) { return 0 @@ -302,6 +361,8 @@ const showChartHeader = computed(() => { return (hasValidChartData.value && resultSetTruncated.value && maxEntitiesShown.value) || props.chartTitle || (props.allowCsvExport && hasValidChartData.value) }) +const hasMenuOptions = computed(() => (props.allowCsvExport && hasValidChartData.value) || !!props.goToExplore) + const timeSeriesGranularity = computed(() => { if (!props.chartData.meta.granularity_ms) { @@ -413,6 +474,31 @@ provide('legendPosition', toRef(props, 'legendPosition')) margin-left: var(--kui-space-50, $kui-space-50); margin-top: var(--kui-space-10, $kui-space-10); } + + // Action menu + .dropdown { + display: flex; + margin-left: var(--kui-space-auto, $kui-space-auto); + margin-right: var(--kui-space-0, $kui-space-0); + + li.k-dropdown-item { + a { + text-decoration: none; + } + } + a { + color: $kui-color-text; + + &:hover { + color: $kui-color-text; + text-decoration: none; + } + } + + &:hover { + cursor: pointer; + } + } } diff --git a/packages/analytics/analytics-chart/src/composables/index.ts b/packages/analytics/analytics-chart/src/composables/index.ts index bd0e80b2a8..49fa8d9331 100644 --- a/packages/analytics/analytics-chart/src/composables/index.ts +++ b/packages/analytics/analytics-chart/src/composables/index.ts @@ -9,6 +9,7 @@ import useExploreResultToDatasets from './useExploreResultToDatasets' import useExploreResultToTimeDataset from './useExploreResultToTimeDatasets' import useReportChartDataForSynthetics from './useReportChartDataForSynthetics' import useTranslatedUnits from './useTranslatedUnits' +import useEvaluateFeatureFlag from './useEvauluateFeatureFlag' // All composables must be exported as part of the default object for Cypress test stubs export default { @@ -23,4 +24,5 @@ export default { useI18n, useReportChartDataForSynthetics, useTranslatedUnits, + useEvaluateFeatureFlag, } diff --git a/packages/analytics/analytics-chart/src/composables/useEvauluateFeatureFlag.ts b/packages/analytics/analytics-chart/src/composables/useEvauluateFeatureFlag.ts new file mode 100644 index 0000000000..83c117f20f --- /dev/null +++ b/packages/analytics/analytics-chart/src/composables/useEvauluateFeatureFlag.ts @@ -0,0 +1,19 @@ +import type { AnalyticsBridge } from '@kong-ui-public/analytics-utilities' +import { inject } from 'vue' +import { INJECT_QUERY_PROVIDER } from '../constants' + +export default function useEvaluateFeatureFlag() { + + const queryBridge: AnalyticsBridge | undefined = inject(INJECT_QUERY_PROVIDER) + + const evaluateFeatureFlag = (key: string, defaultValue: boolean) => { + if (!queryBridge) { + return defaultValue + } + return queryBridge.evaluateFeatureFlagFn(key, defaultValue) + } + + return { + evaluateFeatureFlag, + } +} diff --git a/packages/analytics/analytics-chart/src/constants/index.ts b/packages/analytics/analytics-chart/src/constants/index.ts new file mode 100644 index 0000000000..3063d75fd6 --- /dev/null +++ b/packages/analytics/analytics-chart/src/constants/index.ts @@ -0,0 +1 @@ +export const INJECT_QUERY_PROVIDER = 'analytics-query-provider' diff --git a/packages/analytics/analytics-chart/src/locales/en.json b/packages/analytics/analytics-chart/src/locales/en.json index 24e4fa1fc6..2475cdf977 100644 --- a/packages/analytics/analytics-chart/src/locales/en.json +++ b/packages/analytics/analytics-chart/src/locales/en.json @@ -5,6 +5,7 @@ "entityNoName": "no-name", "debug": "debug", "total": "Total", + "jumpToExplore": "Jump to Explore", "chartUnits": { "ms": "ms", "bytes": "Byte{plural}", diff --git a/packages/analytics/analytics-chart/src/types/chart-data.ts b/packages/analytics/analytics-chart/src/types/chart-data.ts index b133f84fd9..6c6cec1199 100644 --- a/packages/analytics/analytics-chart/src/types/chart-data.ts +++ b/packages/analytics/analytics-chart/src/types/chart-data.ts @@ -39,6 +39,7 @@ export interface LegendValueEntry { */ export interface EnhancedLegendItem extends LegendItem { value: LegendValueEntry + text: string } /**