Skip to content

Commit

Permalink
Display aggregation builder in widget focus mode instead of modal. (#…
Browse files Browse the repository at this point in the history
…10212)

* Transforming `WidgetGrid` to function component.

* Creating separate component for `WidgetContainer` and using it in `WidgetComponent` instead of `WidgetGrid`.

* Add `className` prop for `ReactGridContainer` to be able to style the component with styled-components `styled()`.

* Removing `WidgetContainer` from `WidgetComponent` because `WidgetContainer` needs to be a direct child of the `ReactGridLayout`.

* Do not replace `WidgetGrid` with widget on widget focus. Instead only render focused widget in widget grid to avoid unmoiunting the widget.

This way the focused widget keeps its state on focus.

* Implementing `useMemo` for `WidgetGrid`.

* Preventing widget resize when widget is focused.

* Disabling `WidgetGrid` animation.

* Do not unmount any widget on widget focus.

Previously on widget focus we unmounted every other widget. With this change we are only changing the style of the focused widget, to ensure we do not loose the state and e.g. the scroll position of other widgets.

* Maintaining widget edit status in `WidgetFocusContext`.

* Displaying widget edit component inside `Widget` component instead of a modal.

* Removing console.logs

* Fixing widget toggle edit.

* Unifying `WidgetFrame` container paddings with `SearchBar` container paddings.

* Removing remaining modal component from `EditWidgetFrame`.

* Removing no longer needed widget edit code in `Widget`.

* Fixing relation between widget edit and focus mode.

* Replacing `EditWidgetFrame.css` with styled components.

* Creating separate component for widget actions menu.

* Simplifying `WIdgetFocusProvider` logic by creating state based on query params.

* Cleanup url if focus query params are not current.

* Adding padding for `QueryControls`.

* Fixing `WidgetFocusProvider` state query params sync.

* Fixing copy to dashboard and move to page widget action.

* Fixing and simplifying `WidgetFocusProvider` updates.

* Updating and extending `WidgetFocusProvider.test`.

* Creating test for `WidgetActionsMenu`.

* Removing no longer needed `Widget` tests.

* Updating `Widget.test`.

* Updating `WidgetActionsMenu`.

* Crating wigdet focus tests for `Widget`.

* Creating widget focus test for `WidgetActionsMenu`.

* Fixing `ExtraWidgetActions.test`.

* Fixing naming.

* Removing no longer needed `Widget` editing state.

* Hiding `WidgetActionsMenu` on widget edit.

* Updating type definition in `Widget.test`.

* Removing not needed `jest.clearAllMocks` in `WidgetFocusProvider.test`.

* Refactoring redundant code in `WidgetFocusProvider`.

* Creating more explicit types for `WidgetFocusContext` and `WidgetFocusProvider`.

* Removing no longer needed test setup code in `WidgetActionsMenu.test`.

* Simplifying `_updateDashboardWithNewSearch` usage in `WidgetActionsMenu`.

* Implement separate functions for `WidgetFocusContext` to unset widget focussing and widget editing.

* Fixing failing `Widget.test`.
  • Loading branch information
linuspahl committed Mar 18, 2021
1 parent 1917314 commit d01554d
Show file tree
Hide file tree
Showing 22 changed files with 1,060 additions and 893 deletions.
2 changes: 1 addition & 1 deletion graylog2-web-interface/src/views/components/Query.test.jsx
Expand Up @@ -50,7 +50,7 @@ const widgets = Immutable.Map({ widget1, widget2 });

