Skip to content
Snippets Groups Projects
Unverified Commit 5389aee5 authored by Ryan Laurie's avatar Ryan Laurie Committed by GitHub
Browse files

Add Recent Item Links to link cards (#29197)

* Allow linkviz to show recent item suggestions

* unit test recent link adding

* oops
parent c46d7099
No related branches found
No related tags found
No related merge requests found
......@@ -5,6 +5,10 @@ export interface ModelObject {
}
export interface RecentItem {
cnt: number;
max_ts: string;
model_id: number;
user_id: number;
model: ModelType;
model_object: ModelObject;
}
......
......@@ -12,6 +12,10 @@ export const createMockRecentItem = (
): RecentItem => ({
model: "table",
model_object: createMockModelObject(),
cnt: 1,
model_id: 1,
max_ts: "2021-03-01T00:00:00.000Z",
user_id: 1,
...opts,
});
......
......@@ -12,6 +12,7 @@ import { isSyncCompleted } from "metabase/lib/syncing";
import { PLUGIN_MODERATION } from "metabase/plugins";
import {
ResultLink,
ResultButton,
ResultSpinner,
Title,
TitleWrapper,
......@@ -41,15 +42,27 @@ const propTypes = {
),
loading: PropTypes.bool,
onChangeLocation: PropTypes.func,
onClick: PropTypes.func,
className: PropTypes.string,
};
const getItemUrl = item => (isItemActive(item) ? Urls.modelToUrl(item) : "");
function RecentsList({ list, loading, onChangeLocation }) {
function RecentsList({ list, loading, onChangeLocation, onClick, className }) {
const handleSelectItem = item => {
onClick?.({
...item.model_object,
model: item.model,
name: item.model_object.display_name ?? item.model_object.name,
});
};
const { getRef, cursorIndex } = useListKeyboardNavigation({
list,
onEnter: item => onChangeLocation(getItemUrl(item)),
onEnter: item =>
onClick ? handleSelectItem(item) : onChangeLocation(getItemUrl(item)),
});
const [canShowLoader, setCanShowLoader] = useState(false);
const hasRecents = list?.length > 0;
......@@ -62,8 +75,11 @@ function RecentsList({ list, loading, onChangeLocation }) {
return null;
}
// we want to remove link behavior if we have an onClick handler
const ResultContainer = onClick ? ResultButton : ResultLink;
return (
<Root>
<Root className={className}>
<Header>{t`Recently viewed`}</Header>
<LoadingAndErrorWrapper loading={loading} noWrapper>
<React.Fragment>
......@@ -80,11 +96,14 @@ function RecentsList({ list, loading, onChangeLocation }) {
return (
<li key={key} ref={getRef(item)}>
<ResultLink
to={url}
compact={true}
active={active}
<ResultContainer
isSelected={cursorIndex === index}
active={active}
compact={true}
to={onClick ? undefined : url}
onClick={
onClick ? () => handleSelectItem(item) : undefined
}
>
<RecentListItemContent
align="start"
......@@ -114,7 +133,7 @@ function RecentsList({ list, loading, onChangeLocation }) {
</div>
{loading && <ResultSpinner size={24} borderWidth={3} />}
</RecentListItemContent>
</ResultLink>
</ResultContainer>
</li>
);
})}
......
......@@ -2,6 +2,7 @@ import styled from "@emotion/styled";
import { color } from "metabase/lib/colors";
import Link from "metabase/core/components/Link";
import Icon from "metabase/components/Icon";
import RecentsList from "metabase/nav/components/RecentsList";
export const DisplayLinkCardWrapper = styled.div`
padding: 0.5rem;
......@@ -38,7 +39,12 @@ export const CardLink = styled(Link)`
}
`;
export const SearchResultsContainer = styled.div`
export const BrandIconWithHorizontalMargin = styled(Icon)`
color: ${color("brand")};
margin: 0 0.5rem;
`;
const searchResultsStyles = `
padding-top: 0.5rem;
padding-bottom: 0.5rem;
min-width: 20rem;
......@@ -55,7 +61,10 @@ export const SearchResultsContainer = styled.div`
pointer-events: all;
`;
export const BrandIconWithHorizontalMargin = styled(Icon)`
color: ${color("brand")};
margin: 0 0.5rem;
export const SearchResultsContainer = styled.div`
${searchResultsStyles}
`;
export const StyledRecentsList = styled(RecentsList)`
${searchResultsStyles}
`;
......@@ -27,6 +27,7 @@ import {
DisplayLinkCardWrapper,
CardLink,
SearchResultsContainer,
StyledRecentsList,
} from "./LinkViz.styled";
import { isUrlString } from "./utils";
......@@ -131,15 +132,19 @@ function LinkViz({
return (
<EditLinkCardWrapper>
<TippyPopover
visible={!!url?.length && inputIsFocused && !isUrlString(url)}
visible={inputIsFocused && !isUrlString(url)}
content={
<SearchResultsContainer>
<SearchResults
searchText={url?.trim()}
onEntitySelect={handleEntitySelect}
models={MODELS_TO_SEARCH}
/>
</SearchResultsContainer>
!url?.trim?.().length && !entity ? (
<StyledRecentsList onClick={handleEntitySelect} />
) : (
<SearchResultsContainer>
<SearchResults
searchText={url?.trim()}
onEntitySelect={handleEntitySelect}
models={MODELS_TO_SEARCH}
/>
</SearchResultsContainer>
)
}
placement="bottom"
>
......
......@@ -7,7 +7,10 @@ import {
fireEvent,
getIcon,
} from "__support__/ui";
import { setupSearchEndpoints } from "__support__/server-mocks";
import {
setupSearchEndpoints,
setupRecentViewsEndpoints,
} from "__support__/server-mocks";
import type {
DashboardOrderedCard,
......@@ -17,6 +20,9 @@ import {
createMockDashboardCardWithVirtualCard,
createMockCollectionItem,
createMockCollection,
createMockRecentItem,
createMockTable,
createMockDashboard,
} from "metabase-types/api/mocks";
import LinkViz, { LinkVizProps } from "./LinkViz";
......@@ -231,6 +237,58 @@ describe("LinkViz", () => {
});
});
it("clicking a recent item should update the entity", async () => {
const recentTableItem = createMockRecentItem({
cnt: 20,
user_id: 20,
model: "table",
model_id: 121,
model_object: createMockTable({
id: 121,
name: "Table Uno",
display_name: "Table Uno",
db_id: 20,
}),
});
const recentDashboardItem = createMockRecentItem({
cnt: 20,
user_id: 20,
model: "dashboard",
model_id: 131,
model_object: createMockDashboard({
id: 131,
name: "Dashboard Uno",
}),
});
setupRecentViewsEndpoints([recentTableItem, recentDashboardItem]);
const { changeSpy } = setup({
isEditing: true,
dashcard: emptyLinkDashcard,
settings:
emptyLinkDashcard.visualization_settings as LinkCardVizSettings,
});
const searchInput = screen.getByPlaceholderText("https://example.com");
userEvent.click(searchInput);
await screen.findByText("Dashboard Uno");
userEvent.click(await screen.findByText("Table Uno"));
expect(changeSpy).toHaveBeenCalledWith({
link: {
entity: expect.objectContaining({
id: 121,
name: "Table Uno",
model: "table",
}),
},
});
});
it("shows a notice if the user doesn't have permissions to the entity", () => {
setup({
isEditing: false,
......
export const isUrlString = (str: string) => /^http/i.test(str);
export const isUrlString = (str?: string) => str && /^http/i.test(str);
import fetchMock from "fetch-mock";
import type { RecentItem } from "metabase-types/api";
export function setupRecentViewsEndpoints(recentlyViewedItems: RecentItem[]) {
fetchMock.get("path:/api/activity/recent_views", recentlyViewedItems);
}
export * from "./action";
export * from "./activity";
export * from "./card";
export * from "./collection";
export * from "./dashboard";
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment