Skip to content

Commit

Permalink
Adds state management to Drag and Drop
Browse files Browse the repository at this point in the history
Signed-off-by: Ashwin Pc <ashwinpc@amazon.com>
  • Loading branch information
ashwin-pc committed Dec 3, 2021
1 parent 2107b99 commit 5a94320
Show file tree
Hide file tree
Showing 15 changed files with 248 additions and 118 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@
"@osd/ace": "1.0.0",
"@osd/monaco": "1.0.0",
"@osd/ui-shared-deps": "1.0.0",
"@reduxjs/toolkit": "^1.6.2",
"@types/yauzl": "^2.9.1",
"JSONStream": "1.3.5",
"abortcontroller-polyfill": "^1.4.0",
Expand Down
19 changes: 10 additions & 9 deletions src/plugins/wizard/public/application/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
* SPDX-License-Identifier: Apache-2.0
*/

import React, { useState, useEffect } from 'react';
import React, { useEffect } from 'react';
import { I18nProvider } from '@osd/i18n/react';
import { EuiPage } from '@elastic/eui';
import { DataPublicPluginStart, IndexPattern } from '../../../data/public';
import { DataPublicPluginStart } from '../../../data/public';
import { SideNav } from './components/side_nav';
import { DragDropProvider } from './utils/drag_drop/drag_drop_context';
import { useOpenSearchDashboards } from '../../../opensearch_dashboards_react/public';
Expand All @@ -15,21 +15,23 @@ import { Workspace } from './components/workspace';

import './app.scss';
import { TopNav } from './components/top_nav';
import { useTypedDispatch } from './utils/state_management';
import { setIndexPattern } from './utils/state_management/datasource_slice';

export const WizardApp = () => {
const {
services: { data },
} = useOpenSearchDashboards<WizardServices>();

const [indexPattern, setIndexPattern] = useIndexPattern(data);
useIndexPattern(data);

// Render the application DOM.
return (
<I18nProvider>
<DragDropProvider>
<EuiPage className="wizLayout">
<TopNav />
<SideNav indexPattern={indexPattern} setIndexPattern={setIndexPattern} />
<SideNav />
<Workspace />
</EuiPage>
</DragDropProvider>
Expand All @@ -39,16 +41,15 @@ export const WizardApp = () => {

// TODO: Temporary. Need to update it fetch the index pattern cohesively
function useIndexPattern(data: DataPublicPluginStart) {
const [indexPattern, setIndexPattern] = useState<IndexPattern | null>(null);
const dispatch = useTypedDispatch();

useEffect(() => {
const fetchIndexPattern = async () => {
const defaultIndexPattern = await data.indexPatterns.getDefault();
if (defaultIndexPattern) {
setIndexPattern(defaultIndexPattern);
dispatch(setIndexPattern(defaultIndexPattern));
}
};
fetchIndexPattern();
}, [data.indexPatterns]);

return [indexPattern, setIndexPattern] as const;
}, [data.indexPatterns, dispatch]);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,13 @@
import { EuiForm, EuiTitle } from '@elastic/eui';
import React from 'react';
import { i18n } from '@osd/i18n';
import { BUCKET_TYPES, METRIC_TYPES } from '../../../../../data/public';
import { ConfigSection } from './config_section';

import './config_panel.scss';

// TODO: Temp. Remove once visualizations can be refgistered and editor configs can be passed along
const CONFIG = {
x: {
title: 'X Axis',
allowedAggregation: BUCKET_TYPES.TERMS,
},
y: {
title: 'Y Axis',
allowedAggregation: METRIC_TYPES.AVG,
},
};
import { useTypedSelector } from '../../utils/state_management';

export function ConfigPanel() {
const sections = CONFIG;
const { configSections } = useTypedSelector((state) => state.config);

return (
<EuiForm className="wizConfigPanel">
Expand All @@ -35,8 +23,8 @@ export function ConfigPanel() {
})}
</h2>
</EuiTitle>
{Object.entries(sections).map(([sectionId, sectionProps], index) => (
<ConfigSection key={index} id={sectionId} {...sectionProps} onChange={() => {}} />
{Object.entries(configSections).map(([sectionId, sectionProps], index) => (
<ConfigSection key={index} id={sectionId} {...sectionProps} />
))}
</EuiForm>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,39 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { EuiButtonIcon, EuiFormRow, EuiPanel, EuiText } from '@elastic/eui';
import { EuiButtonIcon, EuiPanel, EuiText, EuiTitle } from '@elastic/eui';
import { i18n } from '@osd/i18n';
import React, { useCallback, useEffect, useState } from 'react';
import React, { useCallback } from 'react';
import { IndexPatternField } from 'src/plugins/data/common';
import { useDrop } from '../../utils/drag_drop';
import { useTypedDispatch, useTypedSelector } from '../../utils/state_management';
import {
addConfigSectionField,
removeConfigSectionField,
} from '../../utils/state_management/config_slice';

import './config_section.scss';

interface ConfigSectionProps {
id: string;
title: string;
onChange: Function;
}

export const ConfigSection = ({ title, id, onChange }: ConfigSectionProps) => {
const [currentField, setCurrentField] = useState<IndexPatternField>();
export const ConfigSection = ({ title, id }: ConfigSectionProps) => {
const dispatch = useTypedDispatch();
const { fields } = useTypedSelector((state) => state.config.configSections[id]);

const dropHandler = useCallback((field: IndexPatternField) => {
setCurrentField(field);
}, []);
const dropHandler = useCallback(
(field: IndexPatternField) => {
dispatch(
addConfigSectionField({
sectionId: id,
field,
})
);
},
[dispatch, id]
);
const [dropProps, { isValidDropTarget, dragData }] = useDrop('dataPlane', dropHandler);

const dropTargetString = dragData
Expand All @@ -31,24 +44,32 @@ export const ConfigSection = ({ title, id, onChange }: ConfigSectionProps) => {
defaultMessage: 'Click or drop to add',
});

useEffect(() => {
onChange(id, currentField);
}, [id, currentField, onChange]);

return (
<EuiFormRow label={title} className="wizConfigSection">
{currentField ? (
<EuiPanel paddingSize="s" className="wizConfigSection__field">
<EuiText grow size="m">
{currentField.displayName}
</EuiText>
<EuiButtonIcon
color="danger"
iconType="cross"
aria-label="clear-field"
onClick={() => setCurrentField(undefined)}
/>
</EuiPanel>
<div className="wizConfigSection">
<EuiTitle size="xxxs">
<h3 className="wizConfigSection__title">{title}</h3>
</EuiTitle>
{fields.length ? (
fields.map((field, index) => (
<EuiPanel key={index} paddingSize="s" className="wizConfigSection__field">
<EuiText grow size="m">
{field.displayName}
</EuiText>
<EuiButtonIcon
color="danger"
iconType="cross"
aria-label="clear-field"
onClick={() =>
dispatch(
removeConfigSectionField({
sectionId: id,
field,
})
)
}
/>
</EuiPanel>
))
) : (
<div
className={`wizConfigSection__dropTarget ${isValidDropTarget && 'validDropTarget'}`}
Expand All @@ -57,6 +78,6 @@ export const ConfigSection = ({ title, id, onChange }: ConfigSectionProps) => {
<EuiText size="s">{dropTargetString}</EuiText>
</div>
)}
</EuiFormRow>
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,7 @@ import {
import { FieldSelectorField } from './field_selector_field';

import './field_selector.scss';

interface FieldSelectorDeps {
indexFields: IndexPatternField[];
}
import { useTypedSelector } from '../../utils/state_management';

interface IFieldCategories {
categorical: IndexPatternField[];
Expand All @@ -40,7 +37,8 @@ const META_FIELDS: string[] = [
OPENSEARCH_FIELD_TYPES._TYPE,
];

export const FieldSelector = ({ indexFields }: FieldSelectorDeps) => {
export const FieldSelector = () => {
const indexFields = useTypedSelector((state) => state.dataSource.visualizableFields);
const fields = indexFields?.reduce<IFieldCategories>(
(fieldGroups, currentField) => {
const category = getFieldCategory(currentField);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,15 @@
*/

import React from 'react';
import { IndexPatternField } from '../../../../../data/public';
import { FieldSelector } from './field_selector';
import { ConfigPanel } from './config_panel';

import './index.scss';

interface DataTabDeps {
indexFields: IndexPatternField[];
}

export const DataTab = ({ indexFields }: DataTabDeps) => {
export const DataTab = () => {
return (
<div className="wizDataTab">
<FieldSelector indexFields={indexFields} />
<FieldSelector />
<ConfigPanel />
</div>
);
Expand Down
56 changes: 9 additions & 47 deletions src/plugins/wizard/public/application/components/side_nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,66 +3,38 @@
* SPDX-License-Identifier: Apache-2.0
*/

import React, { useState, useEffect } from 'react';
import React from 'react';
import { i18n } from '@osd/i18n';

import {
EuiFormLabel,
EuiFlexGroup,
EuiFlexItem,
EuiTabbedContent,
EuiTabbedContentTab,
} from '@elastic/eui';
import { EuiFormLabel, EuiTabbedContent, EuiTabbedContentTab } from '@elastic/eui';

import { IndexPattern, IndexPatternField, OSD_FIELD_TYPES } from '../../../../data/public';
import { DataTab } from './data_tab';
import { useOpenSearchDashboards } from '../../../../opensearch_dashboards_react/public';
import { WizardServices } from '../../types';
import { StyleTab } from './style_tab';

import './side_nav.scss';
import { useTypedDispatch, useTypedSelector } from '../utils/state_management';
import { setIndexPattern } from '../utils/state_management/datasource_slice';

interface SideNavDeps {
indexPattern: IndexPattern | null;
setIndexPattern: React.Dispatch<React.SetStateAction<IndexPattern | null>>;
}

const ALLOWED_FIELDS: string[] = [OSD_FIELD_TYPES.STRING, OSD_FIELD_TYPES.NUMBER];

export const SideNav = ({ indexPattern, setIndexPattern }: SideNavDeps) => {
export const SideNav = () => {
const {
services: {
data,
savedObjects: { client: savedObjectsClient },
},
} = useOpenSearchDashboards<WizardServices>();
const { IndexPatternSelect } = data.ui;
const [indexFields, setIndexFields] = useState<IndexPatternField[]>([]);

// Fetch the default index pattern using the `data.indexPatterns` service, as the component is mounted.
useEffect(() => {
const setDefaultIndexPattern = async () => {
const defaultIndexPattern = await data.indexPatterns.getDefault();
setIndexPattern(defaultIndexPattern);
};

setDefaultIndexPattern();
}, [data, setIndexPattern]);

// Update the fields list every time the index pattern is modified.
useEffect(() => {
const fields = indexPattern?.fields;

setIndexFields(fields?.filter(isValidField) || []);
}, [indexPattern]);
const { indexPattern } = useTypedSelector((state) => state.dataSource);
const dispatch = useTypedDispatch();

const tabs: EuiTabbedContentTab[] = [
{
id: 'data-tab',
name: i18n.translate('wizard.nav.dataTab.title', {
defaultMessage: 'Data',
}),
content: <DataTab indexFields={indexFields} />,
content: <DataTab />,
},
{
id: 'style-tab',
Expand All @@ -89,7 +61,7 @@ export const SideNav = ({ indexPattern, setIndexPattern }: SideNavDeps) => {
indexPatternId={indexPattern?.id || ''}
onChange={async (newIndexPatternId: any) => {
const newIndexPattern = await data.indexPatterns.get(newIndexPatternId);
setIndexPattern(newIndexPattern);
dispatch(setIndexPattern(newIndexPattern));
}}
isClearable={false}
/>
Expand All @@ -98,13 +70,3 @@ export const SideNav = ({ indexPattern, setIndexPattern }: SideNavDeps) => {
</section>
);
};

// TODO: Temporary validate function
// Need to identify hopw to get fieldCounts to use the standard filter and group functions
function isValidField(field: IndexPatternField): boolean {
const isAggregatable = field.aggregatable === true;
const isNotScripted = !field.scripted;
const isAllowed = ALLOWED_FIELDS.includes(field.type);

return isAggregatable && isNotScripted && isAllowed;
}
9 changes: 4 additions & 5 deletions src/plugins/wizard/public/application/components/top_nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
* SPDX-License-Identifier: Apache-2.0
*/

import React, { useMemo, useState } from 'react';
import { IndexPattern } from '../../../../data/public';
import React, { useMemo } from 'react';
import { PLUGIN_ID } from '../../../common';
import { useOpenSearchDashboards } from '../../../../opensearch_dashboards_react/public';
import { getTopNavconfig } from '../utils/get_top_nav_config';
import { WizardServices } from '../../types';

import './top_nav.scss';
import { useTypedSelector } from '../utils/state_management';

export const TopNav = () => {
const { services } = useOpenSearchDashboards<WizardServices>();
Expand All @@ -22,8 +22,7 @@ export const TopNav = () => {
} = services;

const config = useMemo(() => getTopNavconfig(services), [services]);
// TODO: Set index pattern/data source here. Filters wont show up until you do
const [indexPatterns, setIndexPatterns] = useState<IndexPattern[]>([]);
const { indexPattern } = useTypedSelector((state) => state.dataSource);

return (
<div className="wizTopNav">
Expand All @@ -34,7 +33,7 @@ export const TopNav = () => {
showSearchBar={true}
useDefaultBehaviors={true}
screenTitle="Test"
indexPatterns={indexPatterns}
indexPatterns={indexPattern ? [indexPattern] : []}
/>
</div>
);
Expand Down
Loading

0 comments on commit 5a94320

Please sign in to comment.