lodash#range TypeScript Examples
The following examples show how to use
lodash#range.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: usePollPredictions.ts From vvs-ui with GNU General Public License v3.0 | 6 votes |
usePollPredictions = () => {
const timer = useRef<NodeJS.Timeout>(null)
const dispatch = useAppDispatch()
const { account } = useWeb3React()
const currentEpoch = useGetCurrentEpoch()
const earliestEpoch = useGetEarliestEpoch()
const status = useGetPredictionsStatus()
useEffect(() => {
// Clear old timer
if (timer.current) {
clearInterval(timer.current)
}
if (status !== PredictionStatus.INITIAL) {
timer.current = setInterval(async () => {
const liveCurrentAndRecent = [currentEpoch, currentEpoch - 1, currentEpoch - 2]
dispatch(fetchRounds(liveCurrentAndRecent))
dispatch(fetchMarketData())
if (account) {
const epochRange = range(earliestEpoch, currentEpoch + 1)
dispatch(fetchLedgerData({ account, epochs: epochRange }))
dispatch(fetchClaimableStatuses({ account, epochs: epochRange }))
}
}, POLL_TIME_IN_SECONDS * 1000)
}
return () => {
if (timer.current) {
clearInterval(timer.current)
}
}
}, [timer, account, status, currentEpoch, earliestEpoch, dispatch])
}
Example #2
Source File: list-container.editor.tsx From next-basics with GNU General Public License v3.0 | 6 votes |
export function ListContainerEditor({
nodeUid,
}: EditorComponentProps): React.ReactElement {
const node = useBuilderNode<ListContainerProperties>({ nodeUid });
/**
* 提示:使用构件的属性配置来呈现该构件的关键 UI 特征。
* 例如:对于按钮构件,根据 `buttonType` 来显示对应的背景色。
*/
// const { someProps } = node.$$parsedProperties;
return (
<EditorContainer nodeUid={nodeUid}>
<div className={styles.listContainer}>
{range(0, 3).map((_, index) => (
<div key={index} className={styles.content}></div>
))}
</div>
</EditorContainer>
);
}
Example #3
Source File: LogCompression.test.ts From balancer-v2-monorepo with GNU General Public License v3.0 | 6 votes |
// 0.05%, or ~e^0.00005
describe('LogCompression', function () {
let mock: Contract;
before(async function () {
mock = await deploy('MockLogCompression');
});
function valuesInMagnitude(power: number) {
return [0.4, 1.2, 2.9, 3.3, 4.8, 5.3, 6.1, 7.4, 8.5, 9.4].map((x) => bn(x * 10 ** power));
}
function itRecoversOriginalValueWithError(minPower: number, maxPower: number, maxRelativeError: number) {
for (const power of range(minPower, maxPower)) {
it(`encodes and decodes powers of ${power}`, async () => {
for (const original of valuesInMagnitude(power)) {
const actual = await mock.fromLowResLog(await mock.toLowResLog(original));
expectEqualWithError(actual, original, maxRelativeError);
}
});
}
}
describe('small values', () => {
itRecoversOriginalValueWithError(1, 5, 0.1); // Smaller values have larger error due to a lack of resolution
});
describe('medium and large values', () => {
itRecoversOriginalValueWithError(5, 35, MAX_RELATIVE_ERROR);
});
});
Example #4
Source File: general-radio.editor.tsx From next-basics with GNU General Public License v3.0 | 6 votes |
export function GeneralRadioEditor({
nodeUid,
}: EditorComponentProps): React.ReactElement {
const node = useBuilderNode<GeneralRadioProperties>({ nodeUid });
const { label, type = "default", required } = node.$$parsedProperties;
return (
<EditorContainer nodeUid={nodeUid}>
<div className={formSharedStyle.formItemWrapper}>
<div
className={classNames(formSharedStyle.labelContainer, {
[formSharedStyle.requireMark]: required,
})}
>
<span
className={classNames({ [formSharedStyle.formLabel]: !!label })}
>
{formCommonFieldDisplay(label)}
</span>
</div>
<div className={styles.formRadioItem}>
{range(0, 3).map((optionIndex) => (
<span
className={classNames(
{ [styles.option]: type === "default" },
{ [styles.buttonOption]: type === "button" }
)}
key={optionIndex}
/>
))}
</div>
</div>
</EditorContainer>
);
}
Example #5
Source File: usePollPredictions.ts From glide-frontend with GNU General Public License v3.0 | 6 votes |
usePollPredictions = () => {
const timer = useRef<NodeJS.Timeout>(null)
const dispatch = useAppDispatch()
const { account } = useWeb3React()
const currentEpoch = useGetCurrentEpoch()
const earliestEpoch = useGetEarliestEpoch()
const status = useGetPredictionsStatus()
useEffect(() => {
// Clear old timer
if (timer.current) {
clearInterval(timer.current)
}
if (status === PredictionStatus.LIVE) {
timer.current = setInterval(async () => {
const liveAndCurrent = [currentEpoch, currentEpoch - 1]
dispatch(fetchRounds(liveAndCurrent))
dispatch(fetchMarketData())
if (account) {
const epochRange = range(earliestEpoch, currentEpoch + 1)
dispatch(fetchLedgerData({ account, epochs: epochRange }))
dispatch(fetchClaimableStatuses({ account, epochs: epochRange }))
}
}, POLL_TIME_IN_SECONDS * 1000)
}
return () => {
if (timer.current) {
clearInterval(timer.current)
}
}
}, [timer, account, status, currentEpoch, earliestEpoch, dispatch])
}
Example #6
Source File: GeneMapAxis.tsx From nextclade with MIT License | 6 votes |
export function GeneMapAxis() {
const genomeSize = useRecoilValue(genomeSizeAtom)
const geneMap = useRecoilValue(geneMapAtom)
const viewedGene = useRecoilValue(viewedGeneAtom)
const { ticks, domain } = useMemo(() => {
const length = getAxisLength(genomeSize, viewedGene, geneMap)
const tickSize = getTickSize(length)
const domain: [number, number] = [0, length]
const ticks = range(0, length, tickSize)
return { ticks, domain }
}, [geneMap, genomeSize, viewedGene])
return (
<ResponsiveContainer width="100%" height={30}>
<ComposedChart margin={MARGIN}>
<XAxis dataKey={'ticks'} type="number" ticks={ticks} domain={domain} axisLine={false} />
</ComposedChart>
</ResponsiveContainer>
)
}
Example #7
Source File: chain-limits.test.ts From airnode with MIT License | 6 votes |
createRequests = (apiCallsCount: number, withdrawalCount: number): GroupedRequests => {
// We want the generated requests to be sorted by the order of execution
let blockNumber = 12345;
return {
apiCalls: range(apiCallsCount).map(() => {
return fixtures.requests.buildApiCall({
metadata: fixtures.requests.buildMetadata({ blockNumber: blockNumber++ }),
});
}),
withdrawals: range(withdrawalCount).map(() => {
return fixtures.requests.buildApiCall({
metadata: fixtures.requests.buildMetadata({ blockNumber: blockNumber++ }),
});
}),
};
}
Example #8
Source File: range-selection.ts From S2 with MIT License | 6 votes |
private handleSeriesNumberRowSelected(
startIndex: number,
endIndex: number,
cell: S2CellType<ViewMeta>,
) {
// table模式下序列号行头
const cellIdSufFix = this.spreadsheet.facet.layoutResult.colLeafNodes[0].id;
return range(startIndex, endIndex + 1).map((row) => {
const cellIdPrefix = String(row);
return {
id: cellIdPrefix + '-' + cellIdSufFix,
colIndex: 0,
rowIndex: row,
type: cell.cellType,
};
});
}
Example #9
Source File: testUtils.ts From excalideck with MIT License | 5 votes |
export function makeRandomSlides(length = 10): Slide[] {
return range(0, length).map(makeRandomSlide);
}
Example #10
Source File: useBuilderNodeMountPoints.spec.tsx From next-core with GNU General Public License v3.0 | 5 votes |
// Given a tree:
// - 1:
// - toolbar:
// - 2
// - content:
// - 3
// - content
// - 5
// - <template-internal>
// - 6
// - items
// - 7
// - 4
(useBuilderData as jest.MockedFunction<typeof useBuilderData>).mockReturnValue({
rootId: 1,
nodes: range(1, 8).map(
($$uid) =>
({
$$uid,
} as Partial<BuilderRuntimeNode> as BuilderRuntimeNode)
),
edges: [
{
parent: 1,
child: 2,
mountPoint: "toolbar",
sort: 0,
},
{
parent: 1,
child: 3,
mountPoint: "content",
sort: 1,
},
{
parent: 1,
child: 4,
mountPoint: "content",
sort: 2,
},
{
parent: 3,
child: 5,
mountPoint: "content",
sort: 0,
},
{
parent: 5,
child: 6,
mountPoint: "<internal>",
sort: 0,
$$isTemplateInternal: true,
},
{
parent: 5,
child: 7,
mountPoint: "items",
sort: 1,
$$isTemplateDelegated: true,
},
],
});
Example #11
Source File: generate-auto-extractors.spec.ts From js-client with MIT License | 5 votes |
describe('generateAutoExtractors()', () => {
const generateAutoExtractors = makeGenerateAutoExtractors(TEST_BASE_API_CONTEXT);
// Use a randomly generated tag, so that we know exactly what we're going to query
const tag = uuidv4();
// The number of entries to generate
const count = 1000;
// The start date for generated queries
const start = new Date(2010, 0, 0);
// The end date for generated queries; one minute between each entry
const end = addMinutes(start, count - 1);
beforeAll(async () => {
// Generate and ingest some entries
const ingestJSONEntries = makeIngestJSONEntries(TEST_BASE_API_CONTEXT);
const values: Array<CreatableJSONEntry> = range(0, count).map(i => {
const timestamp = addMinutes(start, i).toISOString();
return {
timestamp,
tag,
data: JSON.stringify({ timestamp, value: i }, null, 2), // Add vertical whitespace, so that JSON is recommended over CSV
};
});
await ingestJSONEntries(values);
// Check the list of tags until our new tag appears
const getAllTags = makeGetAllTags(TEST_BASE_API_CONTEXT);
while (!(await getAllTags()).includes(tag)) {
// Give the backend a moment to catch up
await sleep(1000);
}
}, 25000);
it(
'Should generate auto extractors',
integrationTest(async () => {
const subscribeToOneSearch = makeSubscribeToOneSearch(TEST_BASE_API_CONTEXT);
const query = `tag=${tag} limit 10`;
const search = await subscribeToOneSearch(query, { filter: { dateRange: { start, end } } });
const entries = await lastValueFrom(
search.entries$.pipe(
map(e => e as RawSearchEntries),
takeWhile(e => !e.finished, true),
),
);
const exploreResults = await generateAutoExtractors({ tag, entries });
expect(Object.keys(exploreResults).length).withContext('we should get >0 AX suggestions').toBeGreaterThan(0);
expect(exploreResults['json']).withContext('we should have a JSON AX suggestion').toBeDefined();
expect(exploreResults['json'].length).withContext('we should have >0 JSON AX suggestions').toBeGreaterThan(0);
exploreResults['json'].forEach(ax => {
expect(ax.autoExtractor.tag).withContext('the suggested AX tag should match the provided tag').toEqual(tag);
expect(ax.autoExtractor.module).withContext('the suggested AX module should be json').toEqual('json');
expect(ax.confidence).withContext('json is the right module, so its confidence should be 10').toEqual(10);
expect(ax.autoExtractor.parameters)
.withContext('the suggested AX module should break out the fields in the entries')
.toEqual('timestamp value');
expect(ax.explorerEntries.length).withContext('explore should have >0 elements').toBeGreaterThan(0);
ax.explorerEntries.forEach(exploreEntry => {
expect(exploreEntry.elements.length)
.withContext('explore should have two elements: one for each field (timestamp and value)')
.toEqual(2);
exploreEntry.elements.forEach(elt => {
expect(elt.filters.length).withContext('we should have filters on explore elements').toBeGreaterThan(0);
});
});
});
// We can't really make assertions about what the other AX generators are going to do when we look at JSON data
}),
25000,
);
});
Example #12
Source File: general-card.editor.tsx From next-basics with GNU General Public License v3.0 | 5 votes |
export function GeneralCardEditor({
nodeUid,
editorProps = {},
}: EditorProperties): React.ReactElement {
const node = useBuilderNode<GeneralCardProperties>({ nodeUid });
const { editorStyle, hasHeader, slots, hasIcon } = editorProps;
const hasSlots = useMemo(() => !isEmpty(slots), [slots]);
const noSlotContent = useMemo(
() => (
<div className={styles.noSlots}>
{hasIcon ? (
<>
<div>
{range(0, 2).map((_, index) => (
<div key={index} className={styles.text}></div>
))}
</div>
<div className={styles.icon}></div>
</>
) : (
<div className={styles.content}></div>
)}
</div>
),
[hasIcon]
);
return (
<EditorContainer nodeUid={nodeUid}>
<div className={styles.cardContainer} style={editorStyle}>
{hasHeader && <div className={styles.header}></div>}
<div className={styles.body}>
{hasSlots
? slots.map((slot, index) => (
<SlotContainer
key={index}
nodeUid={nodeUid}
slotName={slot}
slotContainerStyle={{ marginBottom: 20 }}
></SlotContainer>
))
: noSlotContent}
</div>
</div>
</EditorContainer>
);
}
Example #13
Source File: DebugEvents.tsx From jitsu with MIT License | 5 votes |
DebugEvents = ({ handleClick }: Props) => {
const services = ApplicationServices.get()
const { data: eventsData, isLoading } = useLoaderAsObject(
async () =>
await services.backendApiClient.get(`/events/cache?project_id=${services.activeProject.id}&limit=10`, {
proxy: true,
})
)
const allEvents = useMemo(() => {
const events = eventsData?.events ?? []
if (events.length > 100) events.length = 100
return events
.map(event => ({
data: event,
time: moment(event.original._timestamp),
}))
.sort((e1: Event, e2: Event) => {
if (e1.time.isAfter(e2.time)) {
return -1
} else if (e2.time.isAfter(e1.time)) {
return 1
}
return 0
})
}, [eventsData?.events])
return (
<Card bordered={false} className={`${styles.events}`}>
{isLoading ? (
<List
dataSource={range(0, 25)}
renderItem={() => (
<Skeleton active title={false} paragraph={{ rows: 2, width: ["100%", "70%"] }} className="mb-2" />
)}
/>
) : (
<List
className={`h-full w-full overflow-y-auto overflow-x-hidden ${styles.withSmallScrollbar}`}
dataSource={allEvents}
renderItem={(item: any) => {
return (
<div
className={`flex flex-col items-stretch ${styles.eventItem}`}
onClick={handleClick(item?.data.original)}
>
<p className="truncate mb-0">{item?.time?.utc?.()?.format?.()}</p>
{item?.data?.original?.event_type ? (
<p className="truncate mb-0">{item?.data?.original?.event_type}</p>
) : (
""
)}
</div>
)
}}
/>
)}
</Card>
)
}
Example #14
Source File: max-by.spec.ts From s-libs with MIT License | 5 votes |
describe('maxBy()', () => {
it('can sort by any primitive', () => {
expect(maxBy([0, 1, -1], identity)).toBe(1);
expect(maxBy([false, true, false], identity)).toBe(true);
expect(maxBy(['b', 'c', 'a'], identity)).toBe('c');
});
//
// stolen from https://github.com/lodash/lodash
//
it('should provide correct iteratee arguments', () => {
const spy = jasmine.createSpy();
maxBy([1, 2, 3], spy);
expect(spy.calls.first().args).toEqual([1]);
});
it('should treat sparse arrays as dense', () => {
const array = [1];
array[2] = 3;
const spy = jasmine.createSpy().and.returnValue(true);
maxBy(array, spy);
expectCallsAndReset(spy, [1], [undefined], [3]);
});
it('should not iterate custom properties of arrays', () => {
const array = [1];
(array as any).a = 1;
const spy = jasmine.createSpy();
maxBy(array, spy);
expectCallsAndReset(spy, [1]);
});
it('should ignore changes to `length`', () => {
const array = [1];
const spy = jasmine.createSpy().and.callFake(() => {
array.push(2);
return true;
});
maxBy(array, spy);
expect(spy).toHaveBeenCalledTimes(1);
});
it('should work with extremely large arrays', () => {
expect(maxBy(range(0, 5e5), identity)).toBe(499999);
});
it('should work with an `iteratee`', () => {
expect(maxBy([1, 2, 3], (n) => -n)).toBe(1);
});
it('should work when `iteratee` returns +/-Infinity', () => {
const value = -Infinity;
const object = { a: value };
const actual = maxBy([object, { a: value }], (obj: { a: number }) => obj.a);
expect(actual).toBe(object);
});
});
Example #15
Source File: brick-table.editor.tsx From next-basics with GNU General Public License v3.0 | 5 votes |
export function BrickTableEditor({
nodeUid,
}: EditorComponentProps): React.ReactElement {
const node = useBuilderNode<BrickTableProps>({ nodeUid });
const {
columns: tableColumnsProps,
configProps = {},
} = node.$$parsedProperties;
const rows = 3;
const columns = tableColumnsProps?.length || 3;
return (
<EditorContainer nodeUid={nodeUid}>
<div className={styles.table}>
{range(0, rows).map((rowIndex) => (
<div
key={rowIndex}
className={`${styles.row} ${
rowIndex === 0 ? styles.head : styles.body
}`}
>
{configProps.rowSelection && <span className={styles.checkbox} />}
{range(0, columns).map((columnIndex) => {
const curColumn = tableColumnsProps?.[columnIndex];
const columnTitle = smartDisplayForEvaluableString(
curColumn?.title
);
const isUseBrick = curColumn?.headerBrick;
return (
<div key={columnIndex} className={styles.cell}>
{rowIndex === 0 && columnTitle && !isUseBrick ? (
<span>{columnTitle}</span>
) : (
<div className={styles.content}></div>
)}
</div>
);
})}
</div>
))}
</div>
</EditorContainer>
);
}
Example #16
Source File: WeightedPool.test.ts From balancer-v2-monorepo with GNU General Public License v3.0 | 5 votes |
describe('WeightedPool', function () {
let allTokens: TokenList;
const MAX_TOKENS = 20;
const POOL_SWAP_FEE_PERCENTAGE = fp(0.01);
const WEIGHTS = range(1000, 1000 + MAX_TOKENS); // These will be normalized to weights that are close to each other, but different
sharedBeforeEach('deploy tokens', async () => {
allTokens = await TokenList.create(MAX_TOKENS, { sorted: true, varyDecimals: true });
});
itPaysProtocolFeesFromInvariantGrowth(WeightedPoolType.WEIGHTED_POOL);
describe('weights and scaling factors', () => {
for (const numTokens of range(2, MAX_TOKENS + 1)) {
context(`with ${numTokens} tokens`, () => {
let pool: WeightedPool;
let tokens: TokenList;
sharedBeforeEach('deploy pool', async () => {
tokens = allTokens.subset(numTokens);
pool = await WeightedPool.create({
poolType: WeightedPoolType.WEIGHTED_POOL,
tokens,
weights: WEIGHTS.slice(0, numTokens),
swapFeePercentage: POOL_SWAP_FEE_PERCENTAGE,
});
});
it('sets token weights', async () => {
const normalizedWeights = await pool.getNormalizedWeights();
expect(normalizedWeights).to.deep.equal(pool.normalizedWeights);
});
it('sets scaling factors', async () => {
const poolScalingFactors = await pool.getScalingFactors();
const tokenScalingFactors = tokens.map((token) => fp(10 ** (18 - token.decimals)));
expect(poolScalingFactors).to.deep.equal(tokenScalingFactors);
});
});
}
});
});
Example #17
Source File: getFunctionStep.ts From next-basics with GNU General Public License v3.0 | 5 votes |
function getStageNodesAndEdges(
functionLinksEdges: GraphEdge[],
firstStepId: string,
rootId: string
): [
stageNodes: CollectNode[],
stageEdges: GraphEdge[],
stepDescendantsMap: Map<string, Set<string>>
] {
const stepLevelMap = new Map<string, number>();
let maxLevel = 0;
const stepDescendantsMap = new Map<string, Set<string>>();
const walk = (
id: string,
level: number,
descendantsCarry?: Set<Set<string>>
): void => {
if (level > (stepLevelMap.get(id) ?? -1)) {
// Take every step's max level.
stepLevelMap.set(id, level);
}
if (level > maxLevel) {
maxLevel = level;
}
let selfDesc = stepDescendantsMap.get(id);
if (!selfDesc) {
selfDesc = new Set();
stepDescendantsMap.set(id, selfDesc);
}
const newDescendantsCarry = new Set(descendantsCarry);
newDescendantsCarry.add(selfDesc);
for (const edge of functionLinksEdges) {
if (edge.source === id) {
for (const desc of newDescendantsCarry) {
desc.add(edge.target);
}
walk(edge.target, level + 1, newDescendantsCarry);
}
}
};
walk(firstStepId, 0, new Set());
const stageNodes: CollectNode[] = range(0, maxLevel + 1).map(
(level) =>
({
type: "stage",
id: `stage.${level}`,
} as CollectNode)
);
const stageEdges: GraphEdge[] = stageNodes.map((node) => ({
source: rootId,
target: node.id,
type: "layer",
}));
for (const [id, level] of stepLevelMap.entries()) {
stageEdges.push({
source: `stage.${level}`,
target: id,
type: "stage",
});
}
return [stageNodes, stageEdges, stepDescendantsMap];
}
Example #18
Source File: range-selection.ts From S2 with MIT License | 5 votes |
private bindDataCellClick() {
this.spreadsheet.on(S2Event.DATA_CELL_CLICK, (event: Event) => {
event.stopPropagation();
const cell: DataCell = this.spreadsheet.getCell(event.target);
const meta = cell.getMeta();
const { interaction } = this.spreadsheet;
if (!meta) {
return;
}
const lastClickedCell = this.spreadsheet.store.get('lastClickedCell');
const isShiftSelect =
this.isRangeSelection && lastClickedCell?.cellType === cell.cellType;
if (!isShiftSelect) {
this.spreadsheet.store.set('lastClickedCell', cell);
return;
}
const { start, end } = getRangeIndex(
lastClickedCell.getMeta() as ViewMeta,
cell.getMeta(),
);
const cells = range(start.colIndex, end.colIndex + 1).flatMap((col) => {
const cellIdSuffix =
this.spreadsheet.facet.layoutResult.colLeafNodes[col].id;
return range(start.rowIndex, end.rowIndex + 1).map((row) => {
const cellIdPrefix =
this.spreadsheet.facet.getSeriesNumberWidth() ||
this.spreadsheet.isTableMode()
? String(row)
: this.spreadsheet.facet.layoutResult.rowLeafNodes[row].id;
return {
id: cellIdPrefix + '-' + cellIdSuffix,
colIndex: col,
rowIndex: row,
type: cell.cellType,
};
});
});
interaction.addIntercepts([InterceptType.CLICK, InterceptType.HOVER]);
interaction.changeState({
cells,
stateName: InteractionStateName.SELECTED,
});
this.spreadsheet.showTooltipWithInfo(
event,
getActiveCellsTooltipData(this.spreadsheet),
);
this.spreadsheet.emit(
S2Event.GLOBAL_SELECTED,
interaction.getActiveCells(),
);
});
}
Example #19
Source File: LunrSearchEngineIndexer.test.ts From backstage with Apache License 2.0 | 5 votes |
describe('LunrSearchEngineIndexer', () => {
let indexer: LunrSearchEngineIndexer;
beforeEach(() => {
jest.clearAllMocks();
indexer = new LunrSearchEngineIndexer();
});
it('should index documents', async () => {
const documents = [
{
title: 'testTerm',
text: 'testText',
location: 'test/location',
},
];
await TestPipeline.withSubject(indexer).withDocuments(documents).execute();
expect(lunrBuilderAddSpy).toHaveBeenCalledWith(documents[0]);
});
it('should index documents in bulk', async () => {
const documents = range(350).map(i => ({
title: `Hello World ${i}`,
text: 'Lorem Ipsum',
location: `location-${i}`,
}));
await TestPipeline.withSubject(indexer).withDocuments(documents).execute();
expect(lunrBuilderAddSpy).toHaveBeenCalledTimes(350);
});
it('should initialize schema', async () => {
const documents = [
{
title: 'testTerm',
text: 'testText',
location: 'test/location',
extra: 'field',
},
];
await TestPipeline.withSubject(indexer).withDocuments(documents).execute();
// Builder ref should be set to location (and only once).
expect(lunrBuilderRefSpy).toHaveBeenCalledTimes(1);
expect(lunrBuilderRefSpy).toHaveBeenLastCalledWith('location');
// Builder fields should be based on document fields.
expect(lunrBuilderFieldSpy).toHaveBeenCalledTimes(4);
expect(lunrBuilderFieldSpy).toHaveBeenCalledWith('title');
expect(lunrBuilderFieldSpy).toHaveBeenCalledWith('text');
expect(lunrBuilderFieldSpy).toHaveBeenCalledWith('location');
expect(lunrBuilderFieldSpy).toHaveBeenCalledWith('extra');
});
it('should configure lunr pipeline', async () => {
expect(lunrBuilderSearchPipelineAddSpy).toHaveBeenLastCalledWith(
lunr.stemmer,
);
expect(lunrBuilderPipelineAddSpy).toHaveBeenCalledWith(
...[lunr.trimmer, lunr.stopWordFilter, lunr.stemmer],
);
});
});
Example #20
Source File: actions.test.ts From airnode with MIT License | 5 votes |
describe('processRequests', () => {
test.each(['legacy', 'eip1559'] as const)('processes requests for each EVM provider - txType: %s', async (txType) => {
const { blockSpy, gasPriceSpy } = createAndMockGasTarget(txType);
estimateGasWithdrawalMock.mockResolvedValueOnce(ethers.BigNumber.from(50_000));
staticFulfillMock.mockResolvedValue({ callSuccess: true });
fulfillMock.mockResolvedValue({
hash: '0xad33fe94de7294c6ab461325828276185dff6fed92c54b15ac039c6160d2bac3',
});
const sponsorAddress = '0x641eeb15B15d8E2CFB5f9d6480B175d93c14e6B6';
const apiCall = fixtures.requests.buildSuccessfulApiCall({
id: '0x67caaa2862cf971502d5c5b3d94d09d15c770f3313e76aa95c296b6587e7e5f1',
sponsorAddress,
});
const requests: GroupedRequests = { apiCalls: [apiCall], withdrawals: [] };
const transactionCountsBySponsorAddress = { [sponsorAddress]: 5 };
const allProviders = range(2)
.map(() => fixtures.buildEVMProviderSponsorState({ requests, transactionCountsBySponsorAddress, sponsorAddress }))
.map((initialState) => ({
...initialState,
settings: {
...initialState.settings,
chainOptions: { txType, fulfillmentGasLimit: 500_000 },
},
}));
const workerOpts = fixtures.buildWorkerOptions();
const [logs, res] = await providers.processRequests(allProviders, workerOpts);
expect(logs).toEqual([]);
expect(txType === 'legacy' ? blockSpy : gasPriceSpy).not.toHaveBeenCalled();
expect(txType === 'eip1559' ? blockSpy : gasPriceSpy).toHaveBeenCalled();
expect(res.evm.map((evm) => evm.requests.apiCalls[0])).toEqual(
range(allProviders.length).map(() => ({
...apiCall,
fulfillment: { hash: '0xad33fe94de7294c6ab461325828276185dff6fed92c54b15ac039c6160d2bac3' },
nonce: 5,
}))
);
});
});
Example #21
Source File: TimeGraph.tsx From office-hours with GNU General Public License v3.0 | 4 votes |
export default function TimeGraph({
values,
maxTime,
firstHour,
lastHour,
width,
height,
}: {
values: number[];
maxTime: number;
firstHour: number;
lastHour: number;
width: number;
height: number;
}): ReactElement {
const {
tooltipOpen,
tooltipLeft,
tooltipTop,
tooltipData,
hideTooltip,
showTooltip,
} = useTooltip<number>();
const { containerRef, TooltipInPortal } = useTooltipInPortal();
// bounds
const xMax = width - RIGHT_MARGIN - LEFT_MARGIN;
const yMax = height - TOP_MARGIN - BOTTOM_MARGIN;
// scales, memoize for performance
const xScale = useMemo(
() =>
scaleBand<number>({
range: [0, xMax],
round: true,
domain: range(Math.max(0, firstHour), Math.min(lastHour + 1, 24) + 1),
padding: BAR_PADDING,
}),
[xMax, firstHour, lastHour]
);
// number of minutes between each grid row line
const gridRowInterval = maxTime >= 60 ? 60 : 30;
const maxTickVal = Math.max(maxTime, gridRowInterval);
const yScale = useMemo(
() =>
scaleLinear<number>({
range: [yMax, 0],
round: true,
domain: [0, maxTickVal + 5],
}),
[yMax, maxTickVal]
);
const barWidth = xScale.bandwidth();
// the tick values for the y axis
const yAxisTickValues = range(
gridRowInterval,
maxTickVal + 1,
gridRowInterval
);
return width < 10 ? null : (
// relative position is needed for correct tooltip positioning
<GraphContainer>
<svg ref={containerRef} width={width} height={height}>
<rect
x={0}
y={0}
width={width}
height={height}
fill="rgba(0,0,0,0)"
rx={14}
/>
<GridRows
top={TOP_MARGIN}
left={LEFT_MARGIN}
width={width - RIGHT_MARGIN - LEFT_MARGIN}
scale={yScale}
tickValues={yAxisTickValues}
stroke="#cccccc"
/>
<Group left={LEFT_MARGIN} top={TOP_MARGIN}>
{values.map((value, i) => {
const barHeight = yMax - yScale(value);
const barX = xScale(i) + (barWidth * (1 + BAR_PADDING)) / 2;
const barY = yMax - barHeight;
const interactWithBar = () => {
if (tooltipTimeout) clearTimeout(tooltipTimeout);
const top = yMax - barHeight - TOP_MARGIN; // - VERTICAL_MARGIN - barHeight;
const left = barX + barWidth;
showTooltip({
tooltipData: value,
tooltipTop: top,
tooltipLeft: left,
});
};
return (
<BarRounded
key={`bar-${formatDateHour(i)}`}
className="popularTimes__bar"
x={barX}
y={barY}
width={barWidth}
height={barHeight}
radius={10}
top
fill="#40a9ff"
onMouseLeave={() => {
tooltipTimeout = window.setTimeout(() => {
hideTooltip();
}, 300);
}}
onMouseOver={interactWithBar}
onMouseDown={interactWithBar}
/>
);
})}
</Group>
<Group left={LEFT_MARGIN}>
<AxisBottom
top={yMax + TOP_MARGIN}
scale={xScale}
tickFormat={(hour: number) =>
(hour - firstHour) % 3 == 0 ? formatDateHour(hour) : ""
}
tickLabelProps={() => ({
fill: "",
fontSize: 11,
textAnchor: "middle",
})}
/>
</Group>
<Group top={TOP_MARGIN} left={LEFT_MARGIN}>
<AxisLeft
scale={yScale}
hideTicks={true}
tickValues={yAxisTickValues}
tickFormat={(hour: number) => formatWaitTime(hour)}
tickLabelProps={() => ({
fill: "",
fontSize: 11,
textAnchor: "end",
})}
/>
</Group>
</svg>
{tooltipOpen && tooltipData && (
<TooltipInPortal
key={Math.random()} // update tooltip bounds each render
top={tooltipTop}
left={tooltipLeft}
style={tooltipStyles}
>
{formatWaitTime(tooltipData)}
</TooltipInPortal>
)}
</GraphContainer>
);
}
Example #22
Source File: ExperimentList.test.tsx From kfp-tekton-backend with Apache License 2.0 | 4 votes |
describe('ExperimentList', () => {
let tree: ShallowWrapper | ReactWrapper;
jest.spyOn(console, 'log').mockImplementation(() => null);
const updateBannerSpy = jest.fn();
const updateDialogSpy = jest.fn();
const updateSnackbarSpy = jest.fn();
const updateToolbarSpy = jest.fn();
const historyPushSpy = jest.fn();
const listExperimentsSpy = jest.spyOn(Apis.experimentServiceApi, 'listExperiment');
const listRunsSpy = jest.spyOn(Apis.runServiceApi, 'listRuns');
// We mock this because it uses toLocaleDateString, which causes mismatches between local and CI
// test enviroments
jest.spyOn(Utils, 'formatDateString').mockImplementation(() => '1/2/2019, 12:34:56 PM');
function generateProps(): PageProps {
return TestUtils.generatePageProps(
ExperimentList,
{ pathname: RoutePage.EXPERIMENTS } as any,
'' as any,
historyPushSpy,
updateBannerSpy,
updateDialogSpy,
updateToolbarSpy,
updateSnackbarSpy,
);
}
function mockListNExpperiments(n: number = 1) {
return () =>
Promise.resolve({
experiments: range(n).map(i => ({
id: 'test-experiment-id' + i,
name: 'test experiment name' + i,
})),
});
}
async function mountWithNExperiments(
n: number,
nRuns: number,
{ namespace }: { namespace?: string } = {},
): Promise<void> {
listExperimentsSpy.mockImplementation(mockListNExpperiments(n));
listRunsSpy.mockImplementation(() => ({
runs: range(nRuns).map(i => ({ id: 'test-run-id' + i, name: 'test run name' + i })),
}));
tree = TestUtils.mountWithRouter(<ExperimentList {...generateProps()} namespace={namespace} />);
await listExperimentsSpy;
await listRunsSpy;
await TestUtils.flushPromises();
tree.update(); // Make sure the tree is updated before returning it
}
afterEach(() => {
jest.resetAllMocks();
jest.clearAllMocks();
if (tree.exists()) {
tree.unmount();
}
});
it('renders an empty list with empty state message', () => {
tree = shallow(<ExperimentList {...generateProps()} />);
expect(tree).toMatchSnapshot();
});
it('renders a list of one experiment', async () => {
tree = shallow(<ExperimentList {...generateProps()} />);
tree.setState({
displayExperiments: [
{
description: 'test experiment description',
expandState: ExpandState.COLLAPSED,
name: 'test experiment name',
},
],
});
await listExperimentsSpy;
await listRunsSpy;
expect(tree).toMatchSnapshot();
});
it('renders a list of one experiment with no description', async () => {
tree = shallow(<ExperimentList {...generateProps()} />);
tree.setState({
experiments: [
{
expandState: ExpandState.COLLAPSED,
name: 'test experiment name',
},
],
});
await listExperimentsSpy;
await listRunsSpy;
expect(tree).toMatchSnapshot();
});
it('renders a list of one experiment with error', async () => {
tree = shallow(<ExperimentList {...generateProps()} />);
tree.setState({
experiments: [
{
description: 'test experiment description',
error: 'oops! could not load experiment',
expandState: ExpandState.COLLAPSED,
name: 'test experiment name',
},
],
});
await listExperimentsSpy;
await listRunsSpy;
expect(tree).toMatchSnapshot();
});
it('calls Apis to list experiments, sorted by creation time in descending order', async () => {
await mountWithNExperiments(1, 1);
expect(listExperimentsSpy).toHaveBeenLastCalledWith(...LIST_EXPERIMENT_DEFAULTS);
expect(listRunsSpy).toHaveBeenLastCalledWith(
undefined,
5,
'created_at desc',
'EXPERIMENT',
'test-experiment-id0',
encodeURIComponent(
JSON.stringify({
predicates: [
{
key: 'storage_state',
op: PredicateOp.NOTEQUALS,
string_value: RunStorageState.ARCHIVED.toString(),
},
],
} as ApiFilter),
),
);
expect(tree.state()).toHaveProperty('displayExperiments', [
{
expandState: ExpandState.COLLAPSED,
id: 'test-experiment-id0',
last5Runs: [{ id: 'test-run-id0', name: 'test run name0' }],
name: 'test experiment name0',
},
]);
});
it('calls Apis to list experiments with namespace when available', async () => {
await mountWithNExperiments(1, 1, { namespace: 'test-ns' });
expect(listExperimentsSpy).toHaveBeenLastCalledWith(
...LIST_EXPERIMENT_DEFAULTS_WITHOUT_RESOURCE_REFERENCE,
'NAMESPACE',
'test-ns',
);
});
it('has a Refresh button, clicking it refreshes the experiment list', async () => {
await mountWithNExperiments(1, 1);
const instance = tree.instance() as ExperimentList;
expect(listExperimentsSpy.mock.calls.length).toBe(1);
const refreshBtn = instance.getInitialToolbarState().actions[ButtonKeys.REFRESH];
expect(refreshBtn).toBeDefined();
await refreshBtn!.action();
expect(listExperimentsSpy.mock.calls.length).toBe(2);
expect(listExperimentsSpy).toHaveBeenLastCalledWith(...LIST_EXPERIMENT_DEFAULTS);
expect(updateBannerSpy).toHaveBeenLastCalledWith({});
});
it('shows error banner when listing experiments fails', async () => {
TestUtils.makeErrorResponseOnce(listExperimentsSpy, 'bad stuff happened');
tree = TestUtils.mountWithRouter(<ExperimentList {...generateProps()} />);
await listExperimentsSpy;
await TestUtils.flushPromises();
expect(updateBannerSpy).toHaveBeenLastCalledWith(
expect.objectContaining({
additionalInfo: 'bad stuff happened',
message:
'Error: failed to retrieve list of experiments. Click Details for more information.',
mode: 'error',
}),
);
});
it('shows error next to experiment when listing its last 5 runs fails', async () => {
// tslint:disable-next-line:no-console
console.error = jest.spyOn(console, 'error').mockImplementation();
listExperimentsSpy.mockImplementationOnce(() => ({ experiments: [{ name: 'exp1' }] }));
TestUtils.makeErrorResponseOnce(listRunsSpy, 'bad stuff happened');
tree = TestUtils.mountWithRouter(<ExperimentList {...generateProps()} />);
await listExperimentsSpy;
await TestUtils.flushPromises();
expect(tree.state()).toHaveProperty('displayExperiments', [
{
error: 'Failed to load the last 5 runs of this experiment',
expandState: 0,
name: 'exp1',
},
]);
});
it('shows error banner when listing experiments fails after refresh', async () => {
tree = TestUtils.mountWithRouter(<ExperimentList {...generateProps()} />);
const instance = tree.instance() as ExperimentList;
const refreshBtn = instance.getInitialToolbarState().actions[ButtonKeys.REFRESH];
expect(refreshBtn).toBeDefined();
TestUtils.makeErrorResponseOnce(listExperimentsSpy, 'bad stuff happened');
await refreshBtn!.action();
expect(listExperimentsSpy.mock.calls.length).toBe(2);
expect(listExperimentsSpy).toHaveBeenLastCalledWith(...LIST_EXPERIMENT_DEFAULTS);
expect(updateBannerSpy).toHaveBeenLastCalledWith(
expect.objectContaining({
additionalInfo: 'bad stuff happened',
message:
'Error: failed to retrieve list of experiments. Click Details for more information.',
mode: 'error',
}),
);
});
it('hides error banner when listing experiments fails then succeeds', async () => {
TestUtils.makeErrorResponseOnce(listExperimentsSpy, 'bad stuff happened');
tree = TestUtils.mountWithRouter(<ExperimentList {...generateProps()} />);
const instance = tree.instance() as ExperimentList;
await listExperimentsSpy;
await TestUtils.flushPromises();
expect(updateBannerSpy).toHaveBeenLastCalledWith(
expect.objectContaining({
additionalInfo: 'bad stuff happened',
message:
'Error: failed to retrieve list of experiments. Click Details for more information.',
mode: 'error',
}),
);
updateBannerSpy.mockReset();
const refreshBtn = instance.getInitialToolbarState().actions[ButtonKeys.REFRESH];
listExperimentsSpy.mockImplementationOnce(() => ({ experiments: [{ name: 'experiment1' }] }));
listRunsSpy.mockImplementationOnce(() => ({ runs: [{ name: 'run1' }] }));
await refreshBtn!.action();
expect(listExperimentsSpy.mock.calls.length).toBe(2);
expect(updateBannerSpy).toHaveBeenLastCalledWith({});
});
it('can expand an experiment to see its runs', async () => {
await mountWithNExperiments(1, 1);
tree
.find('.tableRow button')
.at(0)
.simulate('click');
expect(tree.state()).toHaveProperty('displayExperiments', [
{
expandState: ExpandState.EXPANDED,
id: 'test-experiment-id0',
last5Runs: [{ id: 'test-run-id0', name: 'test run name0' }],
name: 'test experiment name0',
},
]);
});
it('renders a list of runs for given experiment', async () => {
tree = shallow(<ExperimentList {...generateProps()} />);
tree.setState({
displayExperiments: [{ id: 'experiment1', last5Runs: [{ id: 'run1id' }, { id: 'run2id' }] }],
});
const runListTree = (tree.instance() as any)._getExpandedExperimentComponent(0);
expect(runListTree.props.experimentIdMask).toEqual('experiment1');
});
it('navigates to new experiment page when Create experiment button is clicked', async () => {
tree = TestUtils.mountWithRouter(<ExperimentList {...generateProps()} />);
const createBtn = (tree.instance() as ExperimentList).getInitialToolbarState().actions[
ButtonKeys.NEW_EXPERIMENT
];
await createBtn!.action();
expect(historyPushSpy).toHaveBeenLastCalledWith(RoutePage.NEW_EXPERIMENT);
});
it('always has new experiment button enabled', async () => {
await mountWithNExperiments(1, 1);
const calls = updateToolbarSpy.mock.calls[0];
expect(calls[0].actions[ButtonKeys.NEW_EXPERIMENT]).not.toHaveProperty('disabled');
});
it('enables clone button when one run is selected', async () => {
await mountWithNExperiments(1, 1);
(tree.instance() as any)._selectionChanged(['run1']);
expect(updateToolbarSpy).toHaveBeenCalledTimes(2);
expect(updateToolbarSpy.mock.calls[0][0].actions[ButtonKeys.CLONE_RUN]).toHaveProperty(
'disabled',
true,
);
expect(updateToolbarSpy.mock.calls[1][0].actions[ButtonKeys.CLONE_RUN]).toHaveProperty(
'disabled',
false,
);
});
it('disables clone button when more than one run is selected', async () => {
await mountWithNExperiments(1, 1);
(tree.instance() as any)._selectionChanged(['run1', 'run2']);
expect(updateToolbarSpy).toHaveBeenCalledTimes(2);
expect(updateToolbarSpy.mock.calls[0][0].actions[ButtonKeys.CLONE_RUN]).toHaveProperty(
'disabled',
true,
);
expect(updateToolbarSpy.mock.calls[1][0].actions[ButtonKeys.CLONE_RUN]).toHaveProperty(
'disabled',
true,
);
});
it('enables compare runs button only when more than one is selected', async () => {
await mountWithNExperiments(1, 1);
(tree.instance() as any)._selectionChanged(['run1']);
(tree.instance() as any)._selectionChanged(['run1', 'run2']);
(tree.instance() as any)._selectionChanged(['run1', 'run2', 'run3']);
expect(updateToolbarSpy).toHaveBeenCalledTimes(4);
expect(updateToolbarSpy.mock.calls[0][0].actions[ButtonKeys.COMPARE]).toHaveProperty(
'disabled',
true,
);
expect(updateToolbarSpy.mock.calls[1][0].actions[ButtonKeys.COMPARE]).toHaveProperty(
'disabled',
false,
);
expect(updateToolbarSpy.mock.calls[2][0].actions[ButtonKeys.COMPARE]).toHaveProperty(
'disabled',
false,
);
});
it('navigates to compare page with the selected run ids', async () => {
await mountWithNExperiments(1, 1);
(tree.instance() as any)._selectionChanged(['run1', 'run2', 'run3']);
const compareBtn = (tree.instance() as ExperimentList).getInitialToolbarState().actions[
ButtonKeys.COMPARE
];
await compareBtn!.action();
expect(historyPushSpy).toHaveBeenLastCalledWith(
`${RoutePage.COMPARE}?${QUERY_PARAMS.runlist}=run1,run2,run3`,
);
});
it('navigates to new run page with the selected run id for cloning', async () => {
await mountWithNExperiments(1, 1);
(tree.instance() as any)._selectionChanged(['run1']);
const cloneBtn = (tree.instance() as ExperimentList).getInitialToolbarState().actions[
ButtonKeys.CLONE_RUN
];
await cloneBtn!.action();
expect(historyPushSpy).toHaveBeenLastCalledWith(
`${RoutePage.NEW_RUN}?${QUERY_PARAMS.cloneFromRun}=run1`,
);
});
it('enables archive button when at least one run is selected', async () => {
await mountWithNExperiments(1, 1);
expect(TestUtils.getToolbarButton(updateToolbarSpy, ButtonKeys.ARCHIVE).disabled).toBeTruthy();
(tree.instance() as any)._selectionChanged(['run1']);
expect(TestUtils.getToolbarButton(updateToolbarSpy, ButtonKeys.ARCHIVE).disabled).toBeFalsy();
(tree.instance() as any)._selectionChanged(['run1', 'run2']);
expect(TestUtils.getToolbarButton(updateToolbarSpy, ButtonKeys.ARCHIVE).disabled).toBeFalsy();
(tree.instance() as any)._selectionChanged([]);
expect(TestUtils.getToolbarButton(updateToolbarSpy, ButtonKeys.ARCHIVE).disabled).toBeTruthy();
});
it('renders experiment names as links to their details pages', async () => {
tree = TestUtils.mountWithRouter(<ExperimentList {...generateProps()} />);
expect(
(tree.instance() as ExperimentList)._nameCustomRenderer({
id: 'experiment-id',
value: 'experiment name',
}),
).toMatchSnapshot();
});
it('renders last 5 runs statuses', async () => {
tree = TestUtils.mountWithRouter(<ExperimentList {...generateProps()} />);
expect(
(tree.instance() as ExperimentList)._last5RunsCustomRenderer({
id: 'experiment-id',
value: [
{ status: NodePhase.SUCCEEDED },
{ status: NodePhase.PENDING },
{ status: NodePhase.FAILED },
{ status: NodePhase.UNKNOWN },
{ status: NodePhase.SUCCEEDED },
],
}),
).toMatchSnapshot();
});
describe('EnhancedExperimentList', () => {
it('defaults to no namespace', () => {
render(<EnhancedExperimentList {...generateProps()} />);
expect(listExperimentsSpy).toHaveBeenLastCalledWith(...LIST_EXPERIMENT_DEFAULTS);
});
it('gets namespace from context', () => {
render(
<NamespaceContext.Provider value='test-ns'>
<EnhancedExperimentList {...generateProps()} />
</NamespaceContext.Provider>,
);
expect(listExperimentsSpy).toHaveBeenLastCalledWith(
...LIST_EXPERIMENT_DEFAULTS_WITHOUT_RESOURCE_REFERENCE,
'NAMESPACE',
'test-ns',
);
});
it('auto refreshes list when namespace changes', () => {
const { rerender } = render(
<NamespaceContext.Provider value='test-ns-1'>
<EnhancedExperimentList {...generateProps()} />
</NamespaceContext.Provider>,
);
expect(listExperimentsSpy).toHaveBeenCalledTimes(1);
expect(listExperimentsSpy).toHaveBeenLastCalledWith(
...LIST_EXPERIMENT_DEFAULTS_WITHOUT_RESOURCE_REFERENCE,
'NAMESPACE',
'test-ns-1',
);
rerender(
<NamespaceContext.Provider value='test-ns-2'>
<EnhancedExperimentList {...generateProps()} />
</NamespaceContext.Provider>,
);
expect(listExperimentsSpy).toHaveBeenCalledTimes(2);
expect(listExperimentsSpy).toHaveBeenLastCalledWith(
...LIST_EXPERIMENT_DEFAULTS_WITHOUT_RESOURCE_REFERENCE,
'NAMESPACE',
'test-ns-2',
);
});
it("doesn't keep error message for request from previous namespace", async () => {
listExperimentsSpy.mockImplementation(() => Promise.reject('namespace cannot be empty'));
const { rerender } = render(
<MemoryRouter>
<NamespaceContext.Provider value={undefined}>
<EnhancedExperimentList {...generateProps()} />
</NamespaceContext.Provider>
</MemoryRouter>,
);
listExperimentsSpy.mockImplementation(mockListNExpperiments());
rerender(
<MemoryRouter>
<NamespaceContext.Provider value={'test-ns'}>
<EnhancedExperimentList {...generateProps()} />
</NamespaceContext.Provider>
</MemoryRouter>,
);
await act(TestUtils.flushPromises);
expect(updateBannerSpy).toHaveBeenLastCalledWith(
{}, // Empty object means banner has no error message
);
});
});
});
Example #23
Source File: DataDocChartComposer.tsx From querybook with Apache License 2.0 | 4 votes |
DataDocChartComposerComponent: React.FunctionComponent<
IProps & FormikProps<IChartFormValues>
> = ({
meta,
dataDocId,
cellAboveId,
values,
dirty,
handleSubmit,
setFieldValue,
setFieldTouched,
isEditable,
}) => {
const [formTab, setFormTab] = React.useState<'data' | 'chart' | 'visuals'>(
'data'
);
const [showTable, setShowTable] = React.useState(true);
// const [showSeriesAggTypes, setShowSeriesAggTypes] = React.useState<boolean>(
// !values.aggType || !Object.keys(values.aggSeries).length
// );
const [tableTab, setTableTab] = React.useState<'original' | 'transformed'>(
values.aggregate || values.switch ? 'transformed' : 'original'
);
const [displayExecutionId, setDisplayExecutionId] = React.useState(
values.executionId
);
const [displayStatementId, setDisplayStatementId] = React.useState(
undefined
);
const {
statementResultData,
queryExecutions,
statementExecutions,
} = useChartSource(
values.cellId,
displayExecutionId,
displayStatementId,
setFieldValue.bind(null, 'cellId'),
setFieldValue.bind(null, 'executionId'),
setDisplayStatementId,
values.limit
);
const chartData = React.useMemo(
() =>
statementResultData
? transformData(
statementResultData,
values.aggregate,
values.switch,
values.formatAggCol,
values.formatSeriesCol,
values.formatValueCols,
values.aggSeries,
values.sortIndex,
values.sortAsc,
values.xIndex
)
: null,
[
statementResultData,
values.aggregate,
values.switch,
values.formatAggCol,
values.formatSeriesCol,
values.formatValueCols,
values.aggSeries,
values.sortIndex,
values.sortAsc,
values.xIndex,
]
);
// getting redux state
const queryCellOptions = useSelector((state: IStoreState) =>
queryCellSelector(state, dataDocId)
);
React.useEffect(() => {
if (
values.sourceType === 'cell_above' &&
values.cellId !== cellAboveId
) {
setFieldValue('cellId', cellAboveId);
} else if (
(values.sourceType === 'cell' ||
values.sourceType === 'execution') &&
values.cellId == null
) {
setFieldValue('cellId', cellAboveId ?? queryCellOptions?.[0].id);
}
}, [values.sourceType]);
React.useEffect(() => {
if (chartData && values.aggType) {
handleAggTypeChange(values.aggType);
}
}, [(chartData || [])[0]?.length]);
React.useEffect(() => {
setDisplayExecutionId(values.executionId);
}, [values.executionId]);
// ------------- select options ------------------------------------------------------------------
const cellExecutionOptions = React.useMemo(() => {
if (queryExecutions) {
return queryExecutions.map((queryExecution) => ({
value: queryExecution.id,
label: `Execution ${queryExecution.id}`,
}));
} else if (displayExecutionId) {
return [
{
value: displayExecutionId,
label: `Execution ${displayExecutionId}`,
},
];
} else {
return [
{
value: -1,
label: `loading executions`,
},
];
}
}, [displayExecutionId, queryExecutions]);
const xAxisOptions = React.useMemo(() => {
if (!chartData) {
return [];
}
return chartData[0].map((col, idx) => ({
value: idx,
label: col,
}));
}, [chartData]);
const formatAggByOptions = React.useMemo(() => {
if (!statementResultData) {
return [];
}
return statementResultData[0].reduce<Array<ISelectOption<number>>>(
(optionsAcc, label, idx) => {
if (idx !== values.formatSeriesCol) {
optionsAcc.push({
value: idx,
label,
});
}
return optionsAcc;
},
[]
);
}, [statementResultData, values.formatSeriesCol]);
const makeFormatSeriesOptions = React.useMemo(() => {
if (!statementResultData) {
return [];
}
const columns = statementResultData[0];
const options: Array<ISelectOption<number>> = [];
for (let i = 0; i < columns.length; i++) {
if (i !== values.formatAggCol) {
options.push({
value: i,
label: columns[i],
});
}
}
return options;
}, [statementResultData, values.formatAggCol]);
const makeFormatValueOptions = React.useMemo(() => {
if (!statementResultData) {
return [];
}
const columns = statementResultData[0];
const options: Array<ISelectOption<number>> = [];
for (let i = 0; i < columns.length; i++) {
if (i !== values.xIndex && i !== values.formatSeriesCol) {
options.push({
value: i,
label: columns[i],
});
}
}
return options;
}, [statementResultData, values.formatSeriesCol, values.xIndex]);
const makeSeriesValsAndOptions = React.useCallback(
(selectedValues: boolean) => {
if (!chartData || !chartData.length) {
return [{ value: 0, label: 'loading series' }];
}
const valsArray = chartData[0];
const optionIdxs = selectedValues
? range(valsArray.length).filter(
(val) =>
!values.hiddenSeries.includes(val) &&
val !== values.xIndex
)
: values.hiddenSeries;
const options = optionIdxs.map((i) => ({
value: i,
label: valsArray[i],
color:
ColorPalette[
values.coloredSeries[i] ?? i % ColorPalette.length
].color,
}));
return options;
},
[chartData, values.xIndex, values.hiddenSeries, values.coloredSeries]
);
const getAxesScaleType = React.useCallback(
(colIndex: number) => {
for (let row = 1; row < chartData?.length; row++) {
const cell = chartData?.[row]?.[colIndex];
if (cell != null) {
return getDefaultScaleType(chartData?.[row]?.[colIndex]);
}
}
// Unknown type, use linear as a default
return 'linear';
},
[chartData]
);
const seriesColorOptions = ColorPalette.map((color, idx) => ({
value: idx,
label: color.name,
color: color.color,
}));
const seriesAggOptions = Object.entries(aggTypes).map(([val, key]) => ({
value: val as ChartDataAggType,
label: key,
}));
// ------------- event handlers ------------------------------------------------------------------
const handleHiddenSeriesChange = (
selectedVals: Array<ISelectOption<number>>
) => {
const hiddenSeries = [];
const selectedSeries = selectedVals.map((obj) => obj.value);
for (let i = 0; i < chartData[0].length; i++) {
if (i !== values.xIndex && !selectedSeries.includes(i)) {
hiddenSeries.push(i);
}
}
setFieldValue('hiddenSeries', hiddenSeries);
};
const handleAggTypeChange = (aggType: ChartDataAggType) => {
setFieldValue('aggType', aggType);
const newAggSeries = {};
for (let i = 0; i < chartData[0].length; i++) {
newAggSeries[i] = aggType;
}
setFieldValue('aggSeries', newAggSeries);
};
// ------------- DOM elements ------------------------------------------------------------------
const tabsDOM = (
<Tabs
selectedTabKey={formTab}
onSelect={(selectedTab: 'data' | 'chart' | 'visuals') =>
setFormTab(selectedTab)
}
items={formTabs}
wide
/>
);
const renderPickerDOM = () => {
if (values.sourceType === 'custom') {
return null; // Custom data is sourced from internal context
}
const showQueryExecution =
values.sourceType !== 'execution' && queryExecutions.length;
const showStatementExecution =
displayExecutionId != null && statementExecutions.length;
const queryExecutionPicker =
showQueryExecution || showStatementExecution ? (
<FormField
label={`${
showQueryExecution ? 'Query Execution' : 'Statement'
} (Display Only)`}
stacked
help="Not Saved. Defaults to latest."
>
{showQueryExecution && (
<QueryExecutionPicker
queryExecutionId={displayExecutionId}
onSelection={setDisplayExecutionId}
queryExecutions={queryExecutions}
autoSelect
shortVersion
/>
)}
{showStatementExecution && (
<StatementExecutionPicker
statementExecutionId={displayStatementId}
onSelection={setDisplayStatementId}
statementExecutions={statementExecutions}
total={statementExecutions.length}
autoSelect
/>
)}
</FormField>
) : null;
return (
<div className="DataDocChartComposer-exec-picker">
{queryExecutionPicker}
</div>
);
};
const dataSourceDOM = (
<>
<FormSectionHeader>Source</FormSectionHeader>
<FormField stacked label="Type">
<SimpleReactSelect
value={values.sourceType}
onChange={(val) => {
if (
values.sourceType === 'execution' &&
val !== 'execution'
) {
setFieldValue('executionId', null);
}
setFieldValue('sourceType', val);
}}
options={Object.entries(sourceTypes).map(([key, val]) => ({
value: key,
label: val,
}))}
/>
</FormField>
{values.sourceType !== 'cell_above' ? (
<FormField stacked label="Cell">
<ReactSelectField
name="cellId"
options={queryCellOptions.map((val) => ({
value: val.id,
label: val.title,
}))}
/>
</FormField>
) : null}
{values.sourceType === 'execution' ? (
<SimpleField
stacked
type="react-select"
options={cellExecutionOptions}
name="executionId"
label="Execution"
/>
) : null}
{renderPickerDOM()}
<SimpleField
stacked
type="react-select"
options={StatementExecutionResultSizes.map((size) => ({
value: size,
label: formatNumber(size, 'Row'),
}))}
name="limit"
label="Row Limit"
help="Max number of rows to fetch from the result"
/>
</>
);
const dataTransformationDOM = (
<>
<FormSectionHeader>Transformation</FormSectionHeader>
<SimpleField
type="checkbox"
name="aggregate"
label="Aggregate"
onChange={(val) => {
setFieldValue('aggregate', val);
setFieldValue('hiddenSeries', []);
if (val) {
handleAggTypeChange('sum');
setTableTab('transformed');
} else {
setFieldValue('aggType', undefined);
setFieldValue('aggSeries', {});
}
}}
help="By default, all rows will be aggregated"
/>
{values.aggregate ? (
<>
<FormField stacked label="Aggregate By">
<SimpleReactSelect
value={values.aggType}
onChange={(val) => handleAggTypeChange(val)}
options={seriesAggOptions}
/>
</FormField>
<div className="DataDocChartComposer-info m8">
Value must be selected for aggregation by row/column.
</div>
<SimpleField
stacked
type="react-select"
label="Row"
name="formatAggCol"
options={formatAggByOptions}
isDisabled={!statementResultData}
withDeselect
/>
<SimpleField
stacked
label="Column"
type="react-select"
name="formatSeriesCol"
options={makeFormatSeriesOptions}
isDisabled={!statementResultData}
withDeselect
/>
<SimpleField
stacked
label="Value"
type="react-select"
name="formatValueCols"
value={values.formatValueCols[0]}
options={makeFormatValueOptions}
isDisabled={!statementResultData}
onChange={(val) => {
if (val == null) {
setFieldValue('formatValueCols', []);
} else {
setFieldValue('formatValueCols', [val]);
}
}}
withDeselect
/>
</>
) : null}
<SimpleField
type="checkbox"
label="Switch Rows/Columns"
name="switch"
help="Switch is applied after aggregation"
/>
</>
);
const dataTabDOM = (
<>
{dataSourceDOM}
{dataTransformationDOM}
</>
);
const chartOptionsDOM = (
<>
<FormField stacked label="Type">
<SimpleReactSelect
value={values.chartType}
onChange={(val) => {
setFieldValue('chartType', val);
// area defaults to true
if (val === 'area') {
setFieldValue('stack', true);
} else if (
[
'line',
'pie',
'doughnut',
'scatter',
'bubble',
].includes(val)
) {
// these charts cannot be stacked
setFieldValue('stack', false);
if (val === 'bubble' && !values.zIndex) {
setFieldValue('zIndex', 2);
}
}
}}
options={Object.entries(chartTypes).map(([key, val]) => ({
value: key,
label: val,
}))}
/>
</FormField>
{['bar', 'histogram'].includes(values.chartType) ? (
<SimpleField type="checkbox" label="Stack Chart" name="stack" />
) : null}
</>
);
let axesDOM: React.ReactChild = null;
if (values.chartType !== 'table') {
const noAxesConfig = ['pie', 'doughnut'].includes(values.chartType);
const getAxisDOM = (
prefix: string,
axisMeta: IChartAxisMeta,
scaleType: ChartScaleType,
scaleOptions: ChartScaleType[] = ChartScaleOptions
) => {
if (noAxesConfig) {
return null;
}
scaleType = getAutoDetectedScaleType(scaleOptions, scaleType);
const allScaleOptions = [
{
label: `auto detect (${scaleType})`,
value: null,
},
].concat(
scaleOptions.map((value) => ({
label: value,
value,
}))
);
let axisRangeDOM: React.ReactNode;
const assumedScale = axisMeta.scale ?? scaleType;
if (assumedScale === 'linear' || assumedScale === 'logarithmic') {
axisRangeDOM = (
<FormField stacked label="Range">
<Level margin="8px">
<NumberField
name={`${prefix}.min`}
placeholder="Min"
/>
<NumberField
name={`${prefix}.max`}
placeholder="Max"
/>
</Level>
</FormField>
);
}
return (
<>
<SimpleField
stacked
type="react-select"
name={`${prefix}.scale`}
options={allScaleOptions}
/>
{axisRangeDOM}
</>
);
};
const detectedXAxisScale = getAxesScaleType(values.xIndex);
const xAxisDOM = (
<>
<FormSectionHeader>X Axis</FormSectionHeader>
<FormField stacked label="X Axis">
<ReactSelectField
name={`xIndex`}
options={xAxisOptions}
isDisabled={!statementResultData}
/>
</FormField>
{getAxisDOM(
'xAxis',
values.xAxis,
detectedXAxisScale === 'linear'
? 'category'
: detectedXAxisScale,
chartTypeToAllowedAxisType[values.chartType].x
)}
</>
);
let yAxisDOM: React.ReactChild;
if (!noAxesConfig) {
const yAxisSeries = makeSeriesValsAndOptions(true);
const defaultYAxisScaleType = yAxisSeries.length
? getAxesScaleType(yAxisSeries[0].value)
: null;
yAxisDOM = (
<>
<FormSectionHeader>Y Axis</FormSectionHeader>
<FormField stacked label="Series">
<Select
styles={defaultReactSelectStyles}
value={yAxisSeries}
onChange={(val: any[]) =>
handleHiddenSeriesChange(val)
}
options={makeSeriesValsAndOptions(false)}
isMulti
/>
</FormField>
{getAxisDOM(
'yAxis',
values.yAxis,
defaultYAxisScaleType,
chartTypeToAllowedAxisType[values.chartType].y
)}
</>
);
}
const zAxisDOM =
values.chartType === 'bubble' ? (
<>
<FormSectionHeader>Z Axis</FormSectionHeader>
<FormField stacked label="Z Axis">
<ReactSelectField
name={`zIndex`}
options={xAxisOptions}
isDisabled={!statementResultData}
/>
</FormField>
</>
) : null;
axesDOM = (
<>
{xAxisDOM}
{yAxisDOM}
{zAxisDOM}
</>
);
}
const sortDOM = (
<>
<FormSectionHeader>Sort</FormSectionHeader>
<SimpleField
stacked
type="react-select"
options={xAxisOptions}
name="sortIndex"
label="Sort Index"
withDeselect
onChange={(val) => {
setFieldValue('sortIndex', val);
if (val != null) {
setTableTab('transformed');
}
}}
/>
<SimpleField
stacked
type="react-select"
options={[
{ value: true, label: 'Ascending' },
{ value: false, label: 'Descending' },
]}
name="sortAsc"
label="Sort Direction"
/>
</>
);
const chartTabDOM = (
<>
{chartOptionsDOM}
{axesDOM}
{sortDOM}
</>
);
const seriesColorDOM = chartData
? chartData[0].map((col, seriesIdx) => {
if (seriesIdx === 0 || values.hiddenSeries.includes(seriesIdx)) {
return null;
}
const colorIdx =
seriesIdx in values.coloredSeries
? values.coloredSeries[seriesIdx]
: seriesIdx % ColorPalette.length;
return (
<FormField
stacked
key={col}
label={() => (
<>
<b>{col}</b> Color
</>
)}
>
<ReactSelectField
value={colorIdx}
name={`coloredSeries[${seriesIdx}]`}
options={seriesColorOptions}
/>
</FormField>
);
})
: null;
const visualsTabDOM =
values.chartType === 'table' ? (
<FormField stacked label="Title">
<Field name="title" />
</FormField>
) : (
<>
<FormField stacked label="Title">
<Field name="title" />
</FormField>
{['pie', 'doughnut'].includes(values.chartType) ? null : (
<>
<SimpleField
stacked
label="X Axis Label"
name="xAxis.label"
type="input"
/>
<SimpleField
stacked
label="Y Axis Label"
name="yAxis.label"
type="input"
/>
</>
)}
<SimpleField
stacked
label="Chart Height"
name="size"
type="react-select"
help="If set from not auto to auto height, refresh the page to see change."
options={[
{
value: ChartSize.SMALL,
label: 'Small (1/3 height)',
},
{
value: ChartSize.MEDIUM,
label: 'Medium (1/2 height)',
},
{
value: ChartSize.LARGE,
label: 'Large (full height)',
},
{
value: ChartSize.AUTO,
label: 'Auto height',
},
]}
/>
<FormSectionHeader>Legend</FormSectionHeader>
<SimpleField
label="Visible"
name="legendDisplay"
type="checkbox"
/>
<SimpleField
stacked
label="Position"
name="legendPosition"
type="react-select"
options={['top', 'bottom', 'left', 'right']}
/>
<FormSectionHeader>Values</FormSectionHeader>
<SimpleField
stacked
label="Display"
name="valueDisplay"
type="react-select"
options={[
{
value: ChartValueDisplayType.FALSE,
label: 'Hide Values',
},
{
value: ChartValueDisplayType.TRUE,
label: 'Show Values',
},
{
value: ChartValueDisplayType.AUTO,
label: 'Show Values without Overlap',
},
]}
onChange={(val) => {
setFieldValue('valueDisplay', val);
if (val) {
if (values.valuePosition == null) {
setFieldValue('valuePosition', 'center');
}
if (values.valueAlignment == null) {
setFieldValue('valueAlignment', 'center');
}
}
}}
/>
{values.valueDisplay ? (
<>
<SimpleField
stacked
label="Position"
name="valuePosition"
type="react-select"
options={['center', 'start', 'end']}
/>
<SimpleField
stacked
label="Alignment"
name="valueAlignment"
type="react-select"
options={[
'center',
'start',
'end',
'right',
'left',
'top',
'bottom',
]}
/>
</>
) : null}
{['pie', 'doughnut', 'table'].includes(
values.chartType
) ? null : (
<>
<FormSectionHeader>Colors</FormSectionHeader>
{seriesColorDOM}
</>
)}
{['line', 'area'].includes(values.chartType) ? (
<>
<FormSectionHeader>Line Formatting</FormSectionHeader>
<SimpleField
label="Connect missing data"
name="connectMissing"
type="checkbox"
/>
</>
) : null}
</>
);
const formDOM = (
<FormWrapper size={7} className="DataDocChartComposer-form">
<DisabledSection disabled={!isEditable}>
{formTab === 'data' && dataTabDOM}
{formTab === 'chart' && chartTabDOM}
{formTab === 'visuals' && visualsTabDOM}
</DisabledSection>
</FormWrapper>
);
const hideTableButtonDOM = (
<IconButton
icon={showTable ? 'ChevronDown' : 'ChevronUp'}
onClick={() => setShowTable(!showTable)}
noPadding
/>
);
let dataDOM: JSX.Element;
let dataSwitch: JSX.Element;
if (chartData && showTable) {
if (values.aggregate || values.switch || values.sortIndex != null) {
dataSwitch = (
<div className="toggleTableDataSwitch">
<Tabs
selectedTabKey={tableTab}
onSelect={(key: 'original' | 'transformed') =>
setTableTab(key)
}
items={tableTabs}
/>
</div>
);
}
dataDOM = (
<div className="DataDocChartComposer-data">
<StatementResultTable
data={
tableTab === 'original'
? statementResultData
: chartData
}
paginate={true}
maxNumberOfRowsToShow={5}
/>
</div>
);
}
const tableDOM = (
<div className="DataDocChartComposer-bottom">
<Level>
<LevelItem>{dataSwitch}</LevelItem>
<LevelItem>{hideTableButtonDOM}</LevelItem>
</Level>
{dataDOM}
</div>
);
const chartDOM =
values.chartType === 'table' ? (
<DataDocChartCellTable data={chartData} title={values.title} />
) : (
<DataDocChart
data={chartData}
meta={formValsToMeta(values, meta)}
chartJSOptions={{ maintainAspectRatio: false }}
/>
);
const makeLeftDOM = () => (
<div className="DataDocChartComposer-left mr16">
<div className="DataDocChartComposer-chart mt8">
<div className="DataDocChartComposer-chart-sizer">
{chartData ? chartDOM : null}
</div>
</div>
{tableDOM}
</div>
);
return (
<div className="DataDocChartComposer mh16 pb16">
{makeLeftDOM()}
<div className="DataDocChartComposer-right">
{tabsDOM}
{formDOM}
{isEditable ? (
<div className="DataDocChartComposer-button">
<SoftButton
onClick={() => handleSubmit()}
title="Save"
fullWidth
pushable={false}
/>
</div>
) : null}
</div>
</div>
);
}
Example #24
Source File: ElasticSearchSearchEngineIndexer.test.ts From backstage with Apache License 2.0 | 4 votes |
describe('ElasticSearchSearchEngineIndexer', () => {
let indexer: ElasticSearchSearchEngineIndexer;
let bulkSpy: jest.Mock;
let catSpy: jest.Mock;
let createSpy: jest.Mock;
let aliasesSpy: jest.Mock;
let deleteSpy: jest.Mock;
beforeEach(() => {
// Instantiate the indexer to be tested.
indexer = new ElasticSearchSearchEngineIndexer({
type: 'some-type',
indexPrefix: '',
indexSeparator: '-index__',
alias: 'some-type-index__search',
logger: getVoidLogger(),
elasticSearchClient: client,
});
// Set up all requisite Elastic mocks.
mock.clearAll();
bulkSpy = jest.fn().mockReturnValue({ took: 9, errors: false, items: [] });
mock.add(
{
method: 'POST',
path: '/_bulk',
},
bulkSpy,
);
mock.add(
{
method: 'GET',
path: '/:index/_refresh',
},
jest.fn().mockReturnValue({}),
);
catSpy = jest.fn().mockReturnValue([
{
alias: 'some-type-index__search',
index: 'some-type-index__123tobedeleted',
filter: '-',
'routing.index': '-',
'routing.search': '-',
is_write_index: '-',
},
{
alias: 'some-type-index__search_removable',
index: 'some-type-index__456tobedeleted',
filter: '-',
'routing.index': '-',
'routing.search': '-',
is_write_index: '-',
},
]);
mock.add(
{
method: 'GET',
path: '/_cat/aliases/some-type-index__search%2Csome-type-index__search_removable',
},
catSpy,
);
createSpy = jest.fn().mockReturnValue({
acknowledged: true,
shards_acknowledged: true,
index: 'single_index',
});
mock.add(
{
method: 'PUT',
path: '/:index',
},
createSpy,
);
aliasesSpy = jest.fn().mockReturnValue({});
mock.add(
{
method: 'POST',
path: '*',
},
aliasesSpy,
);
deleteSpy = jest.fn().mockReturnValue({});
mock.add(
{
method: 'DELETE',
path: '/some-type-index__123tobedeleted%2Csome-type-index__456tobedeleted',
},
deleteSpy,
);
});
it('indexes documents', async () => {
const documents = [
{
title: 'testTerm',
text: 'testText',
location: 'test/location',
},
{
title: 'Another test',
text: 'Some more text',
location: 'test/location/2',
},
];
await TestPipeline.withSubject(indexer).withDocuments(documents).execute();
// Older indices should have been queried for.
expect(catSpy).toHaveBeenCalled();
// A new index should have been created.
const createdIndex = createSpy.mock.calls[0][0].path.slice(1);
expect(createdIndex).toContain('some-type-index__');
// Bulk helper should have been called with documents.
const bulkBody = bulkSpy.mock.calls[0][0].body;
expect(bulkBody[0]).toStrictEqual({ index: { _index: createdIndex } });
expect(bulkBody[1]).toStrictEqual(documents[0]);
expect(bulkBody[2]).toStrictEqual({ index: { _index: createdIndex } });
expect(bulkBody[3]).toStrictEqual(documents[1]);
// Alias should have been rotated.
expect(aliasesSpy).toHaveBeenCalled();
const aliasActions = aliasesSpy.mock.calls[0][0].body.actions;
expect(aliasActions[0]).toStrictEqual({
remove: { index: 'some-type-index__*', alias: 'some-type-index__search' },
});
expect(aliasActions[1]).toStrictEqual({
add: {
indices: [
'some-type-index__123tobedeleted',
'some-type-index__456tobedeleted',
],
alias: 'some-type-index__search_removable',
},
});
expect(aliasActions[2]).toStrictEqual({
add: { index: createdIndex, alias: 'some-type-index__search' },
});
// Old index should be cleaned up.
expect(deleteSpy).toHaveBeenCalled();
});
it('handles bulk and batching during indexing', async () => {
const documents = range(550).map(i => ({
title: `Hello World ${i}`,
location: `location-${i}`,
// Generate large document sizes to trigger ES bulk flushing.
text: range(2000).join(', '),
}));
await TestPipeline.withSubject(indexer).withDocuments(documents).execute();
// Ensure multiple bulk requests were made.
expect(bulkSpy).toHaveBeenCalledTimes(2);
// Ensure the first and last documents were included in the payloads.
const docLocations: string[] = [
...bulkSpy.mock.calls[0][0].body.map((l: any) => l.location),
...bulkSpy.mock.calls[1][0].body.map((l: any) => l.location),
];
expect(docLocations).toContain('location-0');
expect(docLocations).toContain('location-549');
});
it('ignores cleanup when no existing indices exist', async () => {
const documents = [
{
title: 'testTerm',
text: 'testText',
location: 'test/location',
},
];
// Update initial alias cat to return nothing.
catSpy = jest.fn().mockReturnValue([]);
mock.clear({
method: 'GET',
path: '/_cat/aliases/some-type-index__search%2Csome-type-index__search_removable',
});
mock.add(
{
method: 'GET',
path: '/_cat/aliases/some-type-index__search%2Csome-type-index__search_removable',
},
catSpy,
);
await TestPipeline.withSubject(indexer).withDocuments(documents).execute();
// Final deletion shouldn't be called.
expect(deleteSpy).not.toHaveBeenCalled();
});
});
Example #25
Source File: heatmap.service.ts From office-hours with GNU General Public License v3.0 | 4 votes |
// PRIVATE function that is public for testing purposes
// Rewind through the last few weeks and for each time interval,
// figure out how long wait time would have been if you had joined the queue at that time
// Timezone should be IANA
// Returns heatmap in the timezone (ie 3rd bucket is 3am in that timezone)
_generateHeatMapWithReplay(
questions: QuestionModel[],
hourTimestamps: [number, number][],
timezone: string,
bucketSize: number,
samplesPerBucket: number,
): Heatmap {
const sampleInterval = bucketSize / samplesPerBucket;
/*
TEST: Question1 is 3:05 - 3:25
// The next question is 3:21 - 3:49
THe following question is 4:05 - 4:10
Bucket = 60, Samples = 3, so timepoints are: 3:00, 3:20, 3:40.
3:20 sample gets waittime of 5 minutes
3:40 samples get waittimes of 9 minutes
4:00 sample gets waittime of 0 minutes
If i entered the queue at that time when should I have gotten help?
Every interval of minutes for the past 5 weeks are aggregated (by taking the avg)
analyze the buckets to find the closest time approximation
look at question Q1 and the next question Q2
for all sample timepoints between Q1.createdAt and Q2.createdAt:
- sample = Q1.helpedAt - timepoint (if negative, then it's 0)
*/
function dateToBucket(date: Date | number): number {
// parse in zone to handle daylight savings by getting day/hour/minute within that IANA zone
const cInZone = moment.tz(date, timezone);
return Math.floor(
(cInZone.day() * 24 * 60 + cInZone.hour() * 60 + cInZone.minute()) /
bucketSize,
);
}
const timepointBuckets: number[][] = [
...Array((24 * 7 * 60) / bucketSize),
].map(() => []);
if (questions.length) {
const startDate = questions[0].createdAt;
const sunday = moment.tz(startDate, timezone).startOf('week').toDate();
function getNextTimepointIndex(date: Date): number {
return Math.floor(timeDiffInMins(date, sunday) / sampleInterval) + 1;
}
// Get the date of the sample timepoint immediately after the given date
function getNextSampleTimepoint(date: Date): Date {
const timepointIndex = getNextTimepointIndex(date);
return new Date(
sunday.getTime() + timepointIndex * sampleInterval * 60 * 1000,
);
}
// Get all timepoints between the two dates
function getSampleTimepointsInDateRange(
date1: Date,
date2: Date,
): Date[] {
const ret = [];
let curr = getNextSampleTimepoint(date1);
while (curr.getTime() < date2.getTime()) {
ret.push(curr);
curr = getNextSampleTimepoint(curr);
}
return ret;
}
// Get the start time of the current bucket
function lastBucketBoundary(date: Date): moment.Moment {
const startOfWeek = moment.tz(date, timezone).startOf('week');
const m = moment(date);
return m.subtract(m.diff(startOfWeek, 'm') % bucketSize, 'm');
}
// go two questions at a time
let isFirst = true;
for (let i = 0; i < questions.length; i++) {
const curr = questions[i];
const next = questions[i + 1];
const isLast = i === questions.length - 1;
// get the timepoints in between
let sampledTimepoints = getSampleTimepointsInDateRange(
isFirst
? lastBucketBoundary(curr.createdAt)
.subtract(1, 's') // so that we get the first timepoint
.toDate()
: curr.createdAt,
isLast
? lastBucketBoundary(curr.helpedAt)
.add(bucketSize, 'm') // to get the nextBucketBoundary
.toDate()
: next.createdAt,
);
sampledTimepoints = sampledTimepoints.filter((time) =>
hourTimestamps.some(([start, end]) =>
inRange(time.getTime(), start, end),
),
);
// Pad the first bucket with zeros to account for timepoints before the first
if (sampledTimepoints.length > 0 && isFirst) {
isFirst = false;
}
// When we would have hypothetically gotten help at this timepoint
for (const c of sampledTimepoints) {
let wait = 0;
if (
inRange(
c.getTime(),
curr.createdAt.getTime(),
curr.helpedAt.getTime(),
)
) {
wait = (curr.helpedAt.getTime() - c.getTime()) / 60000;
}
const bucketIndex = dateToBucket(c);
timepointBuckets[bucketIndex].push(wait);
}
}
}
// Were there ever office hours in this bucket?
const wereHoursDuringBucket: boolean[] = [
...Array((24 * 7 * 60) / bucketSize),
];
for (const [start, end] of hourTimestamps) {
//prevents an office hour from [N, M] to register in multiple buckets
for (const i of range(dateToBucket(start), dateToBucket(end - 1) + 1)) {
wereHoursDuringBucket[i] = true;
}
}
const h: Heatmap = timepointBuckets.map((samples, i) => {
if (samples.length > 0) {
return mean(samples);
} else if (wereHoursDuringBucket[i]) {
return 0;
} else {
return -1;
}
});
return h;
}
Example #26
Source File: index.test.ts From analytics-next with MIT License | 4 votes |
describe('Event Factory', () => {
let user: User
let factory: EventFactory
const shoes = { product: 'shoes', total: '$35', category: 'category' }
const shopper = { totalSpent: 100 }
beforeEach(() => {
user = new User()
user.reset()
factory = new EventFactory(user)
})
describe('alias', () => {
test('creates alias events', () => {
const alias = factory.alias('netto', 'netto farah')
expect(alias.type).toEqual('alias')
expect(alias.event).toBeUndefined()
expect(alias.userId).toEqual('netto')
expect(alias.previousId).toEqual('netto farah')
})
it('does not accept traits or properties', () => {
const alias = factory.alias('netto', 'netto farah')
expect(alias.traits).toBeUndefined()
expect(alias.properties).toBeUndefined()
})
})
describe('group', () => {
test('creates group events', () => {
const group = factory.group('userId', { coolkids: true })
expect(group.traits).toEqual({ coolkids: true })
expect(group.type).toEqual('group')
expect(group.event).toBeUndefined()
})
it('accepts traits', () => {
const group = factory.group('netto', shopper)
expect(group.traits).toEqual(shopper)
})
it('sets the groupId to the message', () => {
const group = factory.group('coolKidsId', { coolkids: true })
expect(group.groupId).toEqual('coolKidsId')
})
})
describe('page', () => {
test('creates page events', () => {
const page = factory.page('category', 'name')
expect(page.traits).toBeUndefined()
expect(page.type).toEqual('page')
expect(page.event).toBeUndefined()
expect(page.name).toEqual('name')
expect(page.category).toEqual('category')
})
it('accepts properties', () => {
const page = factory.page('category', 'name', shoes)
expect(page.properties).toEqual(shoes)
})
it('ignores category and page if not passed in', () => {
const page = factory.page(null, null)
expect(page.category).toBeUndefined()
expect(page.name).toBeUndefined()
})
})
describe('identify', () => {
test('creates identify events', () => {
const identify = factory.identify('Netto', shopper)
expect(identify.traits).toEqual(shopper)
expect(identify.properties).toBeUndefined()
expect(identify.type).toEqual('identify')
expect(identify.event).toBeUndefined()
})
})
describe('track', () => {
test('creates track events', () => {
const track = factory.track('Order Completed', shoes)
expect(track.event).toEqual('Order Completed')
expect(track.properties).toEqual(shoes)
expect(track.traits).toBeUndefined()
expect(track.type).toEqual('track')
})
test('adds a message id', () => {
const track = factory.track('Order Completed', shoes)
expect(track.messageId).toContain('ajs-next')
})
test('adds a random message id even when random is mocked', () => {
jest.useFakeTimers()
jest.spyOn(uuid, 'v4').mockImplementation(() => 'abc-123')
// fake timer and fake uuid => equal
expect(factory.track('Order Completed', shoes).messageId).toEqual(
factory.track('Order Completed', shoes).messageId
)
// restore uuid function => not equal
jest.restoreAllMocks()
expect(factory.track('Order Completed', shoes).messageId).not.toEqual(
factory.track('Order Completed', shoes).messageId
)
// restore timers function => not equal
jest.useRealTimers()
expect(factory.track('Order Completed', shoes).messageId).not.toEqual(
factory.track('Order Completed', shoes).messageId
)
})
test('message ids are random', () => {
const ids = range(0, 200).map(
() => factory.track('Order Completed', shoes).messageId
)
expect(uniq(ids)).toHaveLength(200)
})
test('sets an user id', () => {
user.identify('007')
const track = factory.track('Order Completed', shoes)
expect(track.userId).toEqual('007')
})
test('sets an anonymous id', () => {
const track = factory.track('Order Completed', shoes)
expect(track.userId).toBeUndefined()
expect(track.anonymousId).toEqual(user.anonymousId())
})
test('sets options in the context', () => {
const track = factory.track('Order Completed', shoes, {
opt1: true,
})
expect(track.context).toEqual({ opt1: true })
})
test('sets integrations', () => {
const track = factory.track(
'Order Completed',
shoes,
{},
{
amplitude: false,
}
)
expect(track.integrations).toEqual({ amplitude: false })
})
test('merges integrations from `options` and `integrations`', () => {
const track = factory.track(
'Order Completed',
shoes,
{
opt1: true,
integrations: {
amplitude: false,
},
},
{
googleAnalytics: true,
amplitude: true,
}
)
expect(track.integrations).toEqual({
googleAnalytics: true,
amplitude: false,
})
})
test('do not send integration settings overrides from initialization', () => {
const track = factory.track(
'Order Completed',
shoes,
{
integrations: {
Amplitude: {
sessionId: 'session_123',
},
},
},
{
'Segment.io': {
apiHost: 'custom',
},
GoogleAnalytics: false,
'Customer.io': {},
}
)
expect(track.integrations).toEqual({
// do not pass Segment.io global settings
'Segment.io': true,
// accept amplitude event level settings
Amplitude: {
sessionId: 'session_123',
},
// pass along google analytics setting
GoogleAnalytics: false,
// empty objects are still valid
'Customer.io': true,
})
})
test('should move foreign options into `context`', () => {
const track = factory.track('Order Completed', shoes, {
opt1: true,
opt2: '?',
integrations: {
amplitude: false,
},
})
expect(track.context).toEqual({ opt1: true, opt2: '?' })
})
test('should not move known options into `context`', () => {
const track = factory.track('Order Completed', shoes, {
opt1: true,
opt2: '?',
integrations: {
amplitude: false,
},
anonymousId: 'anon-1',
timestamp: new Date(),
})
expect(track.context).toEqual({ opt1: true, opt2: '?' })
})
test('accepts an anonymous id', () => {
const track = factory.track('Order Completed', shoes, {
anonymousId: 'anon-1',
})
expect(track.context).toEqual({})
expect(track.anonymousId).toEqual('anon-1')
})
test('accepts a timestamp', () => {
const timestamp = new Date()
const track = factory.track('Order Completed', shoes, {
timestamp,
})
expect(track.context).toEqual({})
expect(track.timestamp).toEqual(timestamp)
})
test('accepts traits', () => {
const track = factory.track('Order Completed', shoes, {
traits: {
cell: '?',
},
})
expect(track.context?.traits).toEqual({
cell: '?',
})
})
test('accepts a context object', () => {
const track = factory.track('Order Completed', shoes, {
context: {
library: {
name: 'ajs-next',
version: '0.1.0',
},
},
})
expect(track.context).toEqual({
library: {
name: 'ajs-next',
version: '0.1.0',
},
})
})
test('merges a context object', () => {
const track = factory.track('Order Completed', shoes, {
foreignProp: '??',
context: {
innerProp: '?',
library: {
name: 'ajs-next',
version: '0.1.0',
},
},
})
expect(track.context).toEqual({
library: {
name: 'ajs-next',
version: '0.1.0',
},
foreignProp: '??',
innerProp: '?',
})
})
})
describe('normalize', function () {
const msg: SegmentEvent = { type: 'track' }
const opts: Options = (msg.options = {})
describe('message', function () {
it('should merge original with normalized', function () {
msg.userId = 'user-id'
opts.integrations = { Segment: true }
const normalized = factory['normalize'](msg)
expect(normalized.messageId?.length).toBeGreaterThanOrEqual(41) // 'ajs-next-md5(content + [UUID])'
delete normalized.messageId
expect(normalized).toStrictEqual({
integrations: { Segment: true },
type: 'track',
userId: 'user-id',
context: {},
})
})
})
})
})
Example #27
Source File: brush-selection-spec.ts From S2 with MIT License | 4 votes |
describe('Interaction Brush Selection Tests', () => {
let brushSelectionInstance: BrushSelection;
let mockSpreadSheetInstance: SpreadSheet;
let mockRootInteraction: RootInteraction;
const startBrushDataCellMeta: Partial<ViewMeta> = {
colIndex: 0,
rowIndex: 1,
};
const endBrushDataCellMeta: Partial<ViewMeta> = {
colIndex: 4,
rowIndex: 3,
};
const startBrushDataCell = new MockDataCell();
startBrushDataCell.getMeta = () => startBrushDataCellMeta as ViewMeta;
const endBrushDataCell = new MockDataCell();
endBrushDataCell.getMeta = () => endBrushDataCellMeta as ViewMeta;
const panelGroupAllDataCells = Array.from<number[]>({ length: 4 })
.fill(range(10))
.reduce<DataCell[]>((arr, v, i) => {
v.forEach((_, j) => {
const cell = {
cellType: CellTypes.DATA_CELL,
getMeta() {
return {
colIndex: j,
rowIndex: i,
} as ViewMeta;
},
} as DataCell;
arr.push(cell);
});
return arr;
}, []);
const emitEvent = (type: S2Event, event: Partial<OriginalEvent>) => {
brushSelectionInstance.spreadsheet.emit(type, {
originalEvent: event,
preventDefault() {},
} as any);
};
const emitGlobalEvent = (type: S2Event, event: Partial<MouseEvent>) => {
brushSelectionInstance.spreadsheet.emit(type, {
...event,
preventDefault() {},
} as any);
};
beforeEach(() => {
MockRootInteraction.mockClear();
mockSpreadSheetInstance = new PivotSheet(
document.createElement('div'),
null,
null,
);
mockRootInteraction = new MockRootInteraction(mockSpreadSheetInstance);
mockSpreadSheetInstance.getCell = jest.fn(() => startBrushDataCell) as any;
mockSpreadSheetInstance.foregroundGroup = new Group('');
mockSpreadSheetInstance.showTooltipWithInfo = jest.fn();
mockRootInteraction.getPanelGroupAllDataCells = () =>
panelGroupAllDataCells;
mockSpreadSheetInstance.interaction = mockRootInteraction;
mockSpreadSheetInstance.render();
mockSpreadSheetInstance.facet.layoutResult.colLeafNodes = Array.from(
new Array(10),
).map((_, idx) => {
return {
colIndex: idx,
id: idx,
x: idx * 100,
width: 100,
};
}) as unknown[] as Node[];
mockSpreadSheetInstance.facet.layoutResult.rowLeafNodes = Array.from(
new Array(10),
).map((_, idx) => {
return {
rowIndex: idx,
id: idx,
y: idx * 100,
height: 100,
};
}) as unknown[] as Node[];
mockSpreadSheetInstance.facet.getCellRange = () => {
return {
start: 0,
end: 9,
};
};
brushSelectionInstance = new BrushSelection(mockSpreadSheetInstance);
brushSelectionInstance.brushSelectionStage =
InteractionBrushSelectionStage.UN_DRAGGED;
brushSelectionInstance.hidePrepareSelectMaskShape = jest.fn();
});
test('should register events', () => {
expect(brushSelectionInstance.bindEvents).toBeDefined();
});
test('should not render invisible prepare select mask shape after rendered', () => {
expect(brushSelectionInstance.prepareSelectMaskShape).not.toBeDefined();
});
test('should init brush selection stage', () => {
expect(brushSelectionInstance.brushSelectionStage).toEqual(
InteractionBrushSelectionStage.UN_DRAGGED,
);
});
test('should render invisible prepare select mask shape after mouse down on the data cell', () => {
emitEvent(S2Event.DATA_CELL_MOUSE_DOWN, {
layerX: 10,
layerY: 20,
});
expect(brushSelectionInstance.prepareSelectMaskShape).toBeDefined();
expect(
brushSelectionInstance.prepareSelectMaskShape.attr('visible'),
).toBeFalsy();
});
test('should get start brush point when mouse down', () => {
emitEvent(S2Event.DATA_CELL_MOUSE_DOWN, {
layerX: 10,
layerY: 20,
});
expect(brushSelectionInstance.spreadsheet.getCell).toHaveBeenCalled();
expect(brushSelectionInstance.startBrushPoint).toStrictEqual({
x: 10,
y: 20,
scrollX: 0,
scrollY: 0,
rowIndex: 1,
colIndex: 0,
});
expect(brushSelectionInstance.displayedDataCells).toEqual(
panelGroupAllDataCells,
);
});
test('should skip brush selection if mouse not dragged', () => {
emitEvent(S2Event.DATA_CELL_MOUSE_DOWN, {
layerX: 10,
layerY: 20,
});
emitGlobalEvent(S2Event.GLOBAL_MOUSE_MOVE, {
clientX: 12,
clientY: 22,
});
emitEvent(S2Event.GLOBAL_MOUSE_UP, {});
expect(brushSelectionInstance.brushSelectionStage).toEqual(
InteractionBrushSelectionStage.UN_DRAGGED,
);
expect(
brushSelectionInstance.spreadsheet.interaction.hasIntercepts([
InterceptType.BRUSH_SELECTION,
]),
).toBeFalsy();
// 如果刷选距离过短, 则走单选的逻辑, 需要隐藏刷选提示框
expect(
brushSelectionInstance.hidePrepareSelectMaskShape,
).toHaveBeenCalled();
});
// https://github.com/antvis/S2/issues/852
test('should clear brush selection state when mouse down and context menu clicked', () => {
const globalMouseUp = jest.fn();
mockSpreadSheetInstance.on(S2Event.GLOBAL_MOUSE_UP, globalMouseUp);
emitEvent(S2Event.DATA_CELL_MOUSE_DOWN, {
layerX: 10,
layerY: 20,
});
emitGlobalEvent(S2Event.GLOBAL_MOUSE_MOVE, {
clientX: 12,
clientY: 22,
});
expect(brushSelectionInstance.brushSelectionStage).toEqual(
InteractionBrushSelectionStage.DRAGGED,
);
emitEvent(S2Event.GLOBAL_CONTEXT_MENU, {});
expect(globalMouseUp).not.toHaveBeenCalled();
expect(brushSelectionInstance.brushSelectionStage).toEqual(
InteractionBrushSelectionStage.UN_DRAGGED,
);
expect(
brushSelectionInstance.spreadsheet.interaction.hasIntercepts([
InterceptType.HOVER,
]),
).toBeFalsy();
expect(
brushSelectionInstance.spreadsheet.interaction.hasIntercepts([
InterceptType.BRUSH_SELECTION,
]),
).toBeFalsy();
expect(
brushSelectionInstance.hidePrepareSelectMaskShape,
).toHaveReturnedTimes(1);
});
test('should skip brush selection if mouse move less than valid distance', () => {
emitEvent(S2Event.GLOBAL_MOUSE_MOVE, {});
expect(brushSelectionInstance.brushSelectionStage).toEqual(
InteractionBrushSelectionStage.UN_DRAGGED,
);
expect(brushSelectionInstance.endBrushPoint).not.toBeDefined();
expect(brushSelectionInstance.brushRangeDataCells).toHaveLength(0);
expect(
brushSelectionInstance.spreadsheet.interaction.hasIntercepts([
InterceptType.HOVER,
]),
).toBeFalsy();
});
test('should get brush selection range cells', () => {
const selectedFn = jest.fn();
const brushSelectionFn = jest.fn();
mockSpreadSheetInstance.getCell = jest.fn(() => startBrushDataCell) as any;
mockSpreadSheetInstance.on(S2Event.GLOBAL_SELECTED, selectedFn);
mockSpreadSheetInstance.on(
S2Event.DATE_CELL_BRUSH_SELECTION,
brushSelectionFn,
);
// ================== mouse down ==================
emitEvent(S2Event.DATA_CELL_MOUSE_DOWN, { layerX: 10, layerY: 20 });
mockSpreadSheetInstance.getCell = jest.fn(() => endBrushDataCell) as any;
// ================== mouse move ==================
emitGlobalEvent(S2Event.GLOBAL_MOUSE_MOVE, {
clientX: 100,
clientY: 200,
});
expect(brushSelectionInstance.brushSelectionStage).toEqual(
InteractionBrushSelectionStage.DRAGGED,
);
// get end brush point
expect(brushSelectionInstance.endBrushPoint).toEqual({
...endBrushDataCellMeta,
x: 100,
y: 200,
});
// show prepare brush selection mask
expect(brushSelectionInstance.prepareSelectMaskShape.attr()).toMatchObject({
x: 10,
y: 20,
width: 90,
height: 180,
});
// ================== mouse up ==================
emitEvent(S2Event.GLOBAL_MOUSE_UP, {});
// hide prepare mask
expect(
brushSelectionInstance.prepareSelectMaskShape.attr('visible'),
).toBeFalsy();
// show tooltip
expect(mockSpreadSheetInstance.showTooltipWithInfo).toHaveBeenCalled();
// reset brush stage
expect(brushSelectionInstance.brushSelectionStage).toEqual(
InteractionBrushSelectionStage.UN_DRAGGED,
);
// get brush range selected cells
expect(brushSelectionInstance.brushRangeDataCells).toHaveLength(15);
brushSelectionInstance.brushRangeDataCells.forEach((cell) => {
const { rowIndex, colIndex } = cell.getMeta();
expect(rowIndex).toBeLessThanOrEqual(endBrushDataCellMeta.rowIndex);
expect(rowIndex).toBeGreaterThanOrEqual(startBrushDataCellMeta.rowIndex);
expect(colIndex).toBeLessThanOrEqual(endBrushDataCellMeta.colIndex);
expect(colIndex).toBeGreaterThanOrEqual(startBrushDataCellMeta.colIndex);
});
// emit event
expect(selectedFn).toHaveBeenCalledTimes(1);
expect(brushSelectionFn).toHaveBeenCalledTimes(1);
});
test('should get correct formatted brush point', () => {
const EXTRA_PIXEL = 2;
const VSCROLLBAR_WIDTH = 5;
const { width, height } = mockSpreadSheetInstance.facet.getCanvasHW();
const minX = 10;
const minY = 10;
const maxY = height + 10;
const maxX = width + 10;
mockSpreadSheetInstance.facet.panelBBox = {
minX,
minY,
maxY,
maxX,
} as any;
brushSelectionInstance.endBrushPoint = {
x: maxX - 10,
y: maxY - 10,
scrollX: 0,
colIndex: 0,
rowIndex: 0,
};
mockSpreadSheetInstance.facet.vScrollBar = {
getBBox: () =>
({
width: VSCROLLBAR_WIDTH,
} as any),
} as any;
let result = brushSelectionInstance.formatBrushPointForScroll({
x: 20,
y: 20,
});
expect(result).toStrictEqual({
x: {
needScroll: true,
value: maxX - VSCROLLBAR_WIDTH - EXTRA_PIXEL,
},
y: {
needScroll: true,
value: maxY - EXTRA_PIXEL,
},
});
brushSelectionInstance.endBrushPoint = {
x: maxX - 10,
y: maxY - 10,
scrollX: 0,
colIndex: 0,
rowIndex: 0,
};
result = brushSelectionInstance.formatBrushPointForScroll({
x: 1,
y: 1,
});
expect(result).toStrictEqual({
x: {
needScroll: false,
value: maxX - 10 + 1,
},
y: {
needScroll: false,
value: maxY - 10 + 1,
},
});
brushSelectionInstance.endBrushPoint = {
x: minX + 10,
y: minY + 10,
scrollX: 0,
colIndex: 0,
rowIndex: 0,
};
result = brushSelectionInstance.formatBrushPointForScroll({
x: -20,
y: -20,
});
expect(result).toStrictEqual({
x: {
needScroll: true,
value: minX + EXTRA_PIXEL,
},
y: {
needScroll: true,
value: minY + EXTRA_PIXEL,
},
});
});
test('should get correct selected cell metas', () => {
expect(
brushSelectionInstance.getSelectedCellMetas({
start: {
colIndex: 0,
rowIndex: 0,
},
end: {
colIndex: 9,
rowIndex: 9,
},
} as any).length,
).toBe(100);
});
test('should get correct adjusted frozen rowIndex and colIndex', () => {
const { adjustNextColIndexWithFrozen, adjustNextRowIndexWithFrozen } =
brushSelectionInstance;
mockSpreadSheetInstance.setOptions({
frozenColCount: 1,
frozenRowCount: 1,
frozenTrailingColCount: 1,
frozenTrailingRowCount: 1,
});
mockSpreadSheetInstance.dataSet.getDisplayDataSet = () => {
return Array.from(new Array(10)).map(() => {
return {};
});
};
(mockSpreadSheetInstance.facet as TableFacet).panelScrollGroupIndexes = [
1, 8, 1, 8,
];
expect(adjustNextColIndexWithFrozen(9, ScrollDirection.TRAILING)).toBe(8);
expect(adjustNextColIndexWithFrozen(0, ScrollDirection.LEADING)).toBe(1);
expect(adjustNextColIndexWithFrozen(7, ScrollDirection.TRAILING)).toBe(7);
expect(adjustNextRowIndexWithFrozen(9, ScrollDirection.TRAILING)).toBe(8);
expect(adjustNextRowIndexWithFrozen(0, ScrollDirection.LEADING)).toBe(1);
expect(adjustNextRowIndexWithFrozen(7, ScrollDirection.TRAILING)).toBe(7);
});
test('should get correct scroll offset for row and col', () => {
const { facet } = mockSpreadSheetInstance;
expect(
getScrollOffsetForCol(
7,
ScrollDirection.LEADING,
mockSpreadSheetInstance,
),
).toBe(700);
expect(
getScrollOffsetForCol(
7,
ScrollDirection.TRAILING,
mockSpreadSheetInstance,
),
).toBe(200);
(facet as TableFacet).frozenGroupInfo = {
[FrozenGroup.FROZEN_COL]: {
width: 100,
},
[FrozenGroup.FROZEN_TRAILING_COL]: {
width: 100,
},
[FrozenGroup.FROZEN_ROW]: {
height: 0,
},
[FrozenGroup.FROZEN_TRAILING_ROW]: {
height: 0,
},
};
expect(
getScrollOffsetForCol(
7,
ScrollDirection.LEADING,
mockSpreadSheetInstance,
),
).toBe(600);
expect(
getScrollOffsetForCol(
7,
ScrollDirection.TRAILING,
mockSpreadSheetInstance,
),
).toBe(300);
facet.panelBBox = {
height: facet.getCanvasHW().height,
} as any;
facet.viewCellHeights = facet.getViewCellHeights(facet.layoutResult);
expect(
getScrollOffsetForRow(
7,
ScrollDirection.LEADING,
mockSpreadSheetInstance,
),
).toBe(700);
expect(
getScrollOffsetForRow(
7,
ScrollDirection.TRAILING,
mockSpreadSheetInstance,
),
).toBe(320);
(facet as TableFacet).frozenGroupInfo = {
[FrozenGroup.FROZEN_COL]: {
width: 0,
},
[FrozenGroup.FROZEN_TRAILING_COL]: {
width: 0,
},
[FrozenGroup.FROZEN_ROW]: {
height: 100,
},
[FrozenGroup.FROZEN_TRAILING_ROW]: {
height: 100,
},
};
expect(
getScrollOffsetForRow(
7,
ScrollDirection.LEADING,
mockSpreadSheetInstance,
),
).toBe(600);
expect(
getScrollOffsetForRow(
7,
ScrollDirection.TRAILING,
mockSpreadSheetInstance,
),
).toBe(420);
});
test('should get valid x and y index', () => {
const { validateXIndex, validateYIndex } = brushSelectionInstance;
expect(validateXIndex(-1)).toBe(null);
expect(validateXIndex(1)).toBe(1);
expect(validateXIndex(10)).toBe(null);
expect(validateXIndex(9)).toBe(9);
expect(validateYIndex(-1)).toBe(null);
expect(validateYIndex(1)).toBe(1);
expect(validateYIndex(10)).toBe(null);
expect(validateYIndex(9)).toBe(9);
(mockSpreadSheetInstance.facet as TableFacet).frozenGroupInfo = {
[FrozenGroup.FROZEN_COL]: {
range: [0, 1],
},
[FrozenGroup.FROZEN_TRAILING_COL]: {
range: [8, 9],
},
[FrozenGroup.FROZEN_ROW]: {
range: [0, 1],
},
[FrozenGroup.FROZEN_TRAILING_ROW]: {
range: [8, 9],
},
};
expect(validateXIndex(1)).toBe(null);
expect(validateXIndex(2)).toBe(2);
expect(validateXIndex(8)).toBe(null);
expect(validateXIndex(7)).toBe(7);
expect(validateXIndex(1)).toBe(null);
expect(validateXIndex(2)).toBe(2);
expect(validateXIndex(8)).toBe(null);
expect(validateXIndex(7)).toBe(7);
});
});
Example #28
Source File: LogRows.test.tsx From grafana-chinese with Apache License 2.0 | 4 votes |
describe('LogRows', () => {
it('renders rows', () => {
const rows: LogRowModel[] = [makeLog({ uid: '1' }), makeLog({ uid: '2' }), makeLog({ uid: '3' })];
const wrapper = mount(
<LogRows
logRows={rows}
dedupStrategy={LogsDedupStrategy.none}
highlighterExpressions={[]}
showLabels={false}
showTime={false}
wrapLogMessage={true}
timeZone={'utc'}
/>
);
expect(wrapper.find(LogRow).length).toBe(3);
expect(wrapper.contains('log message 1')).toBeTruthy();
expect(wrapper.contains('log message 2')).toBeTruthy();
expect(wrapper.contains('log message 3')).toBeTruthy();
});
it('renders rows only limited number of rows first', () => {
const rows: LogRowModel[] = [makeLog({ uid: '1' }), makeLog({ uid: '2' }), makeLog({ uid: '3' })];
jest.useFakeTimers();
const wrapper = mount(
<LogRows
logRows={rows}
dedupStrategy={LogsDedupStrategy.none}
highlighterExpressions={[]}
showLabels={false}
showTime={false}
wrapLogMessage={true}
timeZone={'utc'}
previewLimit={1}
/>
);
expect(wrapper.find(LogRow).length).toBe(1);
expect(wrapper.contains('log message 1')).toBeTruthy();
jest.runAllTimers();
wrapper.update();
expect(wrapper.find(LogRow).length).toBe(3);
expect(wrapper.contains('log message 1')).toBeTruthy();
expect(wrapper.contains('log message 2')).toBeTruthy();
expect(wrapper.contains('log message 3')).toBeTruthy();
jest.useRealTimers();
});
it('renders deduped rows if supplied', () => {
const rows: LogRowModel[] = [makeLog({ uid: '1' }), makeLog({ uid: '2' }), makeLog({ uid: '3' })];
const dedupedRows: LogRowModel[] = [makeLog({ uid: '4' }), makeLog({ uid: '5' })];
const wrapper = mount(
<LogRows
logRows={rows}
deduplicatedRows={dedupedRows}
dedupStrategy={LogsDedupStrategy.none}
highlighterExpressions={[]}
showLabels={false}
showTime={false}
wrapLogMessage={true}
timeZone={'utc'}
/>
);
expect(wrapper.find(LogRow).length).toBe(2);
expect(wrapper.contains('log message 4')).toBeTruthy();
expect(wrapper.contains('log message 5')).toBeTruthy();
});
it('renders with default preview limit', () => {
// PREVIEW_LIMIT * 2 is there because otherwise we just render all rows
const rows: LogRowModel[] = range(PREVIEW_LIMIT * 2 + 1).map(num => makeLog({ uid: num.toString() }));
const wrapper = mount(
<LogRows
logRows={rows}
dedupStrategy={LogsDedupStrategy.none}
highlighterExpressions={[]}
showLabels={false}
showTime={false}
wrapLogMessage={true}
timeZone={'utc'}
/>
);
expect(wrapper.find(LogRow).length).toBe(100);
});
});
Example #29
Source File: Sidebar.tsx From project-papua with Apache License 2.0 | 4 votes |
Sidebar: React.FC<Props> = (props) => {
const { pages } = props
const { translateByID, form, pageIndex, setPage, completion } = useContext(FormContext)
const { language, setLanguage } = useContext(LanguageContext)
const size = useContext(ResponsiveContext)
const currentPage = pages[pageIndex]
const percent = Math.floor((pageIndex / (pages.length - 1)) * 100)
const canClickPage = (i: number) => {
return range(0, i).every((index) => completion[index])
}
return (
<Box
flex={{ shrink: 0 }}
margin={size === 'small' ? { top: 'small' } : { left: 'small' }}
direction="column"
width={size === 'small' ? '100%' : '350px'}
>
<Card margin={{ bottom: 'small' }} pad={{ horizontal: size === 'small' ? '24px' : 'medium', vertical: 'small' }}>
<Markdown>{translateByID('demo-warning')}</Markdown>
</Card>
<Card pad="medium">
{form.seal && (
<Box margin={{ bottom: 'medium' }}>
<Image src={form.seal} style={{ maxHeight: '175px', maxWidth: '100%', objectFit: 'contain' }} />
</Box>
)}
<Box>
<Heading level={4} margin="none">
{translateByID('language')}
</Heading>
<StyledSelect
a11yTitle="select language"
margin={{ top: 'xsmall' }}
options={languages}
labelKey="title"
valueKey={{ key: 'value', reduce: true }}
value={language}
onChange={({ value }) => setLanguage(value)}
/>
</Box>
<Box margin={{ top: 'medium' }}>
<Box direction="row" justify="between">
<Heading level={4} margin="none">
{translateByID('progress')}
</Heading>
<Paragraph margin="none">{percent}%</Paragraph>
</Box>
<Box
margin={{ top: 'xsmall' }}
style={{ width: '100%', height: '12px', borderRadius: '12px', background: '#F2F2F2' }}
>
<Box style={{ width: `${percent}%`, height: '100%', borderRadius: '12px', background: '#3A80C2' }} />
</Box>
</Box>
<Box margin={{ top: 'medium' }}>
{size === 'small' && pages.length > 2 ? (
<>
{/* On small screens, we collapse the section titles to a Select */}
<Heading level={4} margin="none">
{translateByID('section')}
</Heading>
<StyledSelect
a11yTitle="select section"
margin={{ top: 'xsmall' }}
options={pages.map((page, i) => ({ page, i, disabled: !canClickPage(i) }))}
labelKey="page"
valueKey={{ key: 'i', reduce: true }}
disabledKey="disabled"
value={pageIndex as any} /* These type definitions don't support values as numbers */
// TODO: In production, add a `canClickPage(i) && ` below to prevent folks from jumping forward.
onChange={({ value: i }) => setPage(i)}
/>
</>
) : (
/* On larger screens, we show all section titles as a list */
pages.map((page, i) => {
return (
<Text
style={{
cursor: canClickPage(i) ? 'pointer' : 'not-allowed',
opacity: currentPage === page ? '100%' : canClickPage(i) ? '50%' : '20%',
}}
// TODO: In production, add a `canClickPage(i) && ` below to prevent folks from jumping forward.
onClick={() => setPage(i)}
margin={{ bottom: 'xsmall' }}
key={page}
>
{page}
</Text>
)
})
)}
</Box>
</Card>
</Box>
)
}