You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@superset.apache.org by GitBox <gi...@apache.org> on 2022/08/16 13:28:27 UTC

[GitHub] [superset] geido commented on a diff in pull request #21090: feat: Adds the MetadataBar component

geido commented on code in PR #21090:
URL: https://github.com/apache/superset/pull/21090#discussion_r946739959


##########
superset-frontend/src/components/MetadataBar/ContentConfig.tsx:
##########
@@ -0,0 +1,138 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import React from 'react';
+import moment from 'moment';
+import { ensureIsArray, styled, t } from '@superset-ui/core';
+import Icons from 'src/components/Icons';
+import { ContentType } from './ContentType';
+
+const Header = styled.div`
+  font-weight: ${({ theme }) => theme.typography.weights.bold};
+`;
+
+const Info = ({
+  text,
+  header,
+}: {
+  text?: string | string[];
+  header?: string;
+}) => {
+  const values = ensureIsArray(text);
+  return (
+    <>
+      {header && <Header>{header}</Header>}
+      {values.map(value => (
+        <div key={value}>{value}</div>
+      ))}
+    </>
+  );
+};
+
+const config = (contentType: ContentType) => {
+  const { type } = contentType;
+
+  /**
+   * Tooltips are very similar. It's pretty much blocks
+   * of header/text pairs. That's why they are implemented here.
+   * If more complex tooltips emerge, then we should extract the different
+   * types of tooltips to their own components and reference them here.
+   */
+
+  if (type === 'dashboards') {

Review Comment:
   A switch statement for all these types could make this piece of code a little more cleaner imho. Also see my other comment about using an Enum 



##########
superset-frontend/src/components/MetadataBar/ContentType.ts:
##########
@@ -0,0 +1,80 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+export type Dashboards = {
+  type: 'dashboards';

Review Comment:
   What do you think about creating an Enum for these `type` params to avoid plain strings?



##########
superset-frontend/src/components/MetadataBar/index.tsx:
##########
@@ -0,0 +1,187 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import React, { useEffect, useRef, useState } from 'react';
+import { useResizeDetector } from 'react-resize-detector';
+import { uniqWith } from 'lodash';
+import { styled } from '@superset-ui/core';
+import { Tooltip } from 'src/components/Tooltip';
+import { ContentType } from './ContentType';
+import { config } from './ContentConfig';
+
+const HORIZONTAL_PADDING = 12;
+const VERTICAL_PADDING = 8;
+const ICON_PADDING = 8;
+const SPACE_BETWEEN_ITEMS = 16;
+const ICON_WIDTH = 16;
+const TEXT_MIN_WIDTH = 70;
+const TEXT_MAX_WIDTH = 150;
+const ORDER = {
+  dashboards: 0,
+  rows: 1,
+  sql: 2,
+  table: 3,
+  tags: 4,
+  description: 5,
+  owner: 6,
+  lastModified: 7,
+};
+
+const Bar = styled.div<{ count: number }>`
+  ${({ theme, count }) => `
+    display: flex;
+    align-items: center;
+    padding: ${VERTICAL_PADDING}px ${HORIZONTAL_PADDING}px;
+    background-color: ${theme.colors.grayscale.light4};
+    color: ${theme.colors.grayscale.base};
+    font-size: ${theme.typography.sizes.s}px;
+    min-width: ${
+      HORIZONTAL_PADDING * 2 +
+      (ICON_WIDTH + SPACE_BETWEEN_ITEMS) * count -
+      SPACE_BETWEEN_ITEMS
+    }px;
+  `}
+`;
+
+const StyledItem = styled.div<{
+  collapsed: boolean;
+  last: boolean;
+  onClick?: () => void;
+}>`
+  ${({ theme, collapsed, last, onClick }) => `
+    max-width: ${
+      ICON_WIDTH +
+      ICON_PADDING +
+      TEXT_MAX_WIDTH +
+      (last ? 0 : SPACE_BETWEEN_ITEMS)
+    }px;
+    min-width: ${ICON_WIDTH + (last ? 0 : SPACE_BETWEEN_ITEMS)}px;
+    overflow: hidden;
+    text-overflow: ${collapsed ? 'unset' : 'ellipsis'};
+    white-space: nowrap;
+    padding-right: ${last ? 0 : SPACE_BETWEEN_ITEMS}px;
+    text-decoration: ${onClick ? 'underline' : 'none'};
+    cursor: ${onClick ? 'pointer' : 'default'};
+    & > span {
+      color: ${onClick && collapsed ? theme.colors.primary.base : 'undefined'};
+      padding-right: ${collapsed ? 0 : ICON_PADDING}px;
+    }
+  `}
+`;
+
+// Make sure big tootips are truncated
+const TootipContent = styled.div`
+  display: -webkit-box;
+  -webkit-line-clamp: 20;
+  -webkit-box-orient: vertical;
+  overflow: hidden;
+  text-overflow: ellipsis;
+`;
+
+const Item = ({
+  barWidth,
+  contentType,
+  collapsed,
+  last = false,
+}: {
+  barWidth: number | undefined;
+  contentType: ContentType;
+  collapsed: boolean;
+  last?: boolean;
+}) => {
+  const { icon, title, tooltip = title } = config(contentType);
+  const [isTruncated, setIsTruncated] = useState(false);
+  const ref = useRef<HTMLDivElement>(null);
+  const Icon = icon;
+  const { type, onClick } = contentType;
+
+  useEffect(() => {
+    setIsTruncated(
+      ref.current ? ref.current.scrollWidth > ref.current.clientWidth : false,
+    );
+  }, [barWidth, setIsTruncated, contentType]);
+
+  const content = (
+    <StyledItem
+      collapsed={collapsed}
+      last={last}
+      onClick={onClick ? () => onClick(type) : undefined}
+      ref={ref}
+    >
+      <Icon iconSize="l" />
+      {!collapsed && title}
+    </StyledItem>
+  );
+  return isTruncated || collapsed || (tooltip && tooltip !== title) ? (
+    <Tooltip title={<TootipContent>{tooltip}</TootipContent>}>
+      {content}
+    </Tooltip>
+  ) : (
+    content
+  );
+};
+
+export interface MetadataBarProps {
+  /**
+   * Array of content type configurations. To see the available properties
+   * for each content type, check {@link ContentType}
+   */
+  items: ContentType[];
+}
+
+/**
+ * The metadata bar component is used to display additional information about an entity.
+ * Content types are predefined and consistent across the whole app. This means that
+ * they will be displayed and behave in a consistent manner, keeping the same ordering,
+ * information formatting, and interactions.
+ * To extend the list of content types, a developer needs to request the inclusion of the new type in the design system.
+ * This process is important to make sure the new type is reviewed by the design team, improving Superset consistency.
+ */
+const MetadataBar = ({ items }: MetadataBarProps) => {
+  const { width, ref } = useResizeDetector();
+  const uniqueItems = uniqWith(items, (a, b) => a.type === b.type);
+  const sortedItems = uniqueItems.sort((a, b) => ORDER[a.type] - ORDER[b.type]);
+  const count = sortedItems.length;
+  if (count < 2) {

Review Comment:
   Should we move this min and max to constants just so it stands out more?



##########
superset-frontend/src/components/MetadataBar/MetadataBar.test.tsx:
##########
@@ -0,0 +1,253 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import React from 'react';
+import { render, screen, within } from 'spec/helpers/testing-library';
+import userEvent from '@testing-library/user-event';
+import * as resizeDetector from 'react-resize-detector';
+import moment from 'moment';
+import { supersetTheme } from '@superset-ui/core';
+import { hexToRgb } from 'src/utils/colorUtils';
+import MetadataBar from '.';
+import { ContentType } from './ContentType';
+
+const DASHBOARD_TITLE = 'Added to 452 dashboards';
+const DASHBOARD_DESCRIPTION =
+  'To preview the list of dashboards go to "More" settings on the right.';
+const DESCRIPTION_VALUE = 'This is the description';
+const ROWS_TITLE = '500 rows';
+const SQL_TITLE = 'Click to view query';
+const TABLE_TITLE = 'database.schema.table';
+const CREATED_BY = 'Jane Smith';
+const DATE = new Date(Date.parse('2022-01-01'));
+const MODIFIED_BY = 'Jane Smith';
+const OWNERS = ['John Doe', 'Mary Wilson'];
+const TAGS = ['management', 'research', 'poc'];
+
+const runWithBarCollapsed = async (func: Function) => {
+  const spy = jest.spyOn(resizeDetector, 'useResizeDetector');
+  spy.mockReturnValue({ width: 80, ref: { current: undefined } });
+  await func();
+  spy.mockRestore();
+};
+
+const ITEMS: ContentType[] = [
+  {
+    type: 'dashboards',
+    title: DASHBOARD_TITLE,
+    description: DASHBOARD_DESCRIPTION,
+  },
+  {
+    type: 'description',
+    value: DESCRIPTION_VALUE,
+  },
+  {
+    type: 'lastModified',
+    value: DATE,
+    modifiedBy: MODIFIED_BY,
+  },
+  {
+    type: 'owner',
+    createdBy: CREATED_BY,
+    owners: OWNERS,
+    createdOn: DATE,
+  },
+  {
+    type: 'rows',
+    title: ROWS_TITLE,
+  },
+  {
+    type: 'sql',
+    title: SQL_TITLE,
+  },
+  {
+    type: 'table',
+    title: TABLE_TITLE,
+  },
+  {
+    type: 'tags',
+    values: TAGS,
+  },
+];
+
+test('renders an array of items', () => {
+  render(<MetadataBar items={ITEMS.slice(0, 2)} />);
+  expect(screen.getByText(DASHBOARD_TITLE)).toBeInTheDocument();
+  expect(screen.getByText(DESCRIPTION_VALUE)).toBeInTheDocument();
+});
+
+test('throws errors when out of min/max restrictions', () => {
+  const spy = jest.spyOn(console, 'error');
+  spy.mockImplementation(() => {});
+  expect(() => render(<MetadataBar items={ITEMS.slice(0, 1)} />)).toThrow(
+    'The minimum number of items for the metadata bar is 2.',

Review Comment:
   See my other comment about setting the max and min in constants



##########
superset-frontend/src/components/MetadataBar/MetadataBar.test.tsx:
##########
@@ -0,0 +1,253 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import React from 'react';
+import { render, screen, within } from 'spec/helpers/testing-library';
+import userEvent from '@testing-library/user-event';
+import * as resizeDetector from 'react-resize-detector';
+import moment from 'moment';
+import { supersetTheme } from '@superset-ui/core';
+import { hexToRgb } from 'src/utils/colorUtils';
+import MetadataBar from '.';
+import { ContentType } from './ContentType';
+
+const DASHBOARD_TITLE = 'Added to 452 dashboards';

Review Comment:
   I like that you assigned all of these in constants! 



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org