Skip to content
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

feat: Add survey banner to Explorer #1063

Merged
merged 18 commits into from
Aug 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .infra/rdev/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ stack:
services:
explorer:
image:
tag: sha-3cf6306f
tag: sha-4c9517de
replicaCount: 1
env:
# env vars common to all deployment stages
Expand Down
42 changes: 41 additions & 1 deletion client/__tests__/e2e/e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
pageURLSpatial,
} from "../common/constants";
import {
closeBottomBanner,
conditionallyToggleSidePanel,
goToPage,
shouldSkipTests,
Expand Down Expand Up @@ -207,6 +208,33 @@
});
});


describe("bottom banner", () => {
const SURVEY_LINK = "https://airtable.com/app8fNSQ8ieIiHLOv/shrmD31azkGtSupmO";
test("bottom banner appears", async ({ page }, testInfo) => {
await goToPage(page, url);

const bottomBanner = page.getByTestId("bottom-banner");

await expect(bottomBanner).toBeVisible();

await expect(page.getByText("quick survey")).toHaveAttribute(
"href",
SURVEY_LINK
);

await snapshotTestGraph(page, testInfo);
});
test("bottom banner disappears", async ({ page }, testInfo) => {
await goToPage(page, url);

const bottomBanner = await closeBottomBanner(page)
await expect(bottomBanner).not.toBeVisible();

await snapshotTestGraph(page, testInfo);
});
});

test("resize graph", async ({ page }, testInfo) => {
skipIfSidePanel(graphTestId, MAIN_PANEL);

Expand Down Expand Up @@ -314,6 +342,8 @@

await conditionallyToggleSidePanel(page, graphTestId, SIDE_PANEL);

await closeBottomBanner(page);

const originalCellCount = await getCellSetCount(1, page);

for (const cellset of data.cellsets.lasso) {
Expand Down Expand Up @@ -506,6 +536,8 @@

await conditionallyToggleSidePanel(page, graphTestId, SIDE_PANEL);

await closeBottomBanner(page)

const lassoSelection = await calcDragCoordinates(
graphTestId,
data.subset.lasso["coordinates-as-percent"],
Expand Down Expand Up @@ -717,6 +749,7 @@
});

describe("graph overlay", () => {

test("transform centroids correctly", async ({ page }, testInfo) => {
skipIfSidePanel(graphTestId, MAIN_PANEL);

Expand Down Expand Up @@ -815,6 +848,7 @@
}, testInfo) => {
await goToPage(page, url);
await conditionallyToggleSidePanel(page, graphTestId, SIDE_PANEL);
await closeBottomBanner(page);

await tryUntil(
async () => {
Expand Down Expand Up @@ -871,6 +905,7 @@
test("lasso moves after pan", async ({ page }, testInfo) => {
await goToPage(page, url);
await conditionallyToggleSidePanel(page, graphTestId, SIDE_PANEL);
await closeBottomBanner(page);

await tryUntil(
async () => {
Expand Down Expand Up @@ -1642,7 +1677,7 @@

await page.getByTestId("download-graph-button").click();

expect(afterMinimizeDownloads.length).toBe(1);

Check failure on line 1680 in client/__tests__/e2e/e2e.test.ts

View workflow job for this annotation

GitHub Actions / smoke-tests

[chromium] › e2e/e2e.test.ts:1632:15 › dataset: super-cool-spatial.cxg › graph instance: layout-graph › Image Download › with side panel

1) [chromium] › e2e/e2e.test.ts:1632:15 › dataset: super-cool-spatial.cxg › graph instance: layout-graph › Image Download › with side panel Error: expect(received).toBe(expected) // Object.is equality Expected: 1 Received: 0 1678 | await page.getByTestId("download-graph-button").click(); 1679 | > 1680 | expect(afterMinimizeDownloads.length).toBe(1); | ^ 1681 | }); 1682 | }); 1683 | at /home/runner/work/single-cell-explorer/single-cell-explorer/client/__tests__/e2e/e2e.test.ts:1680:51
});
});

Expand Down Expand Up @@ -1701,7 +1736,12 @@
testInfo: TestInfo;
}) {
await goToPage(page, url);

await tryUntil(
async () => {
await closeBottomBanner(page);
},
{ page }
);
if (withSubset) {
await subset({ x1: 0.1, y1: 0.15, x2: 0.8, y2: 0.85 }, page, testInfo);
}
Expand Down
12 changes: 12 additions & 0 deletions client/__tests__/util/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,3 +304,15 @@ export function shouldSkipTests(
): boolean {
return graphTestId === SIDE_PANEL;
}


export async function closeBottomBanner(page: Page): Promise<Locator> {
const bottomBanner = page.getByTestId("bottom-banner");

if(bottomBanner) {
const bottomBannerClose = bottomBanner.getByRole("button");
await bottomBannerClose.click();
}

return bottomBanner;
}
7 changes: 7 additions & 0 deletions client/src/components/BottomBanner/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const BANNER_FEEDBACK_SURVEY_LINK =
"https://airtable.com/app8fNSQ8ieIiHLOv/shrmD31azkGtSupmO";
export const BOTTOM_BANNER_SURVEY_LINK_TEXT = "quick survey.";
export const BOTTOM_BANNER_SURVEY_TEXT = "Send us feedback with this";
export const BOTTOM_BANNER_LAST_CLOSED_TIME_KEY = "bottomBannerLastClosedTime";
export const BOTTOM_BANNER_EXPIRATION_TIME_MS = 30 * 24 * 60 * 60 * 1000; // 30 days
// export const BOTTOM_BANNER_EXPIRATION_TIME_MS = 30 * 1000; // 30 seconds
70 changes: 70 additions & 0 deletions client/src/components/BottomBanner/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import React, { memo } from "react";
import { connect } from "react-redux";
import {
BOTTOM_BANNER_ID,
StyledBanner,
StyledBottomBannerWrapper,
StyledLink,
} from "./style";
import {
BOTTOM_BANNER_SURVEY_LINK_TEXT,
BOTTOM_BANNER_SURVEY_TEXT,
} from "./constants";
import { AppDispatch, RootState } from "../../reducers";

export interface BottomBannerProps {
surveyLink: string;
showBottomBanner: boolean;
dispatch: AppDispatch;
}

const mapStateToProps = (state: RootState) => ({
showBottomBanner: state.showBottomBanner,
});

const mapDispatchToProps = (dispatch: AppDispatch) => ({
dispatch,
});

const BottomBanner = memo(
({
surveyLink,
showBottomBanner,
dispatch,
}: BottomBannerProps): JSX.Element | null => {
const setBottomBannerLastClosedTime = () => {
dispatch({
type: "update bottom banner last closed time",
time: Date.now(),
});
};

if (!showBottomBanner) return null;

return (
<>
<StyledBottomBannerWrapper
id={BOTTOM_BANNER_ID}
data-testid={BOTTOM_BANNER_ID}
>
<StyledBanner
dismissible
sdsType="primary"
onClose={setBottomBannerLastClosedTime}
// @ts-expect-error -- czifui Banner component types text prop as a string but the prop works with JSX as well
text={
<span>
{BOTTOM_BANNER_SURVEY_TEXT}
<StyledLink href={surveyLink} target="_blank" rel="noopener">
{BOTTOM_BANNER_SURVEY_LINK_TEXT}
</StyledLink>
</span>
}
/>
</StyledBottomBannerWrapper>
</>
);
}
);

export default connect(mapStateToProps, mapDispatchToProps)(BottomBanner);
63 changes: 63 additions & 0 deletions client/src/components/BottomBanner/style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import styled from "@emotion/styled";
import { Banner, Icon } from "czifui";
import { beta100, beta400, gray500 } from "../theme";

export const SKINNY_MODE_BREAKPOINT_WIDTH = 960;
export const BOTTOM_BANNER_ID = "bottom-banner";

export const StyledBanner = styled(Banner)`
@media (max-width: ${SKINNY_MODE_BREAKPOINT_WIDTH}px) {
padding: 8px 16px;
box-shadow: 0px 0px 4px 0px rgba(50, 50, 50, 0.75);
}
span {
font-family: "Roboto Condensed", "Helvetica Neue", "Helvetica", "Arial",
sans-serif;
font-weight: 400;
}
/**
* beta intent does not exist for SDS banner, but the colors do targeting
* specific id to overwrite style
*/
border-color: ${beta400} !important;
background-color: ${beta100};
color: black;

/* Hide default svg icon in the Banner as it is not in figma */
:first-of-type > div:first-of-type > div:first-of-type {
display: none;
}

/* Change close button icon default color */
button svg {
path {
fill: ${gray500};
}
}
`;

export const StyledBottomBannerWrapper = styled.div`
width: 100%;

/* Right behind modal overlay */
z-index: 19;
background-color: purple;
`;

export const StyledLink = styled.a`
padding: 0px 5px 0px 5px;
text-decoration-line: underline;
color: #8f5aff;
font-weight: 500;

:hover {
color: #5826c1;
}
`;

const STYLED_CLOSE_BUTTON_ICON_DENY_PROPS = ["hideCloseButton"];

export const StyledCloseButtonIcon = styled(Icon, {
shouldForwardProp: (prop) =>
!STYLED_CLOSE_BUTTON_ICON_DENY_PROPS.includes(prop),
})``;
57 changes: 31 additions & 26 deletions client/src/components/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { connect } from "react-redux";
import { ThemeProvider as EmotionThemeProvider } from "@emotion/react";
import { StylesProvider, ThemeProvider } from "@material-ui/core/styles";
import { theme } from "./theme";
import BottomBanner from "./BottomBanner";

import Controls from "./controls";
import DatasetSelector from "./datasetSelector/datasetSelector";
Expand All @@ -23,6 +24,7 @@ import Graph from "./graph/graph";
import DiffexNotice from "./diffexNotice";
import Scatterplot from "./scatterplot/scatterplot";
import PanelEmbedding from "./PanelEmbedding";
import { BANNER_FEEDBACK_SURVEY_LINK } from "./BottomBanner/constants";

interface StateProps {
loading: RootState["controls"]["loading"];
Expand Down Expand Up @@ -99,32 +101,35 @@ class App extends React.Component<StateProps & { dispatch: AppDispatch }> {
<Header tosURL={tosURL} privacyURL={privacyURL} />
)}
{loading || error ? null : (
<Layout
addTopPadding={!datasetMetadataError || isCellGuideCxg}
renderGraph={(viewportRef: HTMLDivElement) => (
<>
<GlobalHotkeys />
<Controls>
<MenuBar />
</Controls>
<Legend />
<Graph
viewportRef={viewportRef}
key={graphRenderCounter}
/>
{scatterplotXXaccessor && scatterplotYYaccessor && (
<Scatterplot />
)}
<PanelEmbedding />
<Controls bottom={0}>
<DatasetSelector />
</Controls>
</>
)}
>
<LeftSideBar />
<RightSideBar />
</Layout>
<>
<Layout
addTopPadding={!datasetMetadataError || isCellGuideCxg}
renderGraph={(viewportRef: HTMLDivElement) => (
<>
<GlobalHotkeys />
<Controls>
<MenuBar />
</Controls>
<Legend />
<Graph
viewportRef={viewportRef}
key={graphRenderCounter}
/>
{scatterplotXXaccessor && scatterplotYYaccessor && (
<Scatterplot />
)}
<PanelEmbedding />
<Controls bottom={0}>
<DatasetSelector />
</Controls>
</>
)}
>
<LeftSideBar />
<RightSideBar />
</Layout>
<BottomBanner surveyLink={BANNER_FEEDBACK_SURVEY_LINK} />
</>
)}
</ThemeProvider>
</EmotionThemeProvider>
Expand Down
Loading
Loading