Skip to content

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

Merged
merged 4 commits into from
Aug 12, 2024
Merged

Add columns to Data View. #7281

merged 4 commits into from
Aug 12, 2024

Conversation

coltonw
Copy link
Contributor

@coltonw coltonw commented Aug 2, 2024

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.
  • The correct query is used. (Refer to this list of queries)
  • 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.

Signed-off-by: Will Colton <will.colton@hpe.com>
Signed-off-by: Will Colton <will.colton@hpe.com>
@coltonw
Copy link
Contributor Author

coltonw commented Aug 2, 2024

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.

@jcfilben
Copy link
Collaborator

jcfilben commented Aug 5, 2024

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:

  • Adding columns to the view in the Data component might be too specific to the DataTable use case because columns only applies to DataTable views and the Data component not only supports DataTable but also List, Cards, and other data driven components.

  • Regardless of whether we support columns in view we should support having a way to control the initial visibility of the column from the options prop of the DataTableColumns component. This allows someone to manage the visibility without having to enter into a controlled state of the Data component via view and onView.

Can we explore an approach where we don't add columns to view (we might consider this at a later point in time), and instead handle managing the initial visibility of the columns within the DataTableColumns options property? For example something like this:

<DataTableColumns 
  options={
    [
      { label: 'Name', property: 'name' },
      { label: 'Age', property: 'age', defaultVisible: false },
    ]
  }
/>

@jcfilben
Copy link
Collaborator

jcfilben commented Aug 8, 2024

I realized that the data view is already passing view.columns through onView, so we might reconsider the above comment. I'll keep you posted

@jcfilben
Copy link
Collaborator

We are going to move forward with this approach

@britt6612 britt6612 self-requested a review August 12, 2024 20:40
@britt6612 britt6612 requested a review from taysea August 12, 2024 20:40
Copy link
Collaborator

@taysea taysea left a 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:

  1. Select "view" with predefined filters and columns
  2. Change visible columns --> view not longer in data view
  3. "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',
};

@taysea taysea requested review from jcfilben and MikeKingdom August 12, 2024 20:50
Copy link
Collaborator

@jcfilben jcfilben left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants