Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ export interface AiDatasourceQuery {

export type DatasourceAwareQuery = BasicDatasourceQuery | AdvancedDatasourceQuery | AiDatasourceQuery

// All flags in this interface should be optional; defaults are as documented.
export interface StaticConfig {
increaseCsvExportLimit?: boolean // default: true
}

export interface AnalyticsBridge {
// Issue queries to the KAnalytics API
queryFn: (query: DatasourceAwareQuery, abortController: AbortController) => Promise<ExploreResultV4>
Expand All @@ -29,6 +34,10 @@ export interface AnalyticsBridge {
// Evaluate feature flags (if applicable)
evaluateFeatureFlagFn: <T = boolean>(key: string, defaultValue: T) => T

// Static config flags that may vary by environment
// Optional; the environment generally shouldn't have to set this config.
staticConfig?: StaticConfig

// Define the location of explore to enable jump-to-explore.
// Async because there might need to be permissions checks.
exploreBaseUrl?: () => Promise<string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ describe('<DashboardRenderer />', () => {
const { query } = dsAwareQuery as AdvancedDatasourceQuery

// Dimensions to use if query is not provided
const dimensionMap = { statusCode: ['1XX', '2XX', '3XX', '4XX', '5XX'] }
const dimensionMap = { status_code: ['1XX', '2XX', '3XX', '4XX', '5XX'] }

if (query.dimensions && query.dimensions.findIndex(d => d === 'route') > -1) {
return Promise.resolve(routeExploreResponse)
Expand All @@ -66,7 +66,7 @@ describe('<DashboardRenderer />', () => {
// Timeseries Line chart
const timeSeriesResponse = generateSingleMetricTimeSeriesData(
{ name: 'TotalRequests', unit: 'count' },
{ statusCode: query.metrics as string[] },
{ status_code: query.metrics as string[] },
) as ExploreResultV4

return Promise.resolve(timeSeriesResponse)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ describe('<DashboardTile />', () => {
return Promise.resolve(
generateSingleMetricTimeSeriesData(
{ name: 'TotalRequests', unit: 'count' },
{ statusCode: ['request_count'] as string[] },
{ status_code: ['request_count'] as string[] },
{ start_ms: start, end_ms: end },
) as ExploreResultV4,
)
Expand Down Expand Up @@ -500,7 +500,7 @@ describe('<DashboardTile />', () => {
return Promise.resolve(
generateSingleMetricTimeSeriesData(
{ name: 'TotalRequests', unit: 'count' },
{ statusCode: ['request_count'] as string[] },
{ status_code: ['request_count'] as string[] },
{ start_ms: start, end_ms: end },
) as ExploreResultV4,
)
Expand Down Expand Up @@ -531,5 +531,89 @@ describe('<DashboardTile />', () => {
expect(payload.query).to.have.property('limit', EXPORT_RECORD_LIMIT)
})
})

it("doesn't call queryFn with increased limit when not allowed", () => {
const queryFn = cy.stub().as('queryFn').callsFake(() => {
return Promise.resolve(
generateSingleMetricTimeSeriesData(
{ name: 'TotalRequests', unit: 'count' },
{ status_code: ['request_count'] as string[] },
{ start_ms: start, end_ms: end },
) as ExploreResultV4,
)
})

cy.mount(DashboardTile, {
props: {
definition: mockTileDefinition,
context: mockContext,
queryReady: true,
refreshCounter: 0,
tileId: 1,
},
global: {
provide: {
[INJECT_QUERY_PROVIDER]: { ...mockQueryProvider, queryFn, staticConfig: { increaseCsvExportLimit: false } },
},
},
})

cy.getTestId('kebab-action-menu-1').click()
cy.getTestId('chart-csv-export-1').click()
cy.getTestId('csv-export-modal').find('.vitals-table').should('exist')

cy.get('@queryFn').should('have.been.calledOnce')
.then((stub) => {
const payload = stub.getCall(0).args[0]

expect(payload).to.have.property('datasource', 'api_usage')
expect(payload).to.have.property('query')
expect(payload.query.limit).to.equal(undefined)
})
})

it('handles deferred loading', () => {
const queryFn = cy.stub().as('queryFn').callsFake(() => {
return Promise.resolve(
generateSingleMetricTimeSeriesData(
{ name: 'TotalRequests', unit: 'count' },
{ status_code: ['request_count'] as string[] },
{ start_ms: start, end_ms: end },
) as ExploreResultV4,
)
})

cy.mount(DashboardTile, {
props: {
definition: mockTileDefinition,
context: mockContext,
queryReady: false,
refreshCounter: 0,
tileId: 1,
},
global: {
provide: {
[INJECT_QUERY_PROVIDER]: { ...mockQueryProvider, queryFn, staticConfig: { increaseCsvExportLimit: false } },
},
},
}).then(({ wrapper }) => {
cy.getTestId('kebab-action-menu-1').click()
cy.getTestId('chart-csv-export-1').click()
cy.getTestId('csv-export-modal').should('exist')
cy.getTestId('csv-export-modal').find('.vitals-table').should('not.exist')
cy.getTestId('csv-export-modal').find('.chart-skeleton').should('exist')

// Why is the 'then' needed here? I don't know, but if it's not here, Cypress barges on and
// sets props on the wrapper before it finishes its assertions.
cy.get('@queryFn').should('not.have.been.called').then(() => {
// Update the queryReady prop
wrapper.setProps({ queryReady: true }).then(() => {
cy.getTestId('csv-export-modal').find('.vitals-table').should('exist')

cy.get('@queryFn').should('have.been.calledOnce')
})
})
})
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -374,11 +374,25 @@ const exportCsv = async () => {
exportModalVisible.value = true
exportState.value = { status: 'loading' }

issueQuery(props.definition.query, props.context, EXPORT_RECORD_LIMIT).then(queryResult => {
exportState.value = { status: 'success', chartData: queryResult }
}).catch(error => {
exportState.value = { status: 'error', error: error }
})
// If we're allowed to increase the CSV export limit, issue a new query with an expanded limit.
if (queryBridge?.staticConfig?.increaseCsvExportLimit ?? true) {
issueQuery(props.definition.query, props.context, EXPORT_RECORD_LIMIT).then(queryResult => {
exportState.value = { status: 'success', chartData: queryResult }
}).catch(error => {
exportState.value = { status: 'error', error: error }
})
} else if (chartData.value) {
// If we're not allowed to increase the limit, and results are available, use them.
exportState.value = { status: 'success', chartData: chartData.value }
} else {
// If results aren't available, wait until they are.
const stop = watch(chartData, (newValue) => {
if (newValue) {
exportState.value = { status: 'success', chartData: newValue }
stop()
}
})
}
}

const onZoom = (newTimeRange: AbsoluteTimeRangeV4) => {
Expand Down
Loading