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(client): add "email us" to the session class option #3073

Merged
merged 16 commits into from
Apr 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 3 additions & 1 deletion client/docker-entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ echo " UPLOAD_THRESHOLD=${UPLOAD_THRESHOLD}"
echo " HOMEPAGE=${HOMEPAGE}"
echo " CORE_API_VERSION_CONFIG=${CORE_API_VERSION_CONFIG}"
echo " USER_PREFERENCES_MAX_PINNED_PROJECTS=${USER_PREFERENCES_MAX_PINNED_PROJECTS}"
echo " SESSION_CLASS_EMAIL_US=${SESSION_CLASS_EMAIL_US}"
echo "==================================================="

echo "Privacy file contains the following markdown (first 5 lines):"
Expand Down Expand Up @@ -80,7 +81,8 @@ tee > "${NGINX_PATH}/config.json" << EOF
"STATUSPAGE_ID": "${STATUSPAGE_ID}",
"HOMEPAGE": ${HOMEPAGE},
"CORE_API_VERSION_CONFIG": ${CORE_API_VERSION_CONFIG},
"USER_PREFERENCES_MAX_PINNED_PROJECTS": ${USER_PREFERENCES_MAX_PINNED_PROJECTS}
"USER_PREFERENCES_MAX_PINNED_PROJECTS": ${USER_PREFERENCES_MAX_PINNED_PROJECTS},
"SESSION_CLASS_EMAIL_US": ${SESSION_CLASS_EMAIL_US}
}
EOF
echo "config.json created in ${NGINX_PATH}"
Expand Down
3 changes: 2 additions & 1 deletion client/run-telepresence.sh
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,8 @@ tee > ./public/config.json << EOF
"projectPath": "${HOMEPAGE_PROJECT_PATH}",
"datasetSlug": "${HOMEPAGE_DATASET_SLUG}"
},
"USER_PREFERENCES_MAX_PINNED_PROJECTS": ${USER_PREFERENCES_MAX_PINNED_PROJECTS:-5}
"USER_PREFERENCES_MAX_PINNED_PROJECTS": ${USER_PREFERENCES_MAX_PINNED_PROJECTS:-5},
"SESSION_CLASS_EMAIL_US": { "enabled": false }
}
EOF

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { skipToken } from "@reduxjs/toolkit/query";
import cx from "classnames";
import { useCallback, useEffect, useMemo } from "react";
import { useCallback, useContext, useEffect, useMemo } from "react";
import { ChevronDown } from "react-bootstrap-icons";
import Select, {
ClassNamesConfig,
Expand All @@ -36,8 +36,12 @@ import Select, {
} from "react-select";

import { ErrorAlert, WarnAlert } from "../../../../components/Alert";
import { ExternalLink } from "../../../../components/ExternalLinks";
import { Loader } from "../../../../components/Loader";
import { User } from "../../../../model/renkuModels.types";
import { ProjectStatistics } from "../../../../notebooks/components/session.types";
import AppContext from "../../../../utils/context/appContext";
import { DEFAULT_APP_PARAMS } from "../../../../utils/context/appParams.constants";
import useAppDispatch from "../../../../utils/customHooks/useAppDispatch.hook";
import useAppSelector from "../../../../utils/customHooks/useAppSelector.hook";
import useLegacySelector from "../../../../utils/customHooks/useLegacySelector.hook";
Expand Down Expand Up @@ -212,6 +216,7 @@ export const SessionClassOption = () => {
onChange={onChange}
/>
<SessionClassWarning currentSessionClass={currentSessionClass} />
<AskForComputeResources />
</div>
);
};
Expand Down Expand Up @@ -350,6 +355,44 @@ function SessionClassWarning({
);
}

function AskForComputeResources() {
const { params } = useContext(AppContext);
const SESSION_CLASS_EMAIL_US =
params?.SESSION_CLASS_EMAIL_US ??
DEFAULT_APP_PARAMS["SESSION_CLASS_EMAIL_US"];

const user = useLegacySelector<User>((state) => state.stateModel.user);

if (!SESSION_CLASS_EMAIL_US.enabled) {
return null;
}

const { email } = SESSION_CLASS_EMAIL_US;

const url = new URL(`mailto:${email.to}`);
if (email.subject) {
url.searchParams.set("subject", email.subject);
}
if (email.body) {
const name = (user?.data as { name: string })?.name || "<signature>";
const renderedBody = email.body.replace(
/[{][{]full_name[}][}]/g,
`${name}`
);
url.searchParams.set("body", renderedBody);
}
const urlStr = url.toString().replace(/[+]/g, "%20");

return (
<div className="small">
Need more compute resources?{" "}
<ExternalLink role="link" url={urlStr}>
Email us!
</ExternalLink>
</div>
);
}

