Skip to content

Commit

Permalink
feat(playground): ability to rerun queries (#5597)
Browse files Browse the repository at this point in the history
  • Loading branch information
vasilev-alex committed Nov 10, 2022
1 parent 2a115ff commit 6ef8ce9
Show file tree
Hide file tree
Showing 5 changed files with 166 additions and 80 deletions.
6 changes: 4 additions & 2 deletions packages/cubejs-client-react/src/QueryBuilder.jsx
Expand Up @@ -239,7 +239,7 @@ export default class QueryBuilder extends React.Component {
const toFilter = (member) => ({
member: member.member?.name || member.dimension?.name,
operator: member.operator,
values: member.values,
...(['set', 'notSet'].includes(member.operator) ? {} : { values: member.values }),
});

const updateMethods = (memberType, toQuery = getName) => ({
Expand Down Expand Up @@ -355,7 +355,9 @@ export default class QueryBuilder extends React.Component {
if (this.orderMembersOrderKeys.length) {
// Preserve order until the members change or manually re-ordered
// This is needed so that when an order member becomes active, it doesn't jump to the top of the list
orderMembers = (this.orderMembersOrderKeys || []).map((id) => orderMembers.find((member) => member.id === id));
orderMembers = (this.orderMembersOrderKeys || [])
.map((id) => orderMembers.find((member) => member.id === id))
.filter(Boolean);
}

return {
Expand Down
176 changes: 110 additions & 66 deletions packages/cubejs-playground/src/QueryBuilder/FilterGroup.tsx
@@ -1,11 +1,21 @@
import { Fragment } from 'react';
import { PlusOutlined } from '@ant-design/icons';
import { Fragment } from 'react';

import MemberDropdown from './MemberDropdown';
import RemoveButtonGroup from './RemoveButtonGroup';
import { SectionRow, Select } from '../components';
import { useDeepMemo } from '../hooks';
import FilterInput from './FilterInput';
import MemberDropdown from './MemberDropdown';
import MissingMemberTooltip from './MissingMemberTooltip';
import { SectionRow, Select } from '../components';
import RemoveButtonGroup from './RemoveButtonGroup';

type Props = {
disabled: boolean;
members: any[];
availableMembers: any;
addMemberName: any;
updateMethods: any;
missingMembers: any;
};

const FilterGroup = ({
disabled = false,
Expand All @@ -14,73 +24,107 @@ const FilterGroup = ({
addMemberName,
updateMethods,
missingMembers,
}: any) => (
<SectionRow>
{members.map((m) => {
const isMissing = missingMembers.includes(m.member);
}: Props) => {
const operatorsByMemberName = useDeepMemo(() => {
return members.reduce(
(memo, item) => ({
...memo,
[item.member]: [...(memo[item.member] || []), item.operator],
}),
{}
);
}, [members]);

return (
<SectionRow>
{members.map((m) => {
const isMissing = missingMembers.includes(m.member);

const buttonGroup = (
<RemoveButtonGroup
disabled={disabled}
className={disabled ? 'disabled' : null}
color={isMissing ? 'danger' : 'primary'}
onRemoveClick={() => updateMethods.remove(m)}
>
<MemberDropdown
const buttonGroup = (
<RemoveButtonGroup
disabled={disabled}
availableCubes={availableMembers}
style={{
minWidth: 150,
}}
onClick={(updateWith) =>
updateMethods.update(m, { ...m, dimension: updateWith })
}
className={disabled ? 'disabled' : null}
color={isMissing ? 'danger' : 'primary'}
onRemoveClick={() => updateMethods.remove(m)}
>
{m.dimension.title}
</MemberDropdown>
</RemoveButtonGroup>
);
<MemberDropdown
disabled={disabled}
availableCubes={availableMembers}
style={{
minWidth: 150,
}}
onClick={(updateWith) =>
updateMethods.update(m, { ...m, dimension: updateWith })
}
>
{m.dimension.title}
</MemberDropdown>
</RemoveButtonGroup>
);

return (
<Fragment key={m.member}>
{isMissing ? (
<MissingMemberTooltip>{buttonGroup}</MissingMemberTooltip>
) : (
buttonGroup
)}
return (
<Fragment key={`${m.member}-${m.operator}`}>
{isMissing ? (
<MissingMemberTooltip>{buttonGroup}</MissingMemberTooltip>
) : (
buttonGroup
)}

<Select
disabled={disabled}
value={m.operator}
style={{ width: 200 }}
onChange={(operator) => updateMethods.update(m, { ...m, operator })}
>
{m.operators.map((operator) => (
<Select.Option key={operator.name} value={operator.name}>
{operator.title}
</Select.Option>
))}
</Select>
<Select
disabled={disabled}
value={m.operator}
style={{ width: 200 }}
onChange={(operator) =>
updateMethods.update(m, { ...m, operator })
}
>
{m.operators.map((operator) => {
const isOperatorDisabled = operatorsByMemberName[
m.member
]?.includes(operator.name);

<FilterInput
key="filterInput"
disabled={disabled}
member={m}
updateMethods={updateMethods}
/>
</Fragment>
);
})}
<MemberDropdown
availableCubes={availableMembers}
type="dashed"
disabled={disabled}
icon={<PlusOutlined />}
onClick={(m) => updateMethods.add({ member: m })}
>
{!members.length ? addMemberName : null}
</MemberDropdown>
</SectionRow>
);
return (
<Select.Option
key={operator.name}
value={operator.name}
title={
isOperatorDisabled
? `There is already a filter applied with this operator for ${
m.dimension?.title || m.name
}`
: operator.name
}
disabled={isOperatorDisabled}
>
{operator.title}
</Select.Option>
);
})}
</Select>

{!['set', 'notSet'].includes(m.operator) ? (
<FilterInput
key="filterInput"
disabled={disabled}
member={m}
updateMethods={updateMethods}
/>
) : null}
</Fragment>
);
})}

<MemberDropdown
availableCubes={availableMembers}
type="dashed"
disabled={disabled || members.find((m) => !m.operator)}
icon={<PlusOutlined />}
onClick={(m) => updateMethods.add({ member: m })}
>
{!members.length ? addMemberName : null}
</MemberDropdown>
</SectionRow>
);
};

export default FilterGroup;
@@ -1,7 +1,14 @@
import { TOrderMember } from '@cubejs-client/core';
import { DragDropContext, Droppable } from 'react-beautiful-dnd';
import DraggableItem from './DraggableItem';

export default function OrderGroup({ orderMembers, onOrderChange, onReorder }) {
type Props = {
orderMembers: TOrderMember[];
onOrderChange: any;
onReorder: any;
};

export default function OrderGroup({ orderMembers, onOrderChange, onReorder }: Props) {
return (
<DragDropContext
onDragEnd={({ source, destination }) => {
Expand Down
Expand Up @@ -13,10 +13,11 @@ import {
VizState,
} from '@cubejs-client/react';
import { Col, Row, Space } from 'antd';
import { PlaySquareOutlined } from '@ant-design/icons';
import React, { RefObject, useEffect, useRef, useState } from 'react';
import styled from 'styled-components';

import { Card } from '../../../atoms';
import { Button, Card } from '../../../atoms';
import { FatalError } from '../../../components/Error/FatalError';
import ChartContainer from '../../../ChartContainer';
import { SectionHeader, SectionRow } from '../../../components';
Expand Down Expand Up @@ -203,15 +204,20 @@ export function PlaygroundQueryBuilder({

const isGraphQLSupported = useServerCoreVersionGte('0.29.0');

const { isChartRendererReady, queryStatus, queryError, queryRequestId } =
useChartRendererState(queryId);
const {
isChartRendererReady,
queryStatus,
queryError,
queryRequestId,
resultSetExists,
} = useChartRendererState(queryId);
const {
setQueryStatus,
setQueryLoading,
setChartRendererReady,
setQueryError,
} = useChartRendererStateMethods();

const { refreshToken } = useSecurityContext();

const iframeRef = useRef<HTMLIFrameElement>(null);
Expand Down Expand Up @@ -315,14 +321,19 @@ export function PlaygroundQueryBuilder({
availableFilterMembers,
}) => {
let parsedDateRange;

if (dryRunResponse) {
const { timeDimensions = [] } = dryRunResponse.pivotQuery || {};
parsedDateRange = timeDimensions[0]?.dateRange;
} else if (Array.isArray(query.timeDimensions?.[0]?.dateRange)) {
// @ts-ignore
parsedDateRange = query.timeDimensions[0].dateRange;
}

const queriesEqual = areQueriesEqual(
validateQuery(query),
validateQuery(queryRef.current)
);

return (
<Wrapper data-testid={`query-builder-${queryId}`}>
Expand Down Expand Up @@ -455,7 +466,9 @@ export function PlaygroundQueryBuilder({
<Space style={{ marginLeft: 'auto' }}>
{Extra ? (
<Extra
queryRequestId={queryRequestId || queryError?.response?.requestId}
queryRequestId={
queryRequestId || queryError?.response?.requestId
}
queryStatus={queryStatus}
error={queryError}
/>
Expand All @@ -468,6 +481,23 @@ export function PlaygroundQueryBuilder({
{...(queryStatus as QueryStatus)}
/>
) : null}

{resultSetExists && queriesEqual ? (
<Button
icon={<PlaySquareOutlined />}
onClick={async () => {
await refreshToken();

handleRunButtonClick({
query: validateQuery(query),
pivotConfig,
chartType: chartType || 'line',
});
}}
>
Rerun query
</Button>
) : null}
</Space>
</SectionRow>
</Col>
Expand Down Expand Up @@ -529,16 +559,18 @@ export function PlaygroundQueryBuilder({
isFetchingMeta={isFetchingMeta}
render={({ framework }) => {
if (richMetaError) {
return <FatalError error={richMetaError} stack={metaErrorStack} />;
return (
<FatalError
error={richMetaError}
stack={metaErrorStack}
/>
);
}

return (
<ChartRenderer
queryId={queryId}
areQueriesEqual={areQueriesEqual(
validateQuery(query),
validateQuery(queryRef.current)
)}
areQueriesEqual={queriesEqual}
isFetchingMeta={isFetchingMeta}
queryError={queryError}
framework={framework}
Expand Down
Expand Up @@ -89,6 +89,7 @@ export default function Settings({
type="number"
value={limit}
step={500}
min={0}
onChange={setLimit}
onPressEnter={() => {
onUpdate({ limit });
Expand Down

0 comments on commit 6ef8ce9

Please sign in to comment.