Files
nebula.js/commands/serve/web/components/FieldsPopover.jsx
Christoffer Åström 7f45fbc6c5 feat: long running queries (#194)
* feat: long running queries

* fix: snapshotting and exporting

* chore: treat console as error

We default to error for console now
Added overrides for current use cases
This ensure not getting console logs in without overriding
2019-11-28 23:29:17 +01:00

190 lines
5.7 KiB
JavaScript

import React, { useContext, useState, useMemo } from 'react';
import { Popover, List, ListSubheader, ListItem, ListItemText, ListItemIcon, Divider } from '@material-ui/core';
import parse from 'autosuggest-highlight/parse';
import match from 'autosuggest-highlight/match';
import { ChevronRight, ChevronLeft } from '@nebula.js/ui/icons';
import { useTheme } from '@nebula.js/ui/theme';
import useModel from '@nebula.js/nucleus/src/hooks/useModel';
import useLayout from '@nebula.js/nucleus/src/hooks/useLayout';
import useLibraryList from '../hooks/useLibraryList';
import AppContext from '../contexts/AppContext';
import Search from './Search';
const Field = ({ field, onSelect, sub, parts }) => (
<ListItem button onClick={() => onSelect(field.qName)} data-key={field.qName}>
{parts.map((part, ix) => (
<ListItemText
component="span"
// eslint-disable-next-line react/no-array-index-key
key={ix}
style={part.highlight ? { flex: '0 1 auto', backgroundColor: '#FFC72A' } : { flex: '0 1 auto' }}
>
{part.text}
</ListItemText>
))}
{sub && <ChevronRight fontSize="small" />}
</ListItem>
);
const LibraryItem = ({ item, onSelect, parts }) => (
<ListItem button onClick={() => onSelect(item.qInfo)} data-key={item.qInfo.qId}>
{parts.map((part, ix) => (
<ListItemText
component="span"
// eslint-disable-next-line react/no-array-index-key
key={ix}
style={part.highlight ? { flex: '0 1 auto', backgroundColor: '#FFC72A' } : { flex: '0 1 auto' }}
>
{part.text}
</ListItemText>
))}
</ListItem>
);
const Aggr = ({ aggr, field, onSelect }) => (
<ListItem button onClick={() => onSelect(aggr)} data-key={aggr}>
<ListItemText>{`${aggr}(${field})`}</ListItemText>
</ListItem>
);
const LibraryList = ({ app, onSelect, title = '', type = 'dimension', searchTerm = '' }) => {
const [libraryItems] = useLibraryList(app, type);
const sortedLibraryItems = useMemo(
() => libraryItems.slice().sort((a, b) => a.qData.title.toLowerCase().localeCompare(b.qData.title.toLowerCase())),
[libraryItems]
);
return libraryItems.length > 0 ? (
<>
<ListSubheader component="div" style={{ backgroundColor: 'inherit' }}>
{title}
</ListSubheader>
{sortedLibraryItems.map(item => {
const matches = match(item.qData.title, searchTerm);
const parts = parse(item.qData.title, matches);
if (searchTerm && matches.length === 0) return null;
return <LibraryItem key={item.qInfo.qId} item={item} onSelect={onSelect} parts={parts} />;
})}
</>
) : null;
};
export default function FieldsPopover({ alignTo, show, close, onSelected, type }) {
const app = useContext(AppContext);
const [selectedField, setSelectedField] = useState(null);
const [searchTerm, setSearchTerm] = useState('');
const theme = useTheme();
const [model] = useModel(
{
qInfo: {
qType: 'FieldList',
qId: 'FieldList',
},
qFieldListDef: {
qShowDerivedFelds: false,
qShowHidden: false,
qShowSemantic: true,
qShowSrcTables: true,
qShowSystem: false,
},
},
app
);
const [layout] = useLayout({ model, app });
const fields = useMemo(
() =>
(layout ? layout.qFieldList.qItems || [] : [])
.slice()
.sort((a, b) => a.qName.toLowerCase().localeCompare(b.qName.toLowerCase())),
[layout]
);
const onSelect = s => {
if (s && s.qId) {
onSelected(s);
close();
} else if (type === 'measure') {
setSelectedField(s);
} else {
onSelected({
field: s,
});
close();
}
};
const onAggregateSelected = s => {
onSelected({
field: selectedField,
aggregation: s,
});
close();
};
return (
<Popover
open={show}
onClose={close}
anchorEl={alignTo.current}
marginThreshold={theme.spacing(1)}
elevation={3}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'center',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'center',
}}
PaperProps={{
style: { minWidth: '250px', maxHeight: '300px', background: theme.palette.background.lightest },
}}
>
<Search onChange={setSearchTerm} />
{selectedField && (
<List dense component="nav">
<ListItem button onClick={() => setSelectedField(null)}>
<ListItemIcon>
<ChevronLeft />
</ListItemIcon>
<ListItemText>Back</ListItemText>
</ListItem>
<Divider />
<ListSubheader component="div">Aggregation</ListSubheader>
{['sum', 'count', 'avg', 'min', 'max'].map(v => (
<Aggr key={v} aggr={v} field={selectedField} onSelect={onAggregateSelected} />
))}
</List>
)}
{!selectedField && fields.length > 0 && (
<List dense component="nav" style={{ background: theme.palette.background.lightest }}>
<LibraryList
app={app}
onSelect={onSelect}
type={type}
title={type === 'measure' ? 'Measures' : 'Dimensions'}
searchTerm={searchTerm}
/>
<ListSubheader component="div" style={{ backgroundColor: 'inherit' }}>
Fields
</ListSubheader>
{fields.map(field => {
const matches = match(field.qName, searchTerm);
const parts = parse(field.qName, matches);
if (searchTerm && matches.length === 0) return null;
return <Field key={field.qName} field={field} onSelect={onSelect} sub={type === 'measure'} parts={parts} />;
})}
</List>
)}
</Popover>
);
}