Skip to content

Commit

Permalink
rebase
Browse files Browse the repository at this point in the history
  • Loading branch information
rafpaf committed May 8, 2024
1 parent 3eb3c33 commit d7feef3
Show file tree
Hide file tree
Showing 15 changed files with 265 additions and 216 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { CollectionEssentials, SearchResult } from "metabase-types/api";
import type { CollectionEssentials } from "metabase-types/api";
import { createMockModelResult } from "metabase-types/api/mocks";

import { availableModelFilters, sortCollectionsByVerification } from "./utils";
Expand All @@ -24,7 +24,7 @@ describe("Utilities related to content verification", () => {
expect(sorted[1].name).toBe("Collection Alpha - unverified");
});
it("include a constant that defines a filter for only showing verified models", () => {
const models: SearchResult[] = [
const models = [
createMockModelResult({
name: "A verified model",
moderated_status: "verified",
Expand Down
9 changes: 6 additions & 3 deletions frontend/src/metabase-types/api/mocks/search.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import _ from "underscore";

import type {
SearchResult,
ModelResult,
SearchResponse,
SearchResult,
SearchScore,
} from "metabase-types/api";

Expand Down Expand Up @@ -81,5 +82,7 @@ export const createMockSearchResults = ({
};
};

export const createMockModelResult = (model: Partial<SearchResult>) =>
createMockSearchResult({ model: "dataset", ...model });
export const createMockModelResult = (
model: Partial<ModelResult>,
): ModelResult =>
createMockSearchResult({ ...model, model: "dataset" }) as ModelResult;
8 changes: 8 additions & 0 deletions frontend/src/metabase-types/api/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,11 @@ export interface SearchResult<
can_write: boolean | null;
}

export enum SortDirection {
Asc = "asc",
Desc = "desc",
}

export type SearchRequest = {
q?: string;
archived?: boolean;
Expand All @@ -139,3 +144,6 @@ export type SearchRequest = {
collection?: CollectionId;
namespace?: "snippets";
} & PaginationRequest;

/** Model retrieved through the search endpoint */
export type ModelResult = SearchResult<number, "dataset">;
40 changes: 31 additions & 9 deletions frontend/src/metabase/browse/components/BrowseModels.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import { useMemo } from "react";
import { useMemo, useState } from "react";
import { t } from "ttag";

import NoResults from "assets/img/no_results.svg";
import { useSearchQuery } from "metabase/api";
import type { SortingOptions } from "metabase/components/ItemsTable/BaseItemsTable";
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
import { color } from "metabase/lib/colors";
import { useSelector } from "metabase/lib/redux";
import { PLUGIN_CONTENT_VERIFICATION } from "metabase/plugins";
import { getLocale } from "metabase/setup/selectors";
import { Box, Flex, Group, Icon, Stack, Title } from "metabase/ui";
import type { SearchRequest, ModelResult } from "metabase-types/api";
import { SortDirection } from "metabase-types/api";

import { filterModels, type ActualModelFilters } from "../utils";

Expand All @@ -19,6 +24,7 @@ import {
} from "./BrowseContainer.styled";
import { ModelExplanationBanner } from "./ModelExplanationBanner";
import { ModelsTable } from "./ModelsTable";
import { sortModels } from "./utils";

const { availableModelFilters, useModelFilterSettings } =
PLUGIN_CONTENT_VERIFICATION;
Expand Down Expand Up @@ -62,23 +68,35 @@ export const BrowseModels = () => {
export const BrowseModelsBody = ({
actualModelFilters,
}: {
/** Mapping of filter names to true if the filter is active
* or false if it is inactive */
actualModelFilters: ActualModelFilters;
}) => {
const { data, error, isLoading } = useSearchQuery({
models: ["dataset"],
filter_items_in_personal_collection: "exclude",
model_ancestors: true,
const [sortingOptions, setSortingOptions] = useState<SortingOptions>({
sort_column: "name",
sort_direction: SortDirection.Asc,
});

const locale = useSelector(getLocale);
const localeCode: string | undefined = locale?.code;

const query: SearchRequest = {
models: ["dataset"], // 'model' in the sense of 'type of thing'
model_ancestors: true,
filter_items_in_personal_collection: "exclude",
};

const { data, error, isLoading } = useSearchQuery(query);

const models = useMemo(() => {
const unfilteredModels = data?.data ?? [];
const unfilteredModels = (data?.data as ModelResult[]) ?? [];
const filteredModels = filterModels(
unfilteredModels || [],
actualModelFilters,
availableModelFilters,
);
return filteredModels;
}, [data, actualModelFilters]);
return sortModels(filteredModels, sortingOptions, localeCode);
}, [data, actualModelFilters, sortingOptions, localeCode]);

if (error || isLoading) {
return (
Expand All @@ -94,7 +112,11 @@ export const BrowseModelsBody = ({
return (
<Stack spacing="md" mb="lg">
<ModelExplanationBanner />
<ModelsTable items={models} />
<ModelsTable
items={models}
sortingOptions={sortingOptions}
onSortingOptionsChange={setSortingOptions}
/>
</Stack>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,9 @@ import {
Breadcrumb,
CollectionBreadcrumbsWrapper,
} from "./CollectionBreadcrumbsWithTooltip.styled";
import { pathSeparatorChar } from "./constants";
import type { RefProp } from "./types";
import { getBreadcrumbMaxWidths } from "./utils";

const separatorCharacter = "/";
import { getBreadcrumbMaxWidths, getCollectionPathString } from "./utils";

export const CollectionBreadcrumbsWithTooltip = ({
collection,
Expand All @@ -31,9 +30,7 @@ export const CollectionBreadcrumbsWithTooltip = ({
const collections = (
(collection.effective_ancestors as CollectionEssentials[]) || []
).concat(collection);
const pathString = collections
.map(coll => getCollectionName(coll))
.join(` ${separatorCharacter} `);
const pathString = getCollectionPathString(collection);
const ellipsifyPath = collections.length > 2;
const shownCollections = ellipsifyPath
? [collections[0], collections[collections.length - 1]]
Expand Down Expand Up @@ -148,6 +145,6 @@ Ellipsis.displayName = "Ellipsis";

const PathSeparator = () => (
<Text color="text-light" mx="xs" py={1}>
{separatorCharacter}
{pathSeparatorChar}
</Text>
);
41 changes: 28 additions & 13 deletions frontend/src/metabase/browse/components/ModelsTable.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { t } from "ttag";

import EntityItem from "metabase/components/EntityItem";
import {
SortableColumnHeader,
type SortingOptions,
} from "metabase/components/ItemsTable/BaseItemsTable";
import {
ColumnHeader,
ItemCell,
Expand All @@ -12,10 +16,9 @@ import {
} from "metabase/components/ItemsTable/BaseItemsTable.styled";
import { Columns } from "metabase/components/ItemsTable/Columns";
import type { ResponsiveProps } from "metabase/components/ItemsTable/utils";
import { color } from "metabase/lib/colors";
import * as Urls from "metabase/lib/urls";
import { Icon, type IconProps } from "metabase/ui";
import type { Card, SearchResult } from "metabase-types/api";
import type { Card, ModelResult } from "metabase-types/api";

import { trackModelClick } from "../analytics";
import { getCollectionName, getIcon } from "../utils";
Expand All @@ -25,7 +28,9 @@ import { EllipsifiedWithMarkdown } from "./EllipsifiedWithMarkdown";
import { getModelDescription } from "./utils";

export interface ModelsTableProps {
items: SearchResult[];
items: ModelResult[];
sortingOptions?: SortingOptions;
onSortingOptionsChange?: (newSortingOptions: SortingOptions) => void;
}

const descriptionProps: ResponsiveProps = {
Expand All @@ -38,7 +43,11 @@ const collectionProps: ResponsiveProps = {
containerName: "ItemsTableContainer",
};

export const ModelsTable = ({ items }: ModelsTableProps) => {
export const ModelsTable = ({
items,
sortingOptions,
onSortingOptionsChange,
}: ModelsTableProps) => {
return (
<Table>
<colgroup>
Expand All @@ -55,27 +64,33 @@ export const ModelsTable = ({ items }: ModelsTableProps) => {
</colgroup>
<thead>
<tr>
<Columns.Name.Header />
<Columns.Name.Header
sortingOptions={sortingOptions}
onSortingOptionsChange={onSortingOptionsChange}
/>
<ColumnHeader {...descriptionProps}>{t`Description`}</ColumnHeader>
<ColumnHeader {...collectionProps}>{t`Collection`}</ColumnHeader>
<SortableColumnHeader
name="collection"
sortingOptions={sortingOptions}
onSortingOptionsChange={onSortingOptionsChange}
{...collectionProps}
>
{t`Collection`}
</SortableColumnHeader>
<Columns.RightEdge.Header />
</tr>
</thead>
<TBody>
{items.map((item: SearchResult) => (
{items.map((item: ModelResult) => (
<TBodyRow item={item} key={`${item.model}-${item.id}`} />
))}
</TBody>
</Table>
);
};

const TBodyRow = ({ item }: { item: SearchResult }) => {
const TBodyRow = ({ item }: { item: ModelResult }) => {
const icon = getIcon(item);
if (item.model === "card") {
icon.color = color("text-light");
}

const containerName = `collections-path-for-${item.id}`;

return (
Expand Down Expand Up @@ -125,7 +140,7 @@ const NameCell = ({
onClick,
icon,
}: {
item: SearchResult;
item: ModelResult;
testIdPrefix?: string;
onClick?: () => void;
icon: IconProps;
Expand Down
1 change: 1 addition & 0 deletions frontend/src/metabase/browse/components/constants.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const pathSeparatorChar = "/";
62 changes: 61 additions & 1 deletion frontend/src/metabase/browse/components/utils.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
import { t } from "ttag";

import type { CollectionEssentials, SearchResult } from "metabase-types/api";
import type { SortingOptions } from "metabase/components/ItemsTable/BaseItemsTable";
import type {
CollectionEssentials,
ModelResult,
SearchResult,
} from "metabase-types/api";
import { SortDirection } from "metabase-types/api";

import { getCollectionName } from "../utils";

import { pathSeparatorChar } from "./constants";

export const getBreadcrumbMaxWidths = (
collections: CollectionEssentials["effective_ancestors"],
totalUnitsOfWidthAvailable: number,
Expand Down Expand Up @@ -37,3 +45,55 @@ export const getModelDescription = (item: SearchResult) => {
return item.description;
}
};

export const getCollectionPathString = (collection: CollectionEssentials) => {
const ancestors: CollectionEssentials[] =
collection.effective_ancestors || [];
const collections = ancestors.concat(collection);
const pathString = collections
.map(coll => getCollectionName(coll))
.join(` ${pathSeparatorChar} `);
return pathString;
};

const getValueForSorting = (
model: ModelResult,
sort_column: keyof ModelResult,
): string => {
if (sort_column === "collection") {
return getCollectionPathString(model.collection);
} else {
return model[sort_column];
}
};

export const isValidSortColumn = (
sort_column: string,
): sort_column is keyof ModelResult => {
return ["name", "collection"].includes(sort_column);
};

export const sortModels = (
models: ModelResult[],
sortingOptions: SortingOptions,
localeCode: string = "en",
) => {
const { sort_column, sort_direction } = sortingOptions;

if (!isValidSortColumn(sort_column)) {
console.error("Invalid sort column", sort_column);
return models;
}

return [...models].sort((a, b) => {
const aValue = getValueForSorting(a, sort_column);
const bValue = getValueForSorting(b, sort_column);
const [firstValue, secondValue] =
sort_direction === SortDirection.Asc
? [aValue, bValue]
: [bValue, aValue];
return firstValue.localeCompare(secondValue, localeCode, {
sensitivity: "base",
});
});
};

0 comments on commit d7feef3

Please sign in to comment.