describe('Query', () => {
const SUT = (props) => (
<WidgetFocusContext.Provider value={{ focusedWidget: undefined, setFocusedWidget: () => {} }}>
<WidgetFocusContext.Provider value={{ focusedWidget: undefined, setWidgetFocusing: () => {}, setWidgetEditing: () => {} }}>
<Query results={{ errors: [], searchTypes: {} }}
widgetMapping={widgetMapping}
widgets={widgets}
Expand Down
4 changes: 2 additions & 2 deletions graylog2-web-interface/src/views/components/Search.tsx
Expand Up @@ -77,15 +77,15 @@ const SearchArea = styled(PageContentLayout)(() => {
const { focusedWidget } = useContext(WidgetFocusContext);

return css`
${focusedWidget && css`
${focusedWidget?.id && css`
.page-content-grid {
display: flex;
flex-direction: column;
height: 100%;
width: 100%;
/* overflow auto is required to display the message table widget height correctly */
overflow: ${focusedWidget ? 'auto' : 'visible'};
overflow: ${focusedWidget?.id ? 'auto' : 'visible'};
}
`}
`;
Expand Down
Expand Up @@ -77,7 +77,7 @@ const SearchResult = React.memo(({ queryId, searches, viewState }: Props) => {
const results = searches && searches.result;
const widgetMapping = searches && searches.widgetMapping;

const hasFocusedWidget = !!focusedWidget;
const hasFocusedWidget = !!focusedWidget?.id;

const currentResults = results ? results.forId(queryId) : undefined;
const allFields = fieldTypes.all;
Expand Down
28 changes: 16 additions & 12 deletions graylog2-web-interface/src/views/components/WidgetComponent.tsx
Expand Up @@ -31,6 +31,7 @@ import DrilldownContextProvider from './contexts/DrilldownContextProvider';

type Props = {
data: WidgetDataMap,
editing: boolean,
errors: WidgetErrorsMap,
fields: Immutable.List<TFieldTypeMapping>,
onPositionsChange: (position?: WidgetPosition) => void,
Expand All @@ -43,6 +44,7 @@ type Props = {

const WidgetComponent = ({
data,
editing,
errors,
fields,
onPositionsChange = () => undefined,
Expand All @@ -61,17 +63,18 @@ const WidgetComponent = ({
<WidgetContext.Provider value={widget}>
<AdditionalContext.Provider value={{ widget }}>
<ExportSettingsContextProvider>
<Widget id={widget.id}
widget={widget}
data={widgetData}
<Widget data={widgetData}
editing={editing}
errors={widgetErrors}
height={height}
position={position}
width={width}
fields={fields}
height={height}
id={widget.id}
onPositionsChange={onPositionsChange}
onSizeChange={onWidgetSizeChange}
title={title} />
position={position}
title={title}
widget={widget}
width={width} />
</ExportSettingsContextProvider>
</AdditionalContext.Provider>
</WidgetContext.Provider>
Expand All @@ -80,15 +83,16 @@ const WidgetComponent = ({
};

WidgetComponent.propTypes = {
widget: PropTypes.object.isRequired,
data: PropTypes.object.isRequired,
editing: PropTypes.bool.isRequired,
errors: PropTypes.object.isRequired,
widgetDimension: PropTypes.object.isRequired,
title: PropTypes.string.isRequired,
position: PropTypes.object.isRequired,
onPositionsChange: PropTypes.func,
fields: PropTypes.object.isRequired,
onPositionsChange: PropTypes.func,
onWidgetSizeChange: PropTypes.func,
position: PropTypes.object.isRequired,
title: PropTypes.string.isRequired,
widget: PropTypes.object.isRequired,
widgetDimension: PropTypes.object.isRequired,
};

WidgetComponent.defaultProps = {
Expand Down
16 changes: 9 additions & 7 deletions graylog2-web-interface/src/views/components/WidgetGrid.jsx
Expand Up @@ -43,10 +43,10 @@ const DashboardWrap = styled.div(({ theme }) => css`
height: 100%;
`);

const StyledReactGridContainer = styled(ReactGridContainer)(({ focusedWidget }) => `
height: ${focusedWidget ? '100% !important' : '100%'};
max-height: ${focusedWidget ? '100%' : 'auto'};
overflow: ${focusedWidget ? 'hidden' : 'visible'};
const StyledReactGridContainer = styled(ReactGridContainer)(({ hasFocusedWidget }) => css`
height: ${hasFocusedWidget ? '100% !important' : '100%'};
max-height: ${hasFocusedWidget ? '100%' : 'auto'};
overflow: ${hasFocusedWidget ? 'hidden' : 'visible'};
transition: none;
`);

Expand Down Expand Up @@ -95,12 +95,14 @@ const _renderWidgets = ({
const widget = widgets[widgetId];
returnedWidgets.positions[widgetId] = positions[widgetId] || _defaultDimensions(widget.type);
const widgetTitle = titles.getIn([TitleTypes.Widget, widget.id], defaultTitle(widget));
const isFocused = focusedWidget === widgetId;
const isFocused = focusedWidget?.id === widgetId && focusedWidget?.focusing;
const editing = focusedWidget?.id === widgetId && focusedWidget?.editing;

returnedWidgets.widgets.push(
<WidgetContainer isFocused={isFocused} key={widget.id}>
<WidgetComponent allFields={allFields}
data={data}
editing={editing}
errors={errors}
fields={fields}
onPositionsChange={_onPositionsChange}
Expand Down Expand Up @@ -158,7 +160,7 @@ const WidgetGrid = ({
]);

const grid = widgets && widgets.length > 0 ? (
<StyledReactGridContainer focusedWidget={focusedWidget}
<StyledReactGridContainer hasFocusedWidget={!!focusedWidget?.id}
columns={{
xxl: 12,
xl: 12,
Expand All @@ -167,7 +169,7 @@ const WidgetGrid = ({
sm: 12,
xs: 12,
}}
isResizable={!focusedWidget}
isResizable={!focusedWidget?.id}
locked={locked}
measureBeforeMount
onPositionsChange={onPositionsChange}
Expand Down
92 changes: 45 additions & 47 deletions graylog2-web-interface/src/views/components/WidgetQueryControls.tsx
Expand Up @@ -86,53 +86,51 @@ const WidgetQueryControls = ({ availableStreams, globalOverride }: Props) => {
<>
{isGloballyOverridden && <ResetOverrideHint />}
<Wrapper>
<>
<TopRow>
<Col md={4}>
<TimeRangeInput disabled={isGloballyOverridden}
onChange={(nextTimeRange) => setFieldValue('timerange', nextTimeRange)}
value={values?.timerange}
hasErrorOnMount={!isValid} />
</Col>

<Col md={8}>
<Field name="streams">
{({ field: { name, value, onChange } }) => (
<StreamsFilter value={value}
disabled={isGloballyOverridden}
streams={availableStreams}
onChange={(newStreams) => onChange({ target: { value: newStreams, name } })} />
)}
</Field>
</Col>
</TopRow>

<Row className="no-bm">
<Col md={12}>
<div className="pull-right search-help">
<DocumentationLink page={DocsHelper.PAGES.SEARCH_QUERY_LANGUAGE}
title="Search query syntax documentation"
text={<Icon name="lightbulb" type="regular" />} />
</div>
<SearchButton disabled={isGloballyOverridden || isSubmitting || !isValid}
dirty={dirty} />

<Field name="queryString">
{({ field: { name, value, onChange } }) => (
<QueryInput value={value}
disabled={isGloballyOverridden}
placeholder={'Type your search query here and press enter. E.g.: ("not found" AND http) OR http_response_code:[400 TO 404]'}
onChange={(newQuery) => {
onChange({ target: { value: newQuery, name } });

return Promise.resolve(newQuery);
}}
onExecute={handleSubmit as () => void} />
)}
</Field>
</Col>
</Row>
</>
<TopRow>
<Col md={4}>
<TimeRangeInput disabled={isGloballyOverridden}
onChange={(nextTimeRange) => setFieldValue('timerange', nextTimeRange)}
value={values?.timerange}
hasErrorOnMount={!isValid} />
</Col>

<Col md={8}>
<Field name="streams">
{({ field: { name, value, onChange } }) => (
<StreamsFilter value={value}
disabled={isGloballyOverridden}
streams={availableStreams}
onChange={(newStreams) => onChange({ target: { value: newStreams, name } })} />
)}
</Field>
</Col>
</TopRow>

<Row className="no-bm">
<Col md={12}>
<div className="pull-right search-help">
<DocumentationLink page={DocsHelper.PAGES.SEARCH_QUERY_LANGUAGE}
title="Search query syntax documentation"
text={<Icon name="lightbulb" type="regular" />} />
</div>
<SearchButton disabled={isGloballyOverridden || isSubmitting || !isValid}
dirty={dirty} />

<Field name="queryString">
{({ field: { name, value, onChange } }) => (
<QueryInput value={value}
disabled={isGloballyOverridden}
placeholder={'Type your search query here and press enter. E.g.: ("not found" AND http) OR http_response_code:[400 TO 404]'}
onChange={(newQuery) => {
onChange({ target: { value: newQuery, name } });

return Promise.resolve(newQuery);
}}
onExecute={handleSubmit as () => void} />
)}
</Field>
</Col>
</Row>
</Wrapper>
</>
);
Expand Down
Expand Up @@ -55,7 +55,7 @@ const FieldElement = styled.span.attrs({

const FieldActions = ({ children, disabled, element, menuContainer, name, type, queryId }: Props) => {
const actionContext = useContext(ActionContext);
const { setFocusedWidget } = useContext(WidgetFocusContext);
const { setWidgetFocusing } = useContext(WidgetFocusContext);
const allFieldActions = usePluginEntities('fieldActions');

const [open, setOpen] = useState(false);
Expand All @@ -82,7 +82,7 @@ const FieldActions = ({ children, disabled, element, menuContainer, name, type,
const { resetFocus = false } = action;

if (resetFocus) {
setFocusedWidget(undefined);
setWidgetFocusing(undefined);
}

_onMenuToggle();
Expand Down
Expand Up @@ -44,7 +44,7 @@ type Props = {

const ValueActions = ({ children, element, field, menuContainer, queryId, type, value }: Props) => {
const actionContext = useContext(ActionContext);
const { setFocusedWidget } = useContext(WidgetFocusContext);
const { unsetWidgetFocusing } = useContext(WidgetFocusContext);
const [open, setOpen] = useState(false);
const [overflowingComponents, setOverflowingComponents] = useState({});

Expand All @@ -68,7 +68,7 @@ const ValueActions = ({ children, element, field, menuContainer, queryId, type,
const { resetFocus } = action;

if (resetFocus) {
setFocusedWidget(undefined);
unsetWidgetFocusing();
}

_onMenuToggle();
Expand Down
Expand Up @@ -18,12 +18,35 @@ import * as React from 'react';

import { singleton } from 'views/logic/singleton';

export type WidgetFocusingState = {
id: string,
editing: false,
focusing: true,
}

export type WidgetEditingState = {
id: string,
editing: true,
focusing: true,
}

export type FocusContextState = WidgetFocusingState | WidgetEditingState;

export type WidgetFocusContextType = {
focusedWidget: string | undefined | null,
setFocusedWidget: (focusedWidget: string | undefined | null) => void,
focusedWidget: FocusContextState | undefined,
setWidgetFocusing: (widgetId: string) => void,
setWidgetEditing: (widgetId: string) => void,
unsetWidgetFocusing: () => void,
unsetWidgetEditing: () => void,
};

const defaultContext = { focusedWidget: undefined, setFocusedWidget: () => {} };
const defaultContext = {
focusedWidget: undefined,
setWidgetFocusing: () => {},
setWidgetEditing: () => {},
unsetWidgetFocusing: () => {},
unsetWidgetEditing: () => {},
};

const WidgetFocus = React.createContext<WidgetFocusContextType>(defaultContext);

Expand Down

0 comments on commit d01554d

Please sign in to comment.