-
Notifications
You must be signed in to change notification settings - Fork 1k
Add columns to Data View. #7281
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Signed-off-by: Will Colton <will.colton@hpe.com>
Signed-off-by: Will Colton <will.colton@hpe.com>
Just to be clear, the work here was basically to do nothing. All form data, including columns, is already controllable by the view, so all I had to do was fix some types and add examples and testing. |
Hey @coltonw thank you for working on this! I chatted with the rest of the grommet team about this PR, here is some of the feedback:
Can we explore an approach where we don't add
|
I realized that the data view is already passing |
We are going to move forward with this approach |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tested locally as well. This behavior makes:
- Select "view" with predefined filters and columns
- Change visible columns --> view not longer in data view
- "Clear filters" --> selected columns are still reflected rather than being cleared out
view-columns.mov
import React, { useEffect, useState } from 'react';
import {
Box,
DataTable,
Data,
Grid,
Pagination,
Text,
Tip,
DataSearch,
DataFilters,
DataView,
DataTableColumns,
Toolbar,
} from 'grommet';
import { StatusCritical } from 'grommet-icons';
const buildQuery = (view) => {
const query = {};
const properties = view?.properties || [];
Object.keys(properties).forEach((property) => {
switch (property) {
case 'success':
if (properties.success.length === 1) {
query[property] = properties.success[0] === 'Successful';
}
break;
case 'rocket':
query.rocket = {
$in: properties.rocket,
};
break;
default:
query[property] = properties[property];
}
});
if (view?.search) query.$text = { $search: view.search };
return query;
};
const fetchLaunches = async (view) => {
const query = buildQuery(view);
const sort = {
[view?.sort?.property || 'name']: view?.sort?.direction || 'asc',
};
const body = {
options: {
populate: [
{
path: 'rocket',
select: { name: 1 },
},
],
sort,
select: ['name', 'success', 'failures'],
limit: view?.step || 10,
page: view?.page || 1,
},
query,
};
return fetch('https://api.spacexdata.com/v4/launches/query', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
}).then((response) => response.json());
};
const fetchRockets = async () => {
const body = {
options: {
sort: { name: 'asc' },
select: ['name', 'id'],
},
};
return fetch('https://api.spacexdata.com/v4/rockets/query', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
}).then((response) => response.json());
};
const columns = [
{
property: 'name',
header: 'Name',
size: 'small',
primary: true,
},
{
property: 'rocket.name',
header: 'Rocket',
size: 'xsmall',
sortable: false,
},
{
property: 'success',
header: 'Success',
size: 'xsmall',
align: 'center',
sortable: false,
render: (datum) => {
if (datum.success === false) {
const content = (
<Box width={{ max: 'medium' }}>
{datum.failures?.map(({ reason }) => (
<Text key={reason}>{reason}</Text>
))}
</Box>
);
return (
<Tip
plain
content={content}
dropProps={{
round: 'medium',
pad: 'small',
background: 'background-back',
}}
>
<Box>
<StatusCritical color="red" />
</Box>
</Tip>
);
}
return undefined;
},
},
];
const options = columns.map(({ header, property }) => ({
property,
label: header,
}));
const defaultView = {
search: '',
sort: { property: 'name', direction: 'asc' },
step: 10,
};
export const SpaceX = () => {
const [total, setTotal] = useState(0);
const [result, setResult] = useState({ data: [] });
const [rockets, setRockets] = useState([]);
const [view, setView] = useState(defaultView);
useEffect(() => {
fetchRockets().then((response) =>
setRockets(
response.docs.map(({ name, id }) => ({ value: id, label: name })),
),
);
}, []);
useEffect(() => {
fetchLaunches(view).then((response) => {
setResult({
data: response.docs,
filteredTotal: response.totalDocs,
page: response.page,
});
// The REST API doesn't return the unfiltered total in responses.
// Since the first request likely has no filtering, we'll likely use
// response.totalDocs the first time and prevTotal thereafter.
setTotal((prevTotal) => Math.max(prevTotal, response.totalDocs));
});
}, [view]);
return (
// Uncomment <Grommet> lines when using outside of storybook
// <Grommet theme={...}>
<Grid
flex={false}
pad="large"
columns={[['small', 'large']]}
justifyContent="center"
>
<Data
properties={{
rocket: { label: 'Rocket', options: rockets },
success: { label: 'Success', options: ['Successful', 'Failed'] },
}}
data={result.data}
total={total}
filteredTotal={result.filteredTotal}
defaultView={defaultView}
views={[
{
name: 'My view',
columns: ['name', 'rocket.name'],
properties: {
success: ['Successful'],
},
},
]}
view={view}
onView={setView}
>
<Toolbar>
<DataSearch />
<DataFilters layer />
<DataView />
<DataTableColumns options={options} drop />
</Toolbar>
<DataTable columns={columns} sortable />
{result.filteredTotal > view.step && (
<Pagination summary border="top" pad={{ vertical: 'xsmall' }} />
)}
</Data>
</Grid>
// </Grommet>
);
};
SpaceX.storyName = 'SpaceX';
SpaceX.args = {
full: true,
};
export default {
title: 'Data/Data/SpaceX',
};
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good!
What does this PR do?
Add columns as an option to data views, allowing for having a default set of columns on or off through the defaultView Data prop.
Where should the reviewer start?
index.d.ts
What testing has been done on this PR?
Manual testing, storybook testing, and automated tests.
How should this be manually tested?
Try a view with the new property.
Do Jest tests follow these best practices?
screen
is used for querying.asFragment()
is used for snapshot testing.Any background context you want to provide?
#7278
What are the relevant issues?
Screenshots (if appropriate)
Do the grommet docs need to be updated?
Probably
Should this PR be mentioned in the release notes?
Yes probably.
Is this change backwards compatible or is it a breaking change?
Backwards compatible.