interface SessionClassSelectorProps {
resourcePools: ResourcePool[];
currentSessionClass?: ResourceClass | undefined;
Expand Down
57 changes: 44 additions & 13 deletions client/src/landing/GetSarted/GetStarted.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,16 @@
*/
import { skipToken } from "@reduxjs/toolkit/query";
import cx from "classnames";
import { useContext } from "react";

import { ExternalDocsLink, ExternalLink } from "../../components/ExternalLinks";
import { CommandCopy } from "../../components/commandCopy/CommandCopy";
import EntityCardSkeleton from "../../components/list/EntityCardSkeleton";
import ListCard from "../../components/list/ListCard";
import { useProjectMetadataQuery } from "../../features/project/projectKg.api";
import { Docs, RenkuContactEmail } from "../../utils/constants/Docs";
import AppContext from "../../utils/context/appContext";
import { DEFAULT_APP_PARAMS } from "../../utils/context/appParams.constants";
import { mapMetadataKgResultToEntity } from "../../utils/helpers/KgSearchFunctions";
import { AnonymousHomeConfig } from "../anonymousHome.types";

Expand All @@ -34,7 +37,6 @@ interface GetStartedProps extends AnonymousHomeConfig {
}
export default function GetStarted(props: GetStartedProps) {
const projectPath = props.homeCustomized.projectPath;
const contactEmail = RenkuContactEmail;
const { sectionRef } = props;
const projectMetadataQuery = useProjectMetadataQuery(
projectPath ? { projectPath } : skipToken
Expand Down Expand Up @@ -91,18 +93,7 @@ export default function GetStarted(props: GetStartedProps) {
resources for researchers and instructors. Contact us to learn
more.
</p>
<ExternalLink
className={cx(
styles.btnContactUs,
"align-self-start",
"align-self-lg-center"
)}
color="outline-rk-green"
role="button"
id="Contact Us"
url={`mailto:${contactEmail}`}
title="Contact us"
/>
<ContactUsLink />
</div>
<div>
<p>
Expand Down Expand Up @@ -140,3 +131,43 @@ export default function GetStarted(props: GetStartedProps) {
</div>
);
}

function ContactUsLink() {
const { params } = useContext(AppContext);
const SESSION_CLASS_EMAIL_US =
params?.SESSION_CLASS_EMAIL_US ??
DEFAULT_APP_PARAMS["SESSION_CLASS_EMAIL_US"];

const emailTo = SESSION_CLASS_EMAIL_US.enabled
? SESSION_CLASS_EMAIL_US.email.to
: RenkuContactEmail;
const url = new URL(`mailto:${emailTo}`);

if (SESSION_CLASS_EMAIL_US.enabled && SESSION_CLASS_EMAIL_US.email.subject) {
url.searchParams.set("subject", SESSION_CLASS_EMAIL_US.email.subject);
}
if (SESSION_CLASS_EMAIL_US.enabled && SESSION_CLASS_EMAIL_US.email.body) {
const renderedBody = SESSION_CLASS_EMAIL_US.email.body.replace(
/[{][{]full_name[}][}]/g,
"<signature>"
);
url.searchParams.set("body", renderedBody);
}
const urlStr = url.toString().replace(/[+]/g, "%20");

return (
<ExternalLink
className={cx(
styles.btnContactUs,
"align-self-start",
"align-self-lg-center"
)}
color="outline-rk-green"
role="button"
id="Contact Us"
url={urlStr}
>
Contact Us
</ExternalLink>
);
}
5 changes: 5 additions & 0 deletions client/src/utils/context/appParams.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ const DEFAULT_UPLOAD_THRESHOLD: AppParams["UPLOAD_THRESHOLD"] = {
soft: 104_857_600,
};

const DEFAULT_SESSION_CLASS_EMAIL_US: AppParams["SESSION_CLASS_EMAIL_US"] = {
enabled: false,
};

export const DEFAULT_APP_PARAMS: AppParams = {
ANONYMOUS_SESSIONS: false,
BASE_URL: "",
Expand All @@ -84,6 +88,7 @@ export const DEFAULT_APP_PARAMS: AppParams = {
SENTRY_NAMESPACE: "",
SENTRY_SAMPLE_RATE: "0",
SENTRY_URL: "",
SESSION_CLASS_EMAIL_US: DEFAULT_SESSION_CLASS_EMAIL_US,
STATUSPAGE_ID: "",
TEMPLATES: DEFAULT_TEMPLATES,
UISERVER_URL: "",
Expand Down
18 changes: 18 additions & 0 deletions client/src/utils/context/appParams.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export interface AppParams {
SENTRY_NAMESPACE: string;
SENTRY_SAMPLE_RATE: string; // TODO: convert to number type
SENTRY_URL: string;
SESSION_CLASS_EMAIL_US: SessionClassEmailUsParams;
STATUSPAGE_ID: string;
TEMPLATES: TemplatesParams;
UISERVER_URL: string;
Expand Down Expand Up @@ -88,3 +89,20 @@ interface TemplatesRepositories {
export interface UploadThresholdParams {
soft: number;
}

export type SessionClassEmailUsParams =
| SessionClassEmailUsParamsDisabled
| SessionClassEmailUsParamsEnabled;

interface SessionClassEmailUsParamsDisabled {
enabled: false;
}

interface SessionClassEmailUsParamsEnabled {
enabled: true;
email: {
to: string;
subject: string;
body: string;
};
}
52 changes: 52 additions & 0 deletions client/src/utils/context/appParams.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import type {
AppParamsStrings,
PreviewThresholdParams,
PrivacyBannerLayoutParams,
SessionClassEmailUsParams,
TemplatesParams,
UploadThresholdParams,
} from "./appParams.types";
Expand Down Expand Up @@ -79,6 +80,7 @@ export function validatedAppParams(params: unknown): AppParams {
const PRIVACY_BANNER_LAYOUT = validatePrivacyBannerLayout(params_);
const TEMPLATES = validateTemplates(params_);
const UPLOAD_THRESHOLD = validateUploadThreshold(params_);
const SESSION_CLASS_EMAIL_US = validateSessionClassEmailUs(params_);

return {
ANONYMOUS_SESSIONS,
Expand All @@ -98,6 +100,7 @@ export function validatedAppParams(params: unknown): AppParams {
SENTRY_NAMESPACE,
SENTRY_SAMPLE_RATE,
SENTRY_URL,
SESSION_CLASS_EMAIL_US,
STATUSPAGE_ID,
TEMPLATES,
UISERVER_URL,
Expand Down Expand Up @@ -302,3 +305,52 @@ function validateUploadThreshold(params: RawAppParams): UploadThresholdParams {
: DEFAULT_APP_PARAMS["PREVIEW_THRESHOLD"].soft;
return { soft };
}

function validateSessionClassEmailUs(
params: RawAppParams
): SessionClassEmailUsParams {
const value = params["SESSION_CLASS_EMAIL_US"];
if (typeof value !== "object" || value == null) {
return DEFAULT_APP_PARAMS["SESSION_CLASS_EMAIL_US"];
}

const rawEmailUsParams = value as {
[key: string]: unknown;
};
const rawEmailUsEmailParams =
typeof rawEmailUsParams.email === "object" && rawEmailUsParams.email != null
? (rawEmailUsParams.email as {
[key: string]: unknown;
})
: {};

const enabled = !!rawEmailUsParams.enabled;

const to =
typeof rawEmailUsEmailParams.to === "string"
? rawEmailUsEmailParams.to
: "";

const subject =
typeof rawEmailUsEmailParams.subject === "string"
? rawEmailUsEmailParams.subject
: "";

const body =
typeof rawEmailUsEmailParams.body === "string"
? rawEmailUsEmailParams.body
: "";

if (enabled && to) {
return {
enabled,
email: {
to,
subject,
body,
},
};
}

return DEFAULT_APP_PARAMS["SESSION_CLASS_EMAIL_US"];
}
20 changes: 20 additions & 0 deletions tests/cypress/e2e/newSession.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,26 @@ describe("launch sessions", () => {

cy.get("#cloud-storage-example-storage-active").should("be.checked");
});

it('new session page - show "email us" link', () => {
fixtures.config({ fixture: "config-session-class-email-us.json" });
fixtures.userTest();
fixtures.newSessionImages();
cy.visit("/projects/e2e/local-test-project/sessions/new");
cy.contains("Need more compute resources?").should("be.visible");
cy.contains("a", "Email us!")
.should("be.visible")
.and("have.attr", "href", "mailto:[email protected]");
});

it('new session page - show "email us" link is disabled', () => {
fixtures.userTest();
fixtures.newSessionImages();
cy.visit("/projects/e2e/local-test-project/sessions/new");
cy.contains("Session requirements").should("be.visible");
cy.contains("Need more compute resources?").should("not.exist");
cy.contains("a", "Email us!").should("not.exist");
});
});

describe("launch sessions, outdated projects", () => {
Expand Down
Loading
Loading