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 @@ -17,18 +17,81 @@
*/

import { render } from '@testing-library/react';
import { mount } from 'enzyme';
import React from 'react';

import { QueryManager } from '../../utils';
import { Capabilities } from '../../utils/capabilities';

import { SegmentTimeline } from './segment-timeline';

describe('Segment Timeline', () => {
it('matches snapshot', () => {
const tableColumn = (
const segmentTimeline = (
<SegmentTimeline capabilities={Capabilities.FULL} chartHeight={100} chartWidth={100} />
);
const { container } = render(tableColumn);
const { container } = render(segmentTimeline);
expect(container.firstChild).toMatchSnapshot();
});

it('queries 3 months of data by default', () => {
const dataQueryManager = new MockDataQueryManager();
const segmentTimeline = (
<SegmentTimeline
capabilities={Capabilities.FULL}
chartHeight={100}
chartWidth={100}
dataQueryManager={dataQueryManager}
/>
);
render(segmentTimeline);

// Ideally, the test should verify the rendered bar graph to see if the bars
// cover the selected period. Since the unit test does not have a druid
// instance to query from, just verify the query has the correct time span.
expect(dataQueryManager.queryTimeSpan).toBe(3);
});

it('queries matching time span when new period is selected from dropdown', () => {
const dataQueryManager = new MockDataQueryManager();
const segmentTimeline = (
<SegmentTimeline
capabilities={Capabilities.FULL}
chartHeight={100}
chartWidth={100}
dataQueryManager={dataQueryManager}
/>
);
const wrapper = mount(segmentTimeline);
const selects = wrapper.find('select');
expect(selects.length).toBe(2); // Datasource & Period
const periodSelect = selects.at(1);
const newTimeSpanMonths = 6;
periodSelect.simulate('change', { target: { value: newTimeSpanMonths } });

// Ideally, the test should verify the rendered bar graph to see if the bars
// cover the selected period. Since the unit test does not have a druid
// instance to query from, just verify the query has the correct time span.
expect(dataQueryManager.queryTimeSpan).toBe(newTimeSpanMonths);
});
});

/**
* Mock the data query manager, since the unit test does not have a druid instance
*/
class MockDataQueryManager extends QueryManager<
{ capabilities: Capabilities; timeSpan: number },
any
> {
queryTimeSpan?: number;

constructor() {
super({
processQuery: async ({ timeSpan }) => {
this.queryTimeSpan = timeSpan;
},
debounceIdle: 0,
debounceLoading: 0,
});
}
}
138 changes: 74 additions & 64 deletions web-console/src/components/segment-timeline/segment-timeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ interface SegmentTimelineProps {
capabilities: Capabilities;
chartHeight: number;
chartWidth: number;

// For testing:
dataQueryManager?: QueryManager<{ capabilities: Capabilities; timeSpan: number }, any>;
}

interface SegmentTimelineState {
Expand Down Expand Up @@ -81,6 +84,8 @@ interface IntervalRow {
size: number;
}

const DEFAULT_TIME_SPAN_MONTHS = 3;

export class SegmentTimeline extends React.PureComponent<
SegmentTimelineProps,
SegmentTimelineState
Expand Down Expand Up @@ -219,7 +224,7 @@ export class SegmentTimeline extends React.PureComponent<
super(props);
const dStart = new Date();
const dEnd = new Date();
dStart.setMonth(dStart.getMonth() - 3);
dStart.setMonth(dStart.getMonth() - DEFAULT_TIME_SPAN_MONTHS);
this.state = {
data: {},
datasources: [],
Expand All @@ -228,81 +233,84 @@ export class SegmentTimeline extends React.PureComponent<
dataToRender: [],
activeDatasource: null,
activeDataType: 'countData',
timeSpan: 3,
timeSpan: DEFAULT_TIME_SPAN_MONTHS,
loading: true,
xScale: null,
yScale: null,
dEnd: dEnd,
dStart: dStart,
};

this.dataQueryManager = new QueryManager({
processQuery: async ({ capabilities, timeSpan }) => {
let intervals: IntervalRow[];
let datasources: string[];
if (capabilities.hasSql()) {
const query = `SELECT
this.dataQueryManager =
props.dataQueryManager ||
new QueryManager({
processQuery: async ({ capabilities, timeSpan }) => {
let intervals: IntervalRow[];
let datasources: string[];
if (capabilities.hasSql()) {
const query = `
SELECT
"start", "end", "datasource",
COUNT(*) AS "count", SUM("size") as "size"
COUNT(*) AS "count", SUM("size") as "size"
FROM sys.segments
WHERE "start" > TIME_FORMAT(TIMESTAMPADD(MONTH, -${timeSpan}, CURRENT_TIMESTAMP), 'yyyy-MM-dd''T''hh:mm:ss.SSS')
GROUP BY 1, 2, 3
ORDER BY "start" DESC`;

intervals = await queryDruidSql({ query });
datasources = uniq(intervals.map(r => r.datasource));
} else if (capabilities.hasCoordinatorAccess()) {
const before = new Date();
before.setMonth(before.getMonth() - timeSpan);
const beforeIso = before.toISOString();

datasources = (await axios.get(`/druid/coordinator/v1/datasources`)).data;
intervals = (await Promise.all(
datasources.map(async datasource => {
const intervalMap = (await axios.get(
`/druid/coordinator/v1/datasources/${datasource}/intervals?simple`,
)).data;

return Object.keys(intervalMap)
.map(interval => {
const [start, end] = interval.split('/');
const { count, size } = intervalMap[interval];
return {
start,
end,
datasource,
count,
size,
};
})
.filter(a => beforeIso < a.start);
}),
))
.flat()
.sort((a, b) => b.start.localeCompare(a.start));
} else {
throw new Error(`must have SQL or coordinator access`);
}
intervals = await queryDruidSql({ query });
datasources = uniq(intervals.map(r => r.datasource));
} else if (capabilities.hasCoordinatorAccess()) {
const before = new Date();
before.setMonth(before.getMonth() - timeSpan);
const beforeIso = before.toISOString();

datasources = (await axios.get(`/druid/coordinator/v1/datasources`)).data;
intervals = (await Promise.all(
datasources.map(async datasource => {
const intervalMap = (await axios.get(
`/druid/coordinator/v1/datasources/${datasource}/intervals?simple`,
)).data;

return Object.keys(intervalMap)
.map(interval => {
const [start, end] = interval.split('/');
const { count, size } = intervalMap[interval];
return {
start,
end,
datasource,
count,
size,
};
})
.filter(a => beforeIso < a.start);
}),
))
.flat()
.sort((a, b) => b.start.localeCompare(a.start));
} else {
throw new Error(`must have SQL or coordinator access`);
}

const data = SegmentTimeline.processRawData(intervals);
const stackedData = SegmentTimeline.calculateStackedData(data, datasources);
const singleDatasourceData = SegmentTimeline.calculateSingleDatasourceData(
data,
datasources,
);
return { data, datasources, stackedData, singleDatasourceData };
},
onStateChange: ({ result, loading, error }) => {
this.setState({
data: result ? result.data : undefined,
datasources: result ? result.datasources : [],
stackedData: result ? result.stackedData : undefined,
singleDatasourceData: result ? result.singleDatasourceData : undefined,
loading,
error,
});
},
});
const data = SegmentTimeline.processRawData(intervals);
const stackedData = SegmentTimeline.calculateStackedData(data, datasources);
const singleDatasourceData = SegmentTimeline.calculateSingleDatasourceData(
data,
datasources,
);
return { data, datasources, stackedData, singleDatasourceData };
},
onStateChange: ({ result, loading, error }) => {
this.setState({
data: result ? result.data : undefined,
datasources: result ? result.datasources : [],
stackedData: result ? result.stackedData : undefined,
singleDatasourceData: result ? result.singleDatasourceData : undefined,
loading,
error,
});
},
});
}

componentDidMount(): void {
Expand Down Expand Up @@ -394,14 +402,16 @@ ORDER BY "start" DESC`;
onTimeSpanChange = (e: any) => {
const dStart = new Date();
const dEnd = new Date();
dStart.setMonth(dStart.getMonth() - e);
const capabilities = this.props.capabilities;
const timeSpan = parseInt(e, 10) || DEFAULT_TIME_SPAN_MONTHS;
dStart.setMonth(dStart.getMonth() - timeSpan);
this.setState({
timeSpan: e,
loading: true,
dStart,
dEnd,
});
this.dataQueryManager.rerunLastQuery();
this.dataQueryManager.runQuery({ capabilities, timeSpan });
};

formatTick = (n: number) => {
Expand